/* 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 "window.h"

void init_window(void);
void dest_window(void);

struct VG_Image * vg4_window_clone_nolist(const struct VG_Rect *, const struct VG_ImagecopyAttr *);

static VG_BOOL window_size_extend(VG_BOOL *);
static VG_BOOL window_open(int, int);
static void window_close(void);
static VG_BOOL window_reopen(int, int);
static void window_getparameters(int *, int *);
static void window_getsize(int *, int *);
static void window_setbrightness(int);
static int window_getbrightness(void);
static void window_setattr(struct VG_ImagecopyAttrPixel *);
static struct VG_ImagecopyAttrPixel window_getattr(void);
static void window_scale_params(VG_BOOL, VG_BOOL *, VG_BOOL *);
static void window_shake(int);
static void window_clear(void);
static void window_fill(int);
static void window_copy(const struct VG_Image *, const struct VG_Position *, const struct VG_ImagecopyAttr *);
static struct VG_PixelColor window_getpoint(int, int);
static void window_draw_point(int, int, int);
static void window_draw_points(const struct VG_Point *, size_t, int);
static void window_draw_line(int, int, int, int, int);
static void window_draw_rect(const struct VG_Rect *, int, VG_BOOL);
static void window_draw_circle(int, int, int, int, VG_BOOL);
static void window_flush(void);
static struct VG_Image * window_clone(const struct VG_Rect *, const struct VG_ImagecopyAttr *);
static struct VG_Image * window_clone_doit(const struct VG_Rect *, const struct VG_ImagecopyAttr *, VG_BOOL);
static VG_BOOL window_screenshot(const char *);


/* set functions */
void
init_window(void)
{
  vg4data.lists.window.wsize = 0;
  vg4data.lists.window.scale = 0;
  vg4data.lists.window.size_extended = VG_FALSE;
  vg4data.lists.window.scale_nearest = VG_FALSE;
  vg4data.lists.window.scale_integer = VG_FALSE;
  vg4data.lists.window.border.isset = VG_FALSE;
  vg4->window->size_extend = window_size_extend;
  vg4->window->open = window_open;
  vg4->window->close = window_close;
  vg4->window->reopen = window_reopen;
  vg4->window->getparameters = window_getparameters;
  vg4->window->getsize = window_getsize;
  vg4->window->setbrightness = window_setbrightness;
  vg4->window->getbrightness = window_getbrightness;
  vg4->window->setattr = window_setattr;
  vg4->window->getattr = window_getattr;
  vg4->window->scale_params = window_scale_params;
  vg4->window->shake = window_shake;
  vg4->window->clear = window_clear;
  vg4->window->fill = window_fill;
  vg4->window->copy = window_copy;
  vg4->window->getpoint = window_getpoint;
  vg4->window->draw_point = window_draw_point;
  vg4->window->draw_points = window_draw_points;
  vg4->window->draw_line = window_draw_line;
  vg4->window->draw_rect = window_draw_rect;
  vg4->window->draw_circle = window_draw_circle;
  vg4->window->flush = window_flush;
  vg4->window->clone = window_clone;
  vg4->window->screenshot = window_screenshot;
} /* Ende init_window */

void
dest_window(void)
{
  vg4->window->close();
} /* Ende dest_window */


/* vg4_window_clone_nolist: like window_clone(), but with hidden-inserting into list */
struct VG_Image *
vg4_window_clone_nolist(const struct VG_Rect *rect, const struct VG_ImagecopyAttr *iattr)
{
  return window_clone_doit(rect, iattr, VG_FALSE);
} /* Ende vg4_window_clone_nolist */


/* window_size_extend:
 * switch on or off size-extension for opening window
 * - VG_WINDOW_SIZE_LOW
 *   size-extension off: 320x200
 *   size-extension on:  320x240
 * - VG_WINDOW_SIZE_HIGH
 *   size-extension off: 640x480
 *   size-extension on:  800x600
 * @param on  address to boolean: VG_TRUE = on, VG_FALSE = off
 *            or NULL: don't change
 * @return  previous value
 */
static VG_BOOL
window_size_extend(VG_BOOL *on)
{
  VG_BOOL size_extended = vg4data.lists.window.size_extended;
  if (on != NULL) { vg4data.lists.window.size_extended = *on; }
  return size_extended;
} /* Ende window_size_extend */


/* window_open:
 * open window
 * @param size   one of VG_WINDOW_SIZES
 * @param scale  one of VG_WINDOW_SCALES
 * @return  VG_TRUE = OK or VG_FALSE = error
 */
static VG_BOOL
window_open(int size, int scale)
{
  VG_BOOL retw = vg4data.iolib.f.window_open(&size, &scale);
  if (retw) {
    vg4data.lists.window.wsize = size;
    vg4data.lists.window.scale = scale;
    retw = vg4->input->open();
  }
  return retw;
} /* Ende window_open */


/* window_close:
 * close window
 */
static void
window_close(void)
{
  vg4->input->close();
  vg4data.iolib.f.window_close();
  vg4data.lists.window.wsize = 0;
  vg4data.lists.window.scale = 0;
  /* vg4data.lists.window.size_extended = VG_FALSE;  // not to be changed */
  vg4data.lists.window.scale_nearest = VG_FALSE;
  vg4data.lists.window.scale_integer = VG_FALSE;
  /* vg4data.lists.window.border.isset = VG_FALSE;  // not to be changed */
} /* Ende window_close */


/* window_reopen:
 * reopen window
 * @param size   one of VG_WINDOW_SIZES or -1 = don't change
 * @param scale  one of VG_WINDOW_SCALES or -1 = don't change
 * @return  VG_TRUE = OK or VG_FALSE = error
 */
static VG_BOOL
window_reopen(int size, int scale)
{
  VG_BOOL retw, redo;
  int osize, oscale;
  struct VG_Image *wclon;

  osize = vg4data.lists.window.wsize;
  oscale = vg4data.lists.window.scale;
  if (osize == 0 || oscale == 0) { redo = VG_FALSE; } else { redo = VG_TRUE; }

  if (size <= 0) { size = osize; }
  if (scale < 0) { scale = oscale; }

  wclon = vg4_window_clone_nolist(NULL, NULL);

  /* close window */
  vg4data.iolib.f.window_close();
  vg4data.lists.window.wsize = 0;
  vg4data.lists.window.scale = 0;

reopen_redo:
  /* open window with size and scale */
  pinfo("Reopening window\n");
  retw = vg4data.iolib.f.window_open(&size, &scale);
  if (retw) {  /* OK */
    vg4data.lists.window.wsize = size;
    vg4data.lists.window.scale = scale;
    vg4->input->reopen();
    vg4->window->copy(wclon, NULL, NULL);
  } else if (redo) {  /* failed, open window with old size and scale */
    pwarn("Reopening window with size-def=%d and scale-def=%d failed\n", size, scale);
    size = osize;
    scale = oscale;
    redo = VG_FALSE;
    goto reopen_redo;
  } else {  /* opening window failed completely */
    vg4->input->close();
  }

  vg4->image->destroy(wclon);

  return retw;
} /* Ende window_reopen */


/* window_getparameters:
 * get parameters of the window
 * @param size   for returning size-parameter (one of VG_WINDOW_SIZES), if not NULL
 * @param scale  for returning scale-parameter (one of VG_WINDOW_SCALES), if not NULL
 */
static void
window_getparameters(int *size, int *scale)
{
  if (size != NULL) { *size = vg4data.lists.window.wsize; }
  if (scale != NULL) { *scale = vg4data.lists.window.scale; }
} /* Ende window_getparameters */


/* window_getsize:
 * get virtual size of the window
 * @param width   for returning width if not NULL
 * @param height  for returning height if not NULL
 */
static void
window_getsize(int *width, int *height)
{
  vg4->image->getsize(vg4data.backbuf.img, NULL, width, height);
} /* Ende window_getsize */


/* window_setbrightness:
 * set common window brightness
 * @param brightness  brightness, from 0 to VG_MAX_BRIGHTNESS, (default = 100)
 */
static void
window_setbrightness(int brightness)
{
  if (brightness < 0 || brightness > VG_MAX_BRIGHTNESS) { brightness = 100; }
  vg4data.backbuf.wbright = brightness;
} /* Ende window_setbrightness */


/* window_getbrightness:
 * get common window brightness
 * @return brightness
 */
static int
window_getbrightness(void)
{
  return vg4data.backbuf.wbright;
} /* Ende window_getbrightness */


/* window_setattr:
 * set window attributes
 * @param iattr_pixel  pixel-modifying part of image-copy attributes,
 *                     or NULL = set to default
 */
static void
window_setattr(struct VG_ImagecopyAttrPixel *iattr_pixel)
{
  if (iattr_pixel == NULL) {
    struct VG_ImagecopyAttr iattr;
    VG_IMAGECOPY_ATTR_DEFAULT(&iattr);
    vg4data.backbuf.iattr_pixel = iattr.pixel;
  } else {
    vg4data.backbuf.iattr_pixel = *iattr_pixel;
  }
} /* Ende window_setattr */


/* window_getattr:
 * get window attributes
 * @return pixel-modifying part of image-copy attributes
 */
struct VG_ImagecopyAttrPixel
window_getattr(void)
{
  return vg4data.backbuf.iattr_pixel;
} /* Ende window_getattr */


/* window_scale_params:
 * set or get scaling parameters
 * @param do_set         VG_TRUE = set, VG_FALSE = get
 * @param scale_nearest  whether using nearest instead of linear scaling, may be NULL
 * @param scale_integer  whether using integer instead of float scaling, may be NULL
 *
 * After setting the window must be reopened to take effect.
 */
static void
window_scale_params(VG_BOOL do_set, VG_BOOL *scale_nearest, VG_BOOL *scale_integer)
{
  if (scale_nearest != NULL) {
    if (do_set) {
      vg4data.lists.window.scale_nearest = *scale_nearest;
    } else {
      *scale_nearest = vg4data.lists.window.scale_nearest;
    }
  }
  if (scale_integer != NULL) {
    if (do_set) {
      vg4data.lists.window.scale_integer = *scale_integer;
    } else {
      *scale_integer = vg4data.lists.window.scale_integer;
    }
  }
} /* Ende window_scale_params */


/* window_shake:
 * set shaking of the window
 * @param axe    one of VG_AXES
 */
static void
window_shake(int axe)
{
  if (axe != VG_AXE_HORIZONTAL && axe != VG_AXE_VERTICAL) { return; }

  vg4data.backbuf.shake.value = 7;
  vg4data.backbuf.shake.axe = axe;
} /* Ende window_shake */


/* window_clear:
 * clear backbuffer of window
 */
static void
window_clear(void)
{
  vg4->image->clear(vg4data.backbuf.img);
} /* Ende window_clear */


/* window_fill:
 * fill backbuffer of window with a color
 */
static void
window_fill(int color)
{
  vg4->image->fill(vg4data.backbuf.img, color);
} /* Ende window_fill */


/* window_copy:
 * copy image onto backbuffer of window
 * @param imgsrc  source image, or NULL = backbuffer
 * @param posdst  destination position, or NULL = copy centered
 * @param iattr   image-copy attributes, or NULL
 */
static void
window_copy(const struct VG_Image *imgsrc, const struct VG_Position *posdst, const struct VG_ImagecopyAttr *iattr)
{
  if (imgsrc != NULL) {
    vg4->image->copy(vg4data.backbuf.img, imgsrc, posdst, iattr);
  } else {
    struct VG_Image *wclon = vg4_window_clone_nolist(NULL, NULL);
    vg4->window->clear();
    vg4->image->copy(vg4data.backbuf.img, wclon, posdst, iattr);
    vg4->image->destroy(wclon);
  }
} /* Ende window_copy */


/* window_getpoint:
 * get a pixel from the window
 * @param xp     x-coordinate of the pixel
 * @param yp     y-coordinate of the pixel
 */
static struct VG_PixelColor
window_getpoint(int xp, int yp)
{
  return vg4->image->getpoint(vg4data.backbuf.img, xp, yp);
} /* Ende window_getpoint */


/* window_draw_point:
 * draw a pixel onto window
 * @param xp     x-coordinate of the pixel
 * @param yp     y-coordinate of the pixel
 * @param color  pixel color (VG_COLOR_*)
 */
static void
window_draw_point(int xp, int yp, int color)
{
  vg4->image->draw_point(vg4data.backbuf.img, xp, yp, color);
} /* Ende window_draw_point */


/* window_draw_points:
 * draw pixels onto window
 * @param points  array of pixel-positions
 * @param count   number of pixel-positions
 * @param color   pixel color (VG_COLOR_*)
 */
static void
window_draw_points(const struct VG_Point *points, size_t count, int color)
{
  vg4->image->draw_points(vg4data.backbuf.img, points, count, color);
} /* Ende window_draw_points */


/* window_draw_line:
 * draw a line onto window
 * @param x1     x-coordinate of the start point
 * @param y1     y-coordinate of the start point
 * @param x2     x-coordinate of the end point
 * @param y2     y-coordinate of the end point
 * @param color  line color (VG_COLOR_*)
 */
static void
window_draw_line(int x1, int y1, int x2, int y2, int color)
{
  vg4->image->draw_line(vg4data.backbuf.img, x1, y1, x2, y2, color);
} /* Ende window_draw_line */


/* window_draw_rect:
 * draw a rectangle onto window
 * @param rect   rectangle, or NULL = whole window
 * @param color  rectangle color (VG_COLOR_*)
 * @param fill   whether to fill rectangle
 */
static void
window_draw_rect(const struct VG_Rect *rect, int color, VG_BOOL fill)
{
  vg4->image->draw_rect(vg4data.backbuf.img, rect, color, fill);
} /* Ende window_draw_rect */


/* window_draw_circle:
 * draw a circle onto window
 * @param xm      x-coordinate of center
 * @param ym      y-coordinate of center
 * @param radius  radius
 * @param color   circle color (VG_COLOR_*)
 * @param fill    whether to fill circle
 */
static void
window_draw_circle(int xm, int ym, int radius, int color, VG_BOOL fill)
{
  vg4->image->draw_circle(vg4data.backbuf.img, xm, ym, radius, color, fill);
} /* Ende window_draw_circle */


/* window_flush:
 * flush backbuffer to window
 */
static void
window_flush(void)
{
  vg4data.iolib.f.window_flush();
} /* Ende window_flush */


/* window_clone:
 * clone window
 * @param rect   rectangle out of window, or NULL = whole window
 * @param iattr  image-copy attributes, or NULL
 * @return  image with window contents, or NULL = rectangle out of bounds
 */
static struct VG_Image *
window_clone(const struct VG_Rect *rect, const struct VG_ImagecopyAttr *iattr)
{
  return window_clone_doit(rect, iattr, VG_TRUE);
} /* Ende window_clone */


/* window_clone_doit: do action */
static struct VG_Image *
window_clone_doit(const struct VG_Rect *rect, const struct VG_ImagecopyAttr *iattr, VG_BOOL intolist)
{
  if (intolist) {
    return vg4->image->clone(vg4data.backbuf.img, rect, iattr);
  } else {
    return vg4_image_clone_nolist(vg4data.backbuf.img, rect, iattr);
  }
} /* Ende window_clone_doit */


/* window_screenshot:
 * save a screenshot of the actual window to file
 * @param filename  filename to save
 * @return  VG_TRUE = OK or VG_FALSE = error
 */
static VG_BOOL
window_screenshot(const char *filename)
{
  VG_BOOL bret;
  struct VG_Image *wclon;
  struct VG_ImagecopyAttr iattr;

  VG_IMAGECOPY_ATTR_DEFAULT(&iattr);
  iattr.pixel = vg4data.backbuf.iattr_pixel;

  wclon = vg4_window_clone_nolist(NULL, &iattr);
  bret = vg4->image->save(wclon, filename, VG_TRUE);
  vg4->image->destroy(wclon);

  return bret;
} /* Ende window_screenshot */
