/* 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_sdl3(void);

static VG_BOOL sdl3_window_open(int *, int *);
static void sdl3_window_close(void);
static void sdl3_window_flush(void);

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


/* set functions */
void
init_window_sdl3(void)
{
  vg4data.iolib.f.window_open = sdl3_window_open;
  vg4data.iolib.f.window_close = sdl3_window_close;
  vg4data.iolib.f.window_flush = sdl3_window_flush;
} /* Ende init_window_sdl3 */


/* 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;
  VG_BOOL rendfound;
  struct VG_PixelColor pxcol;

  envp = getenv("VG_RENDERER");
  if (envp != NULL && *envp == '\0') { envp = NULL; }
  rendfound = VG_FALSE;

  /* select renderer */
  { int ix0, ix1;
    const char *rd_name;
    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++) {
      rd_name = iolib->syms.SDL_GetRenderDriver(ix1);
      if (rd_name != NULL) {
        pinfo(" - %s\n", rd_name);
        if (envp != NULL && strcmp(envp, rd_name) == 0) {
          pinfo("   - trying this selected renderer\n");
          rendfound = VG_TRUE;
        }
      }
    }
  }
  if (envp != NULL && !rendfound) { outerr("Renderer \"%s\" not found", envp); return VG_FALSE; }

  /* create renderer */
  iolib->window.renderer = iolib->syms.SDL_CreateRenderer(iolib->window.wd, envp);
  if (iolib->window.renderer == NULL) {
    pinfo("creating renderer: %s\n", 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_SetRenderLogicalPresentation(iolib->window.renderer, v_width, v_height, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
    pinfo("Using integer-scaling\n");
  } else {
    iolib->syms.SDL_SetRenderLogicalPresentation(iolib->window.renderer, v_width, v_height, SDL_LOGICAL_PRESENTATION_LETTERBOX);
    pinfo("Using letterbox-scaling\n");
  }

  /* get the correct pixel format */
  iolib->window.pixelformat = SDL_PIXELFORMAT_UNKNOWN;
  { SDL_PropertiesID propid;
    if ((propid = iolib->syms.SDL_GetRendererProperties(iolib->window.renderer)) != 0) {
      const SDL_PixelFormat *texture_formats;
      pinfo("Using renderer: %s\n", iolib->syms.SDL_GetRendererName(iolib->window.renderer));
      texture_formats = (const SDL_PixelFormat *)iolib->syms.SDL_GetPointerProperty(propid, SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, NULL);
      if (texture_formats != NULL) {
        while (*texture_formats != SDL_PIXELFORMAT_UNKNOWN) {
          if (SDL_ISPIXELFORMAT_ALPHA(*texture_formats)
            && SDL_BYTESPERPIXEL(*texture_formats) == sizeof(Uint32)
            && !SDL_ISPIXELFORMAT_FOURCC(*texture_formats)
            && !SDL_ISPIXELFORMAT_INDEXED(*texture_formats)
          ) {
            iolib->window.pixelformat = *texture_formats;
            break;
          }
          texture_formats++;
        }
      }
    }
  }
  if (iolib->window.pixelformat == SDL_PIXELFORMAT_UNKNOWN) {
    outerr("renderer does not support required texture-format");
    return VG_FALSE;
  }

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

  { Uint32 pixelformat;
#ifdef SML3_ENDIAN_IS_LITTLE
    pixelformat = SDL_PIXELFORMAT_ABGR8888;
#else
    pixelformat = SDL_PIXELFORMAT_RGBA8888;
#endif
    iolib->window.btex = iolib->syms.SDL_CreateTexture(iolib->window.renderer, pixelformat, SDL_TEXTUREACCESS_STREAMING, v_width, v_height);
    if (iolib->window.btex == NULL) {
      pixelformat = iolib->window.pixelformat;
      iolib->window.btex = iolib->syms.SDL_CreateTexture(iolib->window.renderer, pixelformat, 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);
    if (vg4data.lists.window.scale_nearest) {
      iolib->syms.SDL_SetTextureScaleMode(iolib->window.btex, SDL_SCALEMODE_NEAREST);
      pinfo("Using nearest pixel sampling.\n");
    } else {
      iolib->syms.SDL_SetTextureScaleMode(iolib->window.btex, SDL_SCALEMODE_LINEAR);
      pinfo("Using linear pixel sampling.\n");
    }
    iolib->window.pixelformat = pixelformat;
  }
  iolib->window.bsurf = iolib->syms.SDL_CreateSurface(v_width, v_height, iolib->window.pixelformat);
  if (iolib->window.bsurf == NULL) {
    outerr("creating surface: %s", iolib->syms.SDL_GetError());
    return VG_FALSE;
  }

  return VG_TRUE;
} /* Ende create_renderer */


/* sdl3_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
sdl3_window_open(int *size, int *scale)
{
  struct iolib *iolib;
  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;
    SDL_PropertiesID props;
    border_top = border_left = border_bottom = border_right = 0;
    props = iolib->syms.SDL_CreateProperties();
    if (props != 0) {
      iolib->syms.SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true);
      iolib->syms.SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, vg4data.gamename);
      iolib->syms.SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, 320);
      iolib->syms.SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, 200);
      iolib->syms.SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
      iolib->syms.SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
      iolib->syms.SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MINIMIZED_BOOLEAN, true);
      wd = iolib->syms.SDL_CreateWindowWithProperties(props);
      if (wd != NULL) {
        sleep(1);
        if (iolib->syms.SDL_GetWindowBordersSize(wd, &border_top, &border_left, &border_bottom, &border_right)) {
          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);
      }
      iolib->syms.SDL_DestroyProperties(props);
    }
  }

  /* create window-properties */
  iolib->window.props = iolib->syms.SDL_CreateProperties();
  if (iolib->window.props == 0) {
    outerr("creating window-properties: %s", iolib->syms.SDL_GetError());
    return VG_FALSE;
  }
  iolib->syms.SDL_SetBooleanProperty(iolib->window.props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true);

  iolib->window.bestfactor = 10;

  { int i1, dspanz;
    SDL_DisplayID *dspids;
    SDL_Rect rect, rectmerk;
    memset(&rectmerk, 0, sizeof(rectmerk));
    dspids = iolib->syms.SDL_GetDisplays(&dspanz);
    for (i1 = 0; i1 < dspanz; i1++) {
      if (!iolib->syms.SDL_GetDisplayUsableBounds(dspids[i1], &rect)) {
        pinfo("Cannot get bounds for display-index %d\n", i1);
        continue;
      }
      pinfo("Display-index %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;
        }
      }
    }
    iolib->syms.SDL_free(dspids);
    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) {
    iolib->syms.SDL_SetBooleanProperty(iolib->window.props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, true);
    winw = v_width;
    winh = v_height;
    pinfo("Fullscreen-window\n");
  } else if (*scale == VG_WINDOW_SCALE_MAX) {
    /* scaling and centering activates only after receiving a window-event */
    iolib->syms.SDL_SetBooleanProperty(iolib->window.props, SDL_PROP_WINDOW_CREATE_MAXIMIZED_BOOLEAN, true);
    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->syms.SDL_SetStringProperty(iolib->window.props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, vg4data.gamename);
  iolib->syms.SDL_SetNumberProperty(iolib->window.props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, winw);
  iolib->syms.SDL_SetNumberProperty(iolib->window.props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, winh);
  iolib->syms.SDL_SetNumberProperty(iolib->window.props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
  iolib->syms.SDL_SetNumberProperty(iolib->window.props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
  iolib->window.wd = iolib->syms.SDL_CreateWindowWithProperties(iolib->window.props);
  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_ScreenSaverEnabled()) {
    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 sdl3_window_open */


/* sdl3_window_close:
 * close window
 */
static void
sdl3_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_DestroySurface(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;
  iolib->syms.SDL_DestroyProperties(iolib->window.props);
  iolib->window.props = 0;
} /* Ende sdl3_window_close */


/* sdl3_window_flush:
 * flush backbuffer to window
 */
static void
sdl3_window_flush(void)
{
  struct iolib *iolib;
  SDL_FRect 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 = (float)vg4data.backbuf.img->img.w;
  dstrect.h = (float)vg4data.backbuf.img->img.h;

  if (!iolib->syms.SDL_LockTexture(iolib->window.btex, NULL, &tex_pixels, &tex_pitch)) {
    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.pixelformat == SDL_PIXELFORMAT_ABGR8888) { use_u = 1; } else { use_u = 0; }
#else
  if (iolib->window.pixelformat == 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_MapSurfaceRGBA(iolib->window.bsurf, 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_MapSurfaceRGBA(iolib->window.bsurf, 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_RenderTexture(iolib->window.renderer, iolib->window.btex, NULL, &dstrect);

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