/* 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 <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include "vgi.h"

#ifndef S_ISDIR
# define S_ISDIR(m)  (((m) & S_IFMT) == S_IFDIR)
#endif

#ifndef S_ISREG
# define S_ISREG(m)  (((m) & S_IFMT) == S_IFREG)
#endif

const char * vg4_get_prefix(int *);
void vg4_intern_skip_ws(const char **, const char *);
void vg4_intern_skip_sp(const char **, const char *);
struct VG_Hash * vg4_intern_parser_keyval(const char *, size_t, char **);
VG_BOOL vg4_intern_recurs_dir(const char *, VG_BOOL (*)(const char *, void *), void *);
struct VG_Image * vg4_intern_get_img_from_imgcnt(struct vgi_imgcnt *, struct VG_ImagecopyAttr *);
const char * vg4_intern_get_name_from_imgcnt(struct vgi_imgcnt *);
void vg4_intern_free_imgcnt(struct vgi_imgcnt *);
struct VG_Rect vg4_intern_read_rect(const char *);
void vg4_intern_str_with_var(struct SML3_gummi *, const char *, struct VG_Hash *, const char *);
void vg4_intern_fpath_with_var(char *, size_t, struct VG_Hash *);
struct VG_Image * vg4_intern_ok_cancel_img(struct VG_Position *, int);


/* vg4_get_prefix:
 * return prefix
 * - local copy beneath "vgag4"
 * - definition VGAG_PREFIX
 * @param islocal  whether is local (if not NULL)
 * @return  prefix
 */
const char *
vg4_get_prefix(int *islocal)
{
  static const char *localinstdir = "vgag4";
  const char *prefix;
  DIR *dirp;

  if ((dirp = opendir(localinstdir)) != NULL) {
    prefix = localinstdir;
    closedir(dirp);
    if (islocal != NULL) { *islocal = 1; }
  } else {
    prefix = VGAG_PREFIX;
    if (islocal != NULL) { *islocal = 0; }
  }

  return prefix;
} /* Ende vg4_get_prefix */


/* skip whitespaces (and comments), update ptpos */
void
vg4_intern_skip_ws(const char **ptpos, const char *ptend)
{
  if (ptpos == NULL || *ptpos == NULL) { return; }
  if (ptend == NULL) { *ptpos = NULL; return; }
  if (*ptpos >= ptend) { *ptpos = ptend; return; }

redo:
  while (*ptpos < ptend) {
    if ((*ptpos)[0] == '\0' || strchr(VGI_WSPACE, (int)(*ptpos)[0]) == NULL) { break; }
    (*ptpos)++;
  }

  if (*ptpos < ptend && (*ptpos)[0] == '#') {  /* comment */
    (*ptpos)++;
    while (*ptpos < ptend) {
      if ((*ptpos)[0] == '\n') { (*ptpos)++; break; }
      (*ptpos)++;
    }
    goto redo;
  }
} /* Ende vg4_intern_skip_ws */


/* skip spaces (not comments) up to after newline, update ptpos */
void
vg4_intern_skip_sp(const char **ptpos, const char *ptend)
{
  if (ptpos == NULL || *ptpos == NULL) { return; }
  if (ptend == NULL) { *ptpos = NULL; return; }
  if (*ptpos >= ptend) { *ptpos = ptend; return; }

  while (*ptpos < ptend) {
    if ((*ptpos)[0] == '\0' || strchr(VGI_SPACE, (int)(*ptpos)[0]) == NULL) {
      if ((*ptpos)[0] == '\n') { (*ptpos)++; }
      break;
    }
    (*ptpos)++;
  }
} /* Ende vg4_intern_skip_sp */


/* vg4_intern_parser_keyval:
 * parse for
 * "[" <key>=<value> ... "]"
 *   (<value> may be in " or ',
 *    "=" may be surrounded by whitespaces,
 *    newlines are possible,
 *    comments after "#"
 *   )
 * @param text   text
 * @param size   number of bytes in text, or 0 = until terminating 0-byte
 * @param ntext  for returning position in text after parsed content
 * @return  hash with <key>=<value>,
 *          or NULL = error: data to parse is incomplete
 */
struct VG_Hash *
vg4_intern_parser_keyval(const char *text, size_t size, char **ntext)
{
  struct VG_Hash *hsret;
  const char *ptbeg, *ptpos, *ptend;
  char key[128], val[256];
  char ctren;
  int isok;

  isok = 0;
  hsret = vg4->hash->create();

  if (text == NULL || *text == '\0') {
    if (ntext != NULL) { *ntext = (char *)text; }
    return hsret;
  }

  if (size == 0) { size = strlen(text); }
  ptpos = text;
  ptend = text + size;

  /* skip to "[" */
  vg4_intern_skip_ws(&ptpos, ptend);
  if (ptpos == ptend) { isok = 1; goto psat_end; }
  if (ptpos[0] != '[') { isok = 1; goto psat_end; }

  for (;;) {
    /* skip to key */
    ptpos++;
    if (ptpos == ptend) { goto psat_end; }
    vg4_intern_skip_ws(&ptpos, ptend);
    if (ptpos == ptend) { goto psat_end; }

    /* check for "]" */
    if (ptpos[0] == ']') { ptpos++; isok = 1; goto psat_end; }

    /* skip to end of key */
    ptbeg = ptpos;
    while (ptpos < ptend) {
      if (ptpos[0] == '\0' || strchr(VGI_WSPACE, (int)ptpos[0]) != NULL || ptpos[0] == '=' || ptpos[0] == ']') { break; }
      ptpos++;
    }
    if (ptpos == ptend) { goto psat_end; }
    if (ptpos[0] == ']') { ptpos++; isok = 1; goto psat_end; }
    if ((size_t)(ptpos - ptbeg) >= sizeof(key)) {
      outerr("key too long: %.*s", (int)(ptpos - ptbeg), ptbeg);
      goto psat_end;
    }
    if (ptpos == ptbeg) { outerr("empty key"); goto psat_end; }
    memcpy(key, ptbeg, (size_t)(ptpos - ptbeg));
    key[(size_t)(ptpos - ptbeg)] = '\0';

    /* skip to "=" */
    vg4_intern_skip_ws(&ptpos, ptend);
    if (ptpos == ptend) { goto psat_end; }
    if (ptpos[0] != '=') { goto psat_end; }

    /* skip to val */
    ptpos++;
    if (ptpos == ptend) { goto psat_end; }
    vg4_intern_skip_ws(&ptpos, ptend);
    if (ptpos == ptend) { goto psat_end; }

    /* check for "]" */
    if (ptpos[0] == ']') { ptpos++; isok = 1; goto psat_end; }

    /* delimiter? */
    ctren = 0;
    if (ptpos[0] == '"' || ptpos[0] == '\'') {
      ctren = ptpos[0];
      ptpos++;
      if (ptpos == ptend) { goto psat_end; }
    }

    /* skip to end of val */
    ptbeg = ptpos;
    while (ptpos < ptend) {
      if (ctren != 0) {
        if (ptpos[0] == ctren) { break; }
      } else {
        if (ptpos[0] == '\0' || strchr(VGI_WSPACE, (int)ptpos[0]) != NULL || ptpos[0] == ']') { break; }
      }
      ptpos++;
    }
    if (ptpos == ptend) { goto psat_end; }
    if ((size_t)(ptpos - ptbeg) >= sizeof(val)) {
      outerr("value too long: %.*s", (int)(ptpos - ptbeg), ptbeg);
      goto psat_end;
    }
    if (ptpos > ptbeg) {
      memcpy(val, ptbeg, (size_t)(ptpos - ptbeg));
      val[(size_t)(ptpos - ptbeg)] = '\0';
      vg4->hash->setstr(hsret, key, val);
    }
    if (ctren != 0) {
      ptpos++;
      if (ptpos == ptend) { goto psat_end; }
    }
    if (ptpos[0] == ']') { ptpos++; isok = 1; goto psat_end; }
  }

psat_end:
  if (isok) {
    vg4_intern_skip_ws(&ptpos, ptend);
    if (ntext != NULL) { *ntext = (char *)ptpos; }
  } else {
    if (ntext != NULL) { *ntext = (char *)text; }
    vg4->hash->destroy(hsret);
    hsret = NULL;
  }
  return hsret;
} /* Ende vg4_intern_parser_keyval */


/* vg4_intern_recurs_dir:
 * traverse recursive through a directory
 * @param pdir   directory
 * @param cbkf   callback function for files
 * @param vdata  arbitrary struct for cbkf
 * @return  VG_TRUE = ok or VG_FALSE = error
 */
VG_BOOL
vg4_intern_recurs_dir(const char *pdir, VG_BOOL (*cbkf)(const char *, void *), void *vdata)
{
  DIR *dirp;
  struct dirent *direntp;
  struct stat sbuf;
  char pfad[VGI_PATHSIZE];

  if (pdir == NULL || *pdir == '\0') { outerr("No directory passed"); return VG_FALSE; }

  if ((dirp = opendir(pdir)) == NULL) {
    outerr("Error opening \"%s\": %s", pdir, strerror(errno));
    return VG_FALSE;
  }

  while ((direntp = readdir(dirp)) != NULL) {
    if (*direntp->d_name == '.') { continue; }
    snprintf(pfad, sizeof(pfad), "%s/%s", pdir, direntp->d_name);
    if (stat(pfad, &sbuf) < 0) { continue; }
    if (S_ISDIR(sbuf.st_mode)) {
      if (!vg4_intern_recurs_dir(pfad, cbkf, vdata)) { return VG_FALSE; }
    } else if (S_ISREG(sbuf.st_mode)) {
      if (cbkf != NULL) {
        if (!cbkf(pfad, vdata)) { return VG_FALSE; }
      } else {
        printf("%s\n", pfad);
      }
    }
  }

  closedir(dirp);
  return VG_TRUE;
} /* Ende vg4_intern_recurs_dir */


/* get image from image-container */
struct VG_Image *
vg4_intern_get_img_from_imgcnt(struct vgi_imgcnt *imgcnt, struct VG_ImagecopyAttr *iattr)
{
  struct VG_Image *imgp;

  if (iattr != NULL) { VG_IMAGECOPY_ATTR_DEFAULT(iattr); }
  if (imgcnt == NULL) { return NULL; }

  imgp = NULL;
  if (imgcnt->type == VGI_IMGCNT_IMAGE) {
    imgp = imgcnt->u.img;
  } else if (imgcnt->type == VGI_IMGCNT_SPRITE) {
    vg4->sprite->next(imgcnt->u.sprt, &imgp, iattr);
  }

  return imgp;
} /* Ende vg4_intern_get_img_from_imgcnt */


/* get name from image-container */
const char *
vg4_intern_get_name_from_imgcnt(struct vgi_imgcnt *imgcnt)
{
  const char *name;

  if (imgcnt == NULL) { return ""; }

  name = "";
  if (imgcnt->type == VGI_IMGCNT_IMAGE) {
    name = vg4->image->getname(imgcnt->u.img);
  } else if (imgcnt->type == VGI_IMGCNT_SPRITE) {
    name = vg4->sprite->getname(imgcnt->u.sprt);
  }

  return name;
} /* Ende vg4_intern_get_name_from_imgcnt */


/* free an image-container */
void
vg4_intern_free_imgcnt(struct vgi_imgcnt *imgcnt)
{
  if (imgcnt == NULL) { return; }

  if (imgcnt->type == VGI_IMGCNT_IMAGE) {
    vg4->image->destroy(imgcnt->u.img);
  } else if (imgcnt->type == VGI_IMGCNT_SPRITE) {
    vg4->sprite->destroy(imgcnt->u.sprt);
  }
  imgcnt->type = VGI_IMGCNT_NONE;
} /* Ende vg4_intern_free_imgcnt */


/* read rectangle values from "<x>+<w>,<y>+<h>" */
struct VG_Rect
vg4_intern_read_rect(const char *bval)
{
  const char *pt1, *pt2;
  struct VG_Rect rect_dfl, rect, *prect;

  rect_dfl.x = rect_dfl.w = rect_dfl.y = rect_dfl.h = 0;
  rect = rect_dfl;
  prect = &rect_dfl;

  if (bval == NULL) { return *prect; }

  pt1 = bval;
  if ((pt2 = strchr(pt1, '+')) != NULL) {
    /* <x>+<w>,<y>+<h> */
    rect.x = atoi(pt1);
    pt1 = pt2 + 1;
    if ((pt2 = strchr(pt1, ',')) != NULL) {
      rect.w = atoi(pt1);
      pt1 = pt2 + 1;
      if ((pt2 = strchr(pt1, '+')) != NULL) {
        rect.y = atoi(pt1);
        pt1 = pt2 + 1;
        rect.h = atoi(pt1);
        prect = &rect;
      }
    }
  }

  return *prect;
} /* Ende vg4_intern_read_rect */


/* return in rgm contents of string with expanded variables "$(VARNAME)" */
void
vg4_intern_str_with_var(struct SML3_gummi *rgm, const char *string, struct VG_Hash *hvar, const char *nlang)
{
  size_t gmlen, slen;
  const char *pt1, *pt2;
  char bvar[256];

  if (rgm == NULL) { return; }
  gmlen = 0; SML3_gumcpy(rgm, gmlen, "");
  if (string == NULL) { return; }
  if (nlang != NULL && *nlang == '\0') { nlang = NULL; }

  for (pt1 = string, pt2 = strstr(pt1, "$(");; pt2 = strstr(pt1, "$(")) {
    if (pt2 == NULL) { gmlen += SML3_gumcpy(rgm, gmlen, pt1); break; }
    slen = (size_t)(pt2 - pt1);
    if (slen > 0) { SML3_gumncpy(rgm, gmlen, pt1, slen); gmlen += slen; }
    pt1 = pt2;
    pt2 = strchr(pt1 + 2, ')');
    if (pt2 == NULL) { gmlen += SML3_gumcpy(rgm, gmlen, pt1); break; }
    pt1 += 2;
    vg4->misc->strscpy(bvar, sizeof(bvar), pt1, (size_t)(pt2 - pt1));
    if (*bvar != '\0') {
      if (hvar != NULL && (pt1 = (const char *)vg4->hash->get(hvar, bvar, NULL)) != NULL) {
        gmlen += SML3_gumcpy(rgm, gmlen, pt1);
      } else if (strcmp(bvar, "LANG") == 0 && nlang != NULL) {
        gmlen += SML3_gumcpy(rgm, gmlen, nlang);
      }
    }
    pt1 = pt2 + 1;
  }
} /* Ende vg4_intern_str_with_var */


/* replace filepath (for reading) with correct filepath with expanded variables "$(VARNAME)" */
void
vg4_intern_fpath_with_var(char *filepath, size_t fsize, struct VG_Hash *hvar)
{
  struct SML3_gummi gm = SML3_GUM_INITIALIZER;
  const char *cloc;
  FILE *ffp = NULL;

  if (filepath == NULL || fsize == 0) { return; }

  cloc = vg4->mlang->getlocale(VG_FALSE);
  vg4_intern_str_with_var(&gm, filepath, hvar, cloc);

  if (*SML3_gumgetval(&gm) == '\0' || (ffp = fopen(SML3_gumgetval(&gm), "r")) == NULL) {
    cloc = vg4->mlang->getlocale(VG_TRUE);
    vg4_intern_str_with_var(&gm, filepath, hvar, cloc);
  } else if (ffp != NULL) {
    fclose(ffp);
  }

  if (*SML3_gumgetval(&gm) != '\0' && (ffp = fopen(SML3_gumgetval(&gm), "r")) != NULL) {
    fclose(ffp);
    vg4->misc->strcpy(filepath, fsize, SML3_gumgetval(&gm));
  }

  SML3_gumdest(&gm);
} /* Ende vg4_intern_fpath_with_var */


/* return image and position with cancel- or/and ok-text; type: 0=cancel, 1=ok, 2=both */
struct VG_Image *
vg4_intern_ok_cancel_img(struct VG_Position *posi, int type)
{
  const char *prefix = vg4_get_prefix(NULL);
  char loadfile[VGI_PATHSIZE];
  int wsize, winw, winh, imgw, imgh;
  struct VG_Image *imgp;

  if (type < 0 || type > 2) { return NULL; }

  if (posi != NULL) { memset(posi, 0, sizeof(*posi)); }

  vg4->window->getparameters(&wsize, NULL);
  vg4->window->getsize(&winw, &winh);

  if (type == 0) {
    if (wsize == VG_WINDOW_SIZE_LOW) {
      snprintf(loadfile, sizeof(loadfile), "%s/share/vgagames4/images/low/cancel-esc.bmp", prefix);
    } else {
      snprintf(loadfile, sizeof(loadfile), "%s/share/vgagames4/images/high/cancel-esc.bmp", prefix);
    }
    if ((imgp = vg4_image_load_nolist(loadfile)) != NULL) {
      vg4->image->getsize(imgp, NULL, NULL, &imgh);
      if (posi != NULL) {
        posi->pos = VG_POS_CENTERED;
        posi->x = winw / 2;
        posi->y = winh - imgh * 2 / 3;
      }
    }

  } else if (type == 1) {
    if (wsize == VG_WINDOW_SIZE_LOW) {
      snprintf(loadfile, sizeof(loadfile), "%s/share/vgagames4/images/low/ok-space.bmp", prefix);
    } else {
      snprintf(loadfile, sizeof(loadfile), "%s/share/vgagames4/images/high/ok-space.bmp", prefix);
    }
    if ((imgp = vg4_image_load_nolist(loadfile)) != NULL) {
      vg4->image->getsize(imgp, NULL, NULL, &imgh);
      if (posi != NULL) {
        posi->pos = VG_POS_CENTERED;
        posi->x = winw / 2;
        posi->y = winh - imgh * 2 / 3;
      }
    }

  } else {
    struct VG_Image *imgp1, *imgp2;
    int imgw1, imgh1, imgw2, imgh2;
    struct VG_Position posb;

    if (wsize == VG_WINDOW_SIZE_LOW) {
      snprintf(loadfile, sizeof(loadfile), "%s/share/vgagames4/images/low/ok-space.bmp", prefix);
      imgp1 = vg4_image_load_nolist(loadfile);
      snprintf(loadfile, sizeof(loadfile), "%s/share/vgagames4/images/low/cancel-esc.bmp", prefix);
      imgp2 = vg4_image_load_nolist(loadfile);
    } else {
      snprintf(loadfile, sizeof(loadfile), "%s/share/vgagames4/images/high/ok-space.bmp", prefix);
      imgp1 = vg4_image_load_nolist(loadfile);
      snprintf(loadfile, sizeof(loadfile), "%s/share/vgagames4/images/high/cancel-esc.bmp", prefix);
      imgp2 = vg4_image_load_nolist(loadfile);
    }
    if (imgp1 == NULL || imgp2 == NULL) { vg4->image->destroy(imgp1); vg4->image->destroy(imgp2); return NULL; }

    vg4->image->getsize(imgp1, NULL, &imgw1, &imgh1);
    vg4->image->getsize(imgp2, NULL, &imgw2, &imgh2);

    if (posi != NULL) {
      imgw = (imgw1 < imgw2 ? imgw1 : imgw2);
      imgh = (imgh1 > imgh2 ? imgh1 : imgh2);
      imgp = vg4_image_create_nolist(winw, imgh);

      posb.pos = VG_POS_UPPER_LEFT;
      posb.x = imgw / 2;
      posb.y = (imgh - imgh1) / 2;
      vg4->image->copy(imgp, imgp1, &posb, NULL);

      posb.pos = VG_POS_UPPER_RIGHT;
      posb.x = winw - 1 - imgw / 2;
      posb.y = (imgh - imgh2) / 2;
      vg4->image->copy(imgp, imgp2, &posb, NULL);

    } else {
      imgw = (imgw1 > imgw2 ? imgw1 : imgw2);
      imgh = imgh1 + imgh2 + (wsize == VG_WINDOW_SIZE_LOW ? 2 : 4);
      imgp = vg4_image_create_nolist(imgw, imgh);

      posb.pos = VG_POS_UPPER_LEFT;
      posb.x = (imgw - imgw1) / 2;
      posb.y = 0;
      vg4->image->copy(imgp, imgp1, &posb, NULL);

      posb.pos = VG_POS_LOWER_LEFT;
      posb.x = (imgw - imgw2) / 2;
      posb.y = imgh - 1;
      vg4->image->copy(imgp, imgp2, &posb, NULL);
    }

    vg4->image->destroy(imgp1);
    vg4->image->destroy(imgp2);

    if (posi != NULL) {
      posi->pos = VG_POS_CENTERED;
      posi->x = winw / 2;
      posi->y = winh - imgh * 2 / 3;
    }
  }

  return imgp;
} /* Ende vg4_intern_ok_cancel_img */
