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

/* PBM-Font
 *   Kommentar ab '#' bis Zeilenende
 *   - raw (kann mehrere in einer Datei haben)
 *     P4
 *     <whitespaces>
 *     <Breite in dez.ASCII>
 *     <whitespaces>
 *     <Hoehe in dez.ASCII>
 *     <EIN newline oder whitespace>
 *     <Pixel: Breite mal Hoehe: je ein Bit (1=schwarz und 0=weiss) von MSB-zu-LSB,
 *             je Zeile: wenn Breite erledigt und letztes Byte noch Bits uebrig hat, wird mit 0 (LSB) aufgefuellt>
 *
 *   - plain
 *     P1
 *     <whitespaces>
 *     <Breite in dez.ASCII>
 *     <whitespaces>
 *     <Hoehe in dez.ASCII>
 *     <EIN newline oder whitespace>
 *     <Pixel: Breite mal Hoehe: je eine ASCII-Zahl (1=schwarz und 0=weiss),
 *             keine <whitespaces> benoetigt, max Zeilenlaenge solltesein 70 Bytes>
 */

struct vgi_font * vg4_font_loadfile(const char *);
VG_BOOL vg4_font_savefile(const struct vgi_font *, const char *);

static int read_nr(char **, const char *, const char *);
static void readpbm(int, char **, const char *, int, int, const char *, struct vgi_font *);


/* read number as ASCII-string */
static int
read_nr(char **ptpos, const char *ptend, const char *filename)
{
  char *pstart;
  int nr;

  if (ptpos == NULL || *ptpos == NULL) { return 0; }
  if (ptend == NULL) { *ptpos = NULL; return 0; }
  if (filename == NULL || *filename == '\0') { filename = "[no-name]"; }
  if (*ptpos >= ptend) {
    outerr("reading \"%s\": file too short", filename);
    *ptpos = NULL;
    return 0;
  }

  nr = 0;
  pstart = *ptpos;
  while (*ptpos < ptend) {
    if ((*ptpos)[0] < '0' || (*ptpos)[0] > '9') { break; }
    (*ptpos)++;
  }
  if (pstart < *ptpos) {
    nr = (int)SML3_antoi(pstart, (size_t)(*ptpos - pstart));
  } else {
    outerr("reading \"%s\": invalid data character", filename);
    *ptpos = NULL;
    return 0;
  }
  return nr;
} /* Ende read_nr */


/* read pbm data */
static void
readpbm(int pbm_nr, char **ptpos, const char *ptend, int width, int height, const char *filename, struct vgi_font *vgft)
{
  int anzdata, momdata;

  anzdata = width * height;
  momdata = 0;

  if (pbm_nr == 1) {  /* P1 */
    VG_BOOL isset;
    struct VG_PixelColor pxcol0, pxcol1;
    GET_RGBA(VG_COLOR_WHITE, &pxcol0.r, &pxcol0.g, &pxcol0.b, &pxcol0.a);
    GET_RGBA(VG_COLOR_BLACK, &pxcol1.r, &pxcol1.g, &pxcol1.b, &pxcol1.a);
    while (momdata < anzdata) {
      vg4_intern_skip_ws((const char **)ptpos, ptend);
      if (*ptpos == ptend) { return; }
      if ((*ptpos)[0] == '0') {
        isset = VG_FALSE;
      } else if ((*ptpos)[0] == '1') {
        isset = VG_TRUE;
      } else {
        outerr("reading \"%s\": invalid data character", filename);
        *ptpos = NULL;
        return;
      }
      vgft->data[momdata] = (isset ? 1 : 0);
      (*ptpos)++;
      momdata++;
    }
    if (*ptpos < ptend) { vg4_intern_skip_ws((const char **)ptpos, ptend); }

  } else if (pbm_nr == 4) {  /* P4 */
    VG_BOOL isset;
    struct VG_PixelColor pxcol0, pxcol1;
    int w1, bitpos, ival;
    GET_RGBA(VG_COLOR_WHITE, &pxcol0.r, &pxcol0.g, &pxcol0.b, &pxcol0.a);
    GET_RGBA(VG_COLOR_BLACK, &pxcol1.r, &pxcol1.g, &pxcol1.b, &pxcol1.a);
    w1 = 0;
    bitpos = 7;
    while (momdata < anzdata) {
      if (*ptpos == ptend) {
        outerr("reading \"%s\": file too short", filename);
        *ptpos = NULL;
        return;
      }
      ival = ((*ptpos)[0] & (1 << bitpos));
      if (ival) { isset = VG_TRUE; } else { isset = VG_FALSE; }
      vgft->data[momdata] = (isset ? 1 : 0);
      if (++w1 == width || --bitpos < 0) {
        (*ptpos)++;
        bitpos = 7;
        if (w1 == width) { w1 = 0; }
      }
      momdata++;
    }
    if (*ptpos < ptend) { vg4_intern_skip_ws((const char **)ptpos, ptend); }
  }

  if (momdata < anzdata) {
    outerr("reading \"%s\": invalid format", filename);
    *ptpos = NULL;
  }
} /* Ende readpbm */


/* vg4_font_loadfile:
 * load font-character from file, checking format before
 * @param filename  filename to load
 * @return  font-character or NULL = error
 */
struct vgi_font *
vg4_font_loadfile(const char *filename)
{
  FILE *ffp;
  char bhead[8];
  struct vgi_font *vgft = NULL;

  if (filename == NULL || *filename == '\0') {
    outerr("loading file: no filename");
    return NULL;
  }

  ffp = fopen(filename, "r");
  if (ffp == NULL) { outerr("loading \"%s\": %s", filename, strerror(errno)); return NULL; }
  if (fread(bhead, 1, 3, ffp) != 3) {
    outerr("loading \"%s\": file too short", filename);
    fclose(ffp);
    return NULL;
  }

  if ((strncmp(bhead, "P1", 2) == 0 || strncmp(bhead, "P4", 2) == 0)
      && bhead[2] != '\0' && strchr(VGI_WSPACE, (int)bhead[2]) != NULL) {
    int pbm_nr, width, height;
    struct stat sbuf;
    char *fbuf, *ptpos, *ptend;
    size_t fsize;

    pbm_nr = bhead[1] - '0';

    if (fstat(fileno(ffp), &sbuf) < 0) {
      outerr("loading \"%s\": fstat: %s", filename, strerror(errno));
      fclose(ffp);
      return NULL;
    }
    fsize = (size_t)sbuf.st_size;
    if (fsize < 4) {
      outerr("loading \"%s\": file too short", filename);
      fclose(ffp);
      return NULL;
    }
    fsize -= 3;
    fbuf = SML3_malloc(fsize);
    if (fread(fbuf, 1, fsize, ffp) != fsize) {
      outerr("loading \"%s\": file too short", filename);
      fclose(ffp);
      return NULL;
    }
    fclose(ffp);
    ptpos = fbuf;
    ptend = fbuf + fsize;

    /* read width */
    vg4_intern_skip_ws((const char **)&ptpos, ptend);
    if (ptpos == ptend) { goto lf_end; }
    width = read_nr(&ptpos, ptend, filename);
    if (ptpos == NULL) { goto lf_end; }
    if (width <= 0) {
      outerr("loading \"%s\": width invalid: %d", filename, width);
      goto lf_end;
    }

    /* read height */
    vg4_intern_skip_ws((const char **)&ptpos, ptend);
    if (ptpos == ptend) { goto lf_end; }
    height = read_nr(&ptpos, ptend, filename);
    if (ptpos == NULL) { goto lf_end; }
    if (height <= 0) {
      outerr("loading \"%s\": height invalid: %d", filename, height);
      goto lf_end;
    }

    /* read one whitespace */
    if (ptpos == ptend || ptpos[0] == '\0' || strchr(VGI_WSPACE, (int)ptpos[0]) == NULL) {
      outerr("reading \"%s\": no whitespace at byte %lu", filename, (long)(size_t)(ptpos - fbuf));
      goto lf_end;
    }
    if (++ptpos == ptend) {
      outerr("loading \"%s\": file too short", filename);
      goto lf_end;
    }

    /* read data */
    vgft = SML3_calloc(1, sizeof(*vgft));
    vgft->w = width;
    vgft->h = height;
    vgft->data = SML3_calloc(vgft->w * vgft->h, sizeof(*vgft->data));
    readpbm(pbm_nr, &ptpos, ptend, vgft->w, vgft->h, filename, vgft);
    if (ptpos == NULL) { free(vgft->data); free(vgft); vgft = NULL; goto lf_end; }

lf_end:
    free(fbuf);

  } else {
    fclose(ffp);
    outerr("loading \"%s\": unknown file format", filename);
  }

  return vgft;
} /* Ende vg4_font_loadfile */


/* vg4_font_savefile:
 * save font to file according to format
 * @param vgft  font-character
 * @param filename  filename to save
 * @return  VG_TRUE = OK or VG_FALSE = error
 */
VG_BOOL
vg4_font_savefile(const struct vgi_font *vgft, const char *filename)
{
  const char *extptr, *fnptr;
  FILE *ffp;

  if (vgft == NULL) { outerr("saving font-character: no font-character to save"); return VG_FALSE; }
  if (filename == NULL || *filename == '\0') { outerr("saving font-character: no filename"); return VG_FALSE; }

  if ((fnptr = strrchr(filename, '/')) == NULL) { fnptr = filename; } else { fnptr++; }
  if ((extptr = strrchr(fnptr, '.')) == NULL
      || strcmp(extptr, ".pbm") != 0
      || (strspn(fnptr, "0123456789") != (size_t)(extptr - fnptr)
          && strncmp(fnptr, "unknown", (size_t)(extptr - fnptr)) != 0)
     ) {
    outerr("saving \"%s\": invalid file name", fnptr);
    return VG_FALSE;
  }

  if ((ffp = fopen(filename, "w")) == NULL) {
    outerr("saving font-character \"%s\": %s", filename, strerror(errno));
    return VG_FALSE;
  }

  { unsigned char bitval;
    int bitpos, w, h;
    unsigned char *dptr;

    fprintf(ffp, "P4\n%d\n%d\n", vgft->w, vgft->h);
    for (h = 0; h < vgft->h; h++) {
      bitpos = 7;
      bitval = 0;
      for (w = 0; w < vgft->w; w++) {
        dptr = &vgft->data[h * vgft->w + w];
        if (*dptr) { bitval |= (1 << bitpos); }
        if (--bitpos < 0) {
          if (fwrite(&bitval, 1, 1, ffp) != 1) {
            outerr("saving font-character \"%s\": short write", filename);
            fclose(ffp);
            return VG_FALSE;
          }
          bitpos = 7;
          bitval = 0;
        }
      }
      if (bitpos < 7) {
        if (fwrite(&bitval, 1, 1, ffp) != 1) {
          outerr("saving font-character \"%s\": short write", filename);
          fclose(ffp);
          return VG_FALSE;
        }
      }
    }
  }

  fclose(ffp);

  return VG_TRUE;
} /* Ende vg4_font_savefile */
