/* Copyright 2022-2026 Kurt Nienhaus
 *
 * This file is part of VgaGames.
 * VgaGames is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * VgaGames is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with VgaGames.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "iolib.h"
#include "window/window.h"
#include "image/image.h"

void init_window_sdl2(void);

static VG_BOOL sdl2_window_open(int *, int *);
static void sdl2_window_close(void);
static void sdl2_window_flush(void);

static struct iolib * get_iolib(void);
static VG_BOOL create_renderer(struct iolib *, int, int);


/* set functions */
void
init_window_sdl2(void)
{
  vg4data.iolib.f.window_open = sdl2_window_open;
  vg4data.iolib.f.window_close = sdl2_window_close;
  vg4data.iolib.f.window_flush = sdl2_window_flush;
} /* Ende init_window_sdl2 */


/* check if is valid and return iolib pointer */
static struct iolib *
get_iolib(void)
{
  struct iolib *iolib = (struct iolib *)vg4data.iolib.stptr;

  if (iolib == NULL) { return NULL; }
  if (iolib->window.wd == NULL) { return NULL; }

  return iolib;
} /* Ende get_iolib */


/* create renderer of window */
static VG_BOOL
create_renderer(struct iolib *iolib, int v_width, int v_height)
{
  char *envp;
  SDL_RendererInfo renderinfo;
  int i1, rendidx;
  struct VG_PixelColor pxcol;

  iolib->syms.SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
  if (vg4data.lists.window.scale_nearest) {
    iolib->syms.SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
    pinfo("Using nearest pixel sampling.\n");
  }

  envp = getenv("VG_RENDERER");
  if (envp != NULL && *envp == '\0') { envp = NULL; }
  rendidx = -1;

  /* select renderer */
  { int ix0, ix1, ix2;
    ix0 = iolib->syms.SDL_GetNumRenderDrivers();
    pinfo("Found renderers:\n");
    if (envp != NULL) {
      pinfo("(environment variable VG_RENDERER is set to \"%s\")\n", envp);
    } else {
      pinfo("(use environment variable VG_RENDERER to set arbitrary renderer)\n");
    }
    for (ix1 = 0; ix1 < ix0; ix1++) {
      int has_target, is_accel, is_software, has_bits, has_alpha, has_palette, is_fourcc;
      if (iolib->syms.SDL_GetRenderDriverInfo(ix1, &renderinfo) < 0) { continue; }
      has_target = !!(renderinfo.flags & SDL_RENDERER_TARGETTEXTURE);
      if (!has_target) { continue; }
      is_accel = !!(renderinfo.flags & SDL_RENDERER_ACCELERATED);
      is_software = !!(renderinfo.flags & SDL_RENDERER_SOFTWARE);
      pinfo(" - %s [%s, %s]:\n", renderinfo.name,
              is_accel ? "accelerated" : "not accelerated",
              is_software ? "software" : "hardware");
      if (envp != NULL && strcmp(renderinfo.name, envp) == 0) {
        rendidx = ix1;
        pinfo("   - trying this selected renderer\n");
      }
      for (ix2 = 0; ix2 < (int)renderinfo.num_texture_formats; ix2++) {
        has_bits = SDL_BITSPERPIXEL(renderinfo.texture_formats[ix2]);
        has_alpha = SDL_ISPIXELFORMAT_ALPHA(renderinfo.texture_formats[ix2]);
        has_palette = SDL_ISPIXELFORMAT_INDEXED(renderinfo.texture_formats[ix2]);
        is_fourcc = SDL_ISPIXELFORMAT_FOURCC(renderinfo.texture_formats[ix2]);
        pinfo("   - has texture with %d bits, %s, %s, %s\n", has_bits,
                has_alpha ? "with alpha-byte" : "without alpha-byte",
                has_palette ? "with palette" : "without palette",
                is_fourcc ? "unique format" : "no unique format");
      }
    }
  }
  if (envp != NULL && rendidx < 0) { outerr("Renderer \"%s\" not found", envp); return VG_FALSE; }

  /* create renderer */
  if (rendidx >= 0) {
    iolib->window.renderer = iolib->syms.SDL_CreateRenderer(iolib->window.wd, rendidx, 0);
  } else {
    iolib->window.renderer = iolib->syms.SDL_CreateRenderer(iolib->window.wd, -1,
                             SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
  }
  if (iolib->window.renderer == NULL) {
    pinfo("creating %s-renderer: %s\n", (envp != NULL ? envp : "hardware"), iolib->syms.SDL_GetError());
    iolib->window.renderer = iolib->syms.SDL_CreateRenderer(iolib->window.wd, -1,
                             SDL_RENDERER_SOFTWARE | SDL_RENDERER_TARGETTEXTURE);
    if (iolib->window.renderer == NULL) {
      outerr("creating renderer failed: %s", iolib->syms.SDL_GetError());
      return VG_FALSE;
    }
  }

  /* configure renderer */
  GET_RGBA(VG_COLOR_TRANSPARENT, &pxcol.r, &pxcol.g, &pxcol.b, &pxcol.a);
  iolib->syms.SDL_SetRenderDrawColor(iolib->window.renderer, pxcol.r, pxcol.g, pxcol.b, pxcol.a);
  iolib->syms.SDL_RenderClear(iolib->window.renderer);
  if (vg4data.lists.window.scale_integer) {
    iolib->syms.SDL_RenderSetIntegerScale(iolib->window.renderer, SDL_TRUE);
    pinfo("Using integer-scaling\n");
  }
  iolib->syms.SDL_RenderSetLogicalSize(iolib->window.renderer, v_width, v_height);

  /* get the correct pixel format */
  iolib->window.pixelformatenum = SDL_PIXELFORMAT_UNKNOWN;
  if (iolib->syms.SDL_GetRendererInfo(iolib->window.renderer, &renderinfo) == 0) {
    pinfo("Using renderer: %s\n", renderinfo.name);
    if (!(renderinfo.flags & SDL_RENDERER_TARGETTEXTURE)) {
      outerr("renderer \"%s\" does not support renderering to texture", renderinfo.name);
      return VG_FALSE;
    }
    for (i1 = 0; i1 < (int)renderinfo.num_texture_formats; i1++) {
      if (SDL_ISPIXELFORMAT_ALPHA(renderinfo.texture_formats[i1])
        && SDL_BYTESPERPIXEL(renderinfo.texture_formats[i1]) == sizeof(Uint32)
        && !SDL_ISPIXELFORMAT_FOURCC(renderinfo.texture_formats[i1])
        && !SDL_ISPIXELFORMAT_INDEXED(renderinfo.texture_formats[i1])
      ) {
        iolib->window.pixelformatenum = renderinfo.texture_formats[i1];
        break;
      }
    }
  }
  if (iolib->window.pixelformatenum == SDL_PIXELFORMAT_UNKNOWN) {
    outerr("renderer \"%s\" does not support required texture-format", renderinfo.name);
    return VG_FALSE;
  }

  /* create backbuffer */
  vg4data.backbuf.img = vg4_image_create_nolist(v_width, v_height);

  { Uint32 pixelformatenum;
#ifdef SML3_ENDIAN_IS_LITTLE
    pixelformatenum = SDL_PIXELFORMAT_ABGR8888;
#else
    pixelformatenum = SDL_PIXELFORMAT_RGBA8888;
#endif
    iolib->window.btex = iolib->syms.SDL_CreateTexture(iolib->window.renderer, pixelformatenum, SDL_TEXTUREACCESS_STREAMING, v_width, v_height);
    if (iolib->window.btex == NULL) {
      pixelformatenum = iolib->window.pixelformatenum;
      iolib->window.btex = iolib->syms.SDL_CreateTexture(iolib->window.renderer, pixelformatenum, SDL_TEXTUREACCESS_STREAMING, v_width, v_height);
    }
    if (iolib->window.btex == NULL) {
      outerr("creating texture: %s", iolib->syms.SDL_GetError());
      return VG_FALSE;
    }
    iolib->syms.SDL_SetTextureBlendMode(iolib->window.btex, SDL_BLENDMODE_NONE);
    iolib->window.pixelformatenum = pixelformatenum;
  }
  { SDL_Surface *tsurf = iolib->syms.SDL_CreateRGBSurface(0, v_width, v_height, 32, 0, 0, 0, 0);
    if (tsurf == NULL) {
      outerr("creating surface: %s", iolib->syms.SDL_GetError());
      return VG_FALSE;
    }
    iolib->window.bsurf = iolib->syms.SDL_ConvertSurfaceFormat(tsurf, iolib->window.pixelformatenum, 0);
    if (iolib->window.bsurf == NULL) {
      outerr("converting surface: %s", iolib->syms.SDL_GetError());
      iolib->syms.SDL_FreeSurface(tsurf);
      return VG_FALSE;
    }
    iolib->syms.SDL_FreeSurface(tsurf);
  }

  return VG_TRUE;
} /* Ende create_renderer */


/* sdl2_window_open:
 * open window
 * @param size   address: one of VG_WINDOW_SIZES
 * @param scale  address: one of VG_WINDOW_SCALES
 * @return  VG_TRUE = OK or VG_FALSE = error
 */
static VG_BOOL
sdl2_window_open(int *size, int *scale)
{
  struct iolib *iolib;
  Uint32 winflags;
  int v_width, v_height, winw, winh;
  struct VG_ImagecopyAttr iattr;

  iolib = (struct iolib *)vg4data.iolib.stptr;
  if (iolib == NULL) { outerr("Not initalized"); return VG_FALSE; }
  if (iolib->window.wd != NULL) { outerr("Window exists"); return VG_FALSE; }

  if (size == NULL || scale == NULL) { outerr("Parameter NULL"); return VG_FALSE; }

  if (*size == VG_WINDOW_SIZE_LOW) {
    if (vg4data.lists.window.size_extended) {
      v_width = 320;
      v_height = 240;
    } else {
      v_width = 320;
      v_height = 200;
    }
  } else if (*size == VG_WINDOW_SIZE_HIGH) {
    if (vg4data.lists.window.size_extended) {
      v_width = 800;
      v_height = 600;
    } else {
      v_width = 640;
      v_height = 480;
    }
  } else {
    outerr("Opening window: no suitable window-size given: %d\n", *size);
    return VG_FALSE;
  }

  /* get window borders */
  if (!vg4data.lists.window.border.isset) {
    int border_top, border_left, border_bottom, border_right;
    SDL_Window *wd;
    Uint32 wdflags;
    border_top = border_left = border_bottom = border_right = 0;
    wdflags = SDL_WINDOW_MINIMIZED | SDL_WINDOW_RESIZABLE;
    wd = iolib->syms.SDL_CreateWindow(vg4data.gamename, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 320, 200, wdflags);
    if (wd != NULL) {
      sleep(1);
      if (iolib->syms.SDL_GetWindowBordersSize(wd, &border_top, &border_left, &border_bottom, &border_right) == 0) {
        pinfo("Window-borders: top=%d, left=%d, bottom=%d, right=%d\n", border_top, border_left, border_bottom, border_right);
        vg4data.lists.window.border.isset = VG_TRUE;
        vg4data.lists.window.border.top = border_top;
        vg4data.lists.window.border.left = border_left;
        vg4data.lists.window.border.bottom = border_bottom;
        vg4data.lists.window.border.right = border_right;
      }
      iolib->syms.SDL_DestroyWindow(wd);
    }
  }

  winflags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
  iolib->window.bestfactor = 10;

  { int i1;
    SDL_Rect rect, rectmerk;
    memset(&rectmerk, 0, sizeof(rectmerk));
    for (i1 = iolib->syms.SDL_GetNumVideoDisplays() - 1; i1 >= 0; i1--) {
      if (iolib->syms.SDL_GetDisplayUsableBounds(i1, &rect) < 0) {
        pinfo("Cannot get bounds for display %d\n", i1);
        continue;
      }
      pinfo("Display %d:  %d+%d x %d+%d px\n", i1, rect.x, rect.w, rect.y, rect.h);
      if (v_width <= rect.w && v_height <= rect.h) {
        if (rectmerk.w == 0 || rectmerk.w >= rect.w || rectmerk.h >= rect.h) {
          rectmerk = rect;
        }
      }
    }
    if (rectmerk.w > 0) {
      int wscale, hscale;
      if (vg4data.lists.window.border.isset) {
        wscale = (rectmerk.w - vg4data.lists.window.border.left - vg4data.lists.window.border.right) * 10 / v_width;
        hscale = (rectmerk.h - vg4data.lists.window.border.top - vg4data.lists.window.border.bottom) * 10 / v_height;
      } else {
        wscale = (rectmerk.w - 10) * 10 / v_width;
        hscale = (rectmerk.h - 10) * 10 / v_height;
      }
      iolib->window.bestfactor = (wscale < hscale ? wscale : hscale);
      if (iolib->window.bestfactor < 10) { iolib->window.bestfactor = 10; }
      if (vg4data.lists.window.scale_integer) { iolib->window.bestfactor = iolib->window.bestfactor / 10 * 10; }
    }
  }

  if (*scale == VG_WINDOW_SCALE_FULL) {
    winflags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
    winw = v_width;
    winh = v_height;
    pinfo("Fullscreen-window\n");
  } else if (*scale == VG_WINDOW_SCALE_MAX) {
    /* scaling and centering activates only after a SDL_WINDOWEVENT_SIZE_CHANGED */
    winflags |= SDL_WINDOW_MAXIMIZED;
    winw = v_width;
    winh = v_height;
    pinfo("Maximizing window\n");
  } else if (*scale == VG_WINDOW_SCALE_BEST) {
    winw = v_width * iolib->window.bestfactor / 10;
    winh = v_height * iolib->window.bestfactor / 10;
    pinfo("Scaling window: %d.%dx\n", iolib->window.bestfactor / 10, iolib->window.bestfactor % 10);
  } else {
    *scale = VG_WINDOW_SCALE_NONE;
    winw = v_width;
    winh = v_height;
    pinfo("Not scaling window\n");
  }

  /* create window */
  iolib->window.wd = iolib->syms.SDL_CreateWindow(vg4data.gamename, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, winw, winh, winflags);
  if (iolib->window.wd == NULL) {
    outerr("creating window: %s", iolib->syms.SDL_GetError());
    return VG_FALSE;
  }
  pinfo("Window opened with %dx%d.\n", v_width, v_height);

  /* set pixel-modifying part of image-copy attributes of window to default */
  VG_IMAGECOPY_ATTR_DEFAULT(&iattr);
  vg4data.backbuf.iattr_pixel = iattr.pixel;

  /* set common brightness for window to default */
  vg4data.backbuf.wbright = 100;

  /* deactivate ScreenSaver */
  iolib->syms.SDL_DisableScreenSaver();
  if (iolib->syms.SDL_IsScreenSaverEnabled() == SDL_TRUE) {
    pinfo("Cannot disable ScreenSaver\n");
  }

  /* create renderer */
  if (!create_renderer(iolib, v_width, v_height)) {
    vg4->window->close();
    return VG_FALSE;
  }

  return VG_TRUE;
} /* Ende sdl2_window_open */


/* sdl2_window_close:
 * close window
 */
static void
sdl2_window_close(void)
{
  struct iolib *iolib;

  if ((iolib = get_iolib()) == NULL) { return; }

  if (vg4data.backbuf.img != NULL) { vg4->image->destroy(vg4data.backbuf.img); }
  if (iolib->window.btex != NULL) { iolib->syms.SDL_DestroyTexture(iolib->window.btex); }
  if (iolib->window.bsurf != NULL) { iolib->syms.SDL_FreeSurface(iolib->window.bsurf); }
  if (iolib->window.renderer != NULL) {
    iolib->syms.SDL_DestroyRenderer(iolib->window.renderer);
  }
  iolib->syms.SDL_DestroyWindow(iolib->window.wd);
  iolib->window.wd = NULL;
} /* Ende sdl2_window_close */


/* sdl2_window_flush:
 * flush backbuffer to window
 */
static void
sdl2_window_flush(void)
{
  struct iolib *iolib;
  SDL_Rect dstrect;
  struct VG_Rect src_rect, dst_rect;
  void *tex_pixels;
  int tex_pitch, xpos, ypos, use_u, shake_x, shake_y;
  VG_BOOL iattr_is_default;
  Uint32 *tex_px;
  struct VG_PixelColor *src_px, pxcol, pxctransp;
  union { struct VG_PixelColor px; Uint32 i; } u;

  if ((iolib = get_iolib()) == NULL) { return; }

  dstrect.x = dstrect.y = 0;
  dstrect.w = vg4data.backbuf.img->img.w;
  dstrect.h = vg4data.backbuf.img->img.h;

  if (iolib->syms.SDL_LockTexture(iolib->window.btex, NULL, &tex_pixels, &tex_pitch) < 0) {
    outerr("locking texture: %s", iolib->syms.SDL_GetError());
    return;
  }

  GET_RGBA(VG_COLOR_TRANSPARENT, &pxctransp.r, &pxctransp.g, &pxctransp.b, &pxctransp.a);

  src_rect.x = src_rect.y = 0;
  src_rect.w = vg4data.backbuf.img->img.w;
  src_rect.h = vg4data.backbuf.img->img.h;
  dst_rect.x = dst_rect.y = 0;
  dst_rect.w = vg4data.backbuf.img->img.w;
  dst_rect.h = vg4data.backbuf.img->img.h;

#ifdef SML3_ENDIAN_IS_LITTLE
  if (iolib->window.pixelformatenum == SDL_PIXELFORMAT_ABGR8888) { use_u = 1; } else { use_u = 0; }
#else
  if (iolib->window.pixelformatenum == SDL_PIXELFORMAT_RGBA8888) { use_u = 1; } else { use_u = 0; }
#endif

  /* set shaking values */
  shake_x = shake_y = 0;
  if (vg4data.backbuf.shake.axe == VG_AXE_HORIZONTAL || vg4data.backbuf.shake.axe == VG_AXE_VERTICAL) {
    if (vg4data.backbuf.shake.value > 0) {
      int vorz;
      if (vg4data.backbuf.shake.value % 2 == 1) { vorz = 1; } else { vorz = -1; }
      if (vg4data.backbuf.shake.axe == VG_AXE_HORIZONTAL) { shake_x = vorz; }
      if (vg4data.backbuf.shake.axe == VG_AXE_VERTICAL) { shake_y = vorz; }
      vg4data.backbuf.shake.value--;
    }
    if (vg4data.backbuf.shake.value <= 0) { vg4data.backbuf.shake.axe = VG_AXE_NONE; }
  }

  if (shake_x || shake_y) {  /* use shaking */
    struct VG_Position posb;
    Uint32 utrans;

    posb.pos = VG_POS_UPPER_LEFT;
    posb.x = shake_x;
    posb.y = shake_y;

    /* clear texture */
    utrans = iolib->syms.SDL_MapRGBA(iolib->window.bsurf->format, pxctransp.r, pxctransp.g, pxctransp.b, pxctransp.a);
    for (ypos = 0; ypos < vg4data.backbuf.img->img.h; ypos++) {
      tex_px = (Uint32 *)((unsigned char *)tex_pixels + ypos * tex_pitch);
      for (xpos = 0; xpos < vg4data.backbuf.img->img.w; xpos++) { tex_px[xpos] = utrans; }
    }

    if (!vg4_rect_overlap(&posb, &src_rect, &dst_rect)) { goto endflush; }
  }

  iattr_is_default = vg4_attrpixel_is_default(&vg4data.backbuf.iattr_pixel);
  if (vg4data.backbuf.wbright != 100) { iattr_is_default = VG_FALSE; }

  if (use_u && iattr_is_default && !shake_x && !shake_y && tex_pitch == (int)(dst_rect.w * sizeof(*tex_px))) {
    size_t mlen = tex_pitch * dst_rect.h;
    src_px = vg4data.backbuf.img->img.pixels;
    tex_px = (Uint32 *)tex_pixels;
    memcpy(tex_px, src_px, mlen);
  } else if (use_u && iattr_is_default) {
    size_t mlen = dst_rect.w * sizeof(*tex_px);
    for (ypos = 0; ypos < dst_rect.h; ypos++) {
      src_px = &vg4data.backbuf.img->img.pixels[(src_rect.y + ypos) * vg4data.backbuf.img->img.w];
      tex_px = (Uint32 *)((unsigned char *)tex_pixels + (dst_rect.y + ypos) * tex_pitch);
      memcpy(&tex_px[dst_rect.x], &src_px[src_rect.x], mlen);
    }
  } else {
    struct VG_ImagecopyAttrPixel iattr_pixel = vg4data.backbuf.iattr_pixel;
    const struct VG_ImagecopyAttrPixel *iattr_pixelp = &iattr_pixel;
    iattr_pixel.brightness = iattr_pixel.brightness * vg4data.backbuf.wbright / 100;
    if (iattr_pixel.brightness > VG_MAX_BRIGHTNESS) { iattr_pixel.brightness = VG_MAX_BRIGHTNESS; }
#define PIXMOD_PIXSRC       src_px[src_rect.x + xpos]
#define PIXMOD_IATTR_PIXEL  iattr_pixelp
#define PIXMOD_PIXRET       pxcol
    for (ypos = 0; ypos < dst_rect.h; ypos++) {
      src_px = &vg4data.backbuf.img->img.pixels[(src_rect.y + ypos) * vg4data.backbuf.img->img.w];
      tex_px = (Uint32 *)((unsigned char *)tex_pixels + (dst_rect.y + ypos) * tex_pitch);
      for (xpos = 0; xpos < dst_rect.w; xpos++) {
#include "image/pixmod.h"
        if (use_u) {
          u.px = pxcol; tex_px[dst_rect.x + xpos] = u.i;
        } else {
          tex_px[dst_rect.x + xpos] = iolib->syms.SDL_MapRGBA(iolib->window.bsurf->format, pxcol.r, pxcol.g, pxcol.b, pxcol.a);
        }
      }
    }
#undef PIXMOD_PIXRET
#undef PIXMOD_IATTR_PIXEL
#undef PIXMOD_PIXSRC
  }

endflush:
  iolib->syms.SDL_UnlockTexture(iolib->window.btex);

  iolib->syms.SDL_SetRenderDrawColor(iolib->window.renderer, pxctransp.r, pxctransp.g, pxctransp.b, pxctransp.a);
  iolib->syms.SDL_RenderClear(iolib->window.renderer);

  iolib->syms.SDL_RenderCopy(iolib->window.renderer, iolib->window.btex, NULL, &dstrect);

  iolib->syms.SDL_RenderPresent(iolib->window.renderer);
} /* Ende sdl2_window_flush */
