/* NetHack 5.0	tiletext.c	$NHDT-Date: 1596498342 2020/08/03 23:45:42 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.17 $ */
/*      Copyright (c) 2016 by Pasi Kallinen                       */
/* NetHack may be freely redistributed.  See license for details. */

#include "config.h"
#include "tile.h"

pixval ColorMap[3][MAXCOLORMAPSIZE];
int colorsinmap;
pixval MainColorMap[3][MAXCOLORMAPSIZE];
int colorsinmainmap;

static short color_index[MAXCOLORMAPSIZE];
static int num_colors;
static char charcolors[MAXCOLORMAPSIZE];

static int placeholder_init = 0;
static pixel placeholder[TILE_Y][TILE_X];
static FILE *tile_file;
static int tile_set, tile_set_indx;
static const char *const text_sets[] = {
#if (TILE_X == 8)
    "monthin.txt", "objthin.txt", "oththin.txt"
#else
    "monsters.txt", "objects.txt", "other.txt"
#endif
};

static char inbuf[BUFSZ];

extern const char *tilename(int, int, int);
extern boolean acceptable_tilename(int, int, const char *, const char *);
static int get_next_line(FILE *, boolean);
static void read_text_colormap(FILE *);
static boolean write_text_colormap(FILE *);
static boolean read_txttile(FILE *, pixel (*)[TILE_X]);
static void write_txttile(FILE *, pixel (*)[TILE_X]);

enum { MONSTER_SET, OBJECT_SET, OTHER_SET};

/* Ugh.  DICE doesn't like %[A-Z], so we have to spell it out... */
#define FORMAT_STRING \
    "%1[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.] = " \
    "(%d, %d, %d) "

/*
-----------------------------------------------------------------------------
    Tile palette Feb 9, 2022

    [0]  . = (71, 108, 108)
    [1]  A = (0, 0, 0)        grayshade   maps to [1] itself
    [2]  B = (0, 182, 255)                maps to [17] or Q for shade of gray
    [3]  C = (255, 108, 0)                maps to [18] or R for shade of gray
    [4]  D = (255, 0, 0)                  maps to [19] or S for shade of gray
    [5]  E = (0, 0, 255)                  maps to [20] or T for shade of gray
    [6]  F = (0, 145, 0)                  maps to [27] or 0 for shade of gray
    [7]  G = (108, 255, 0)                maps to [22] or V for shade of gray
    [8]  H = (255, 255, 0)                maps to [23] or W for shade of gray
    [9]  I = (255, 0, 255)                maps to [24] or X for shade of gray
    [10] J = (145, 71, 0)                 maps to [25] or Y for shade of gray
    [11] K = (204, 79, 0)                 maps to [26] or Z for shade of gray
    [12] L = (255, 182, 145)              maps to [21] or U for shade of gray
    [13] M = (237, 237, 237)  grayshade   maps to [15] or O for shade of gray
    [14] N = (255, 255, 255)  grayshade   maps to [13] or M for shade of gray
    [15] O = (215, 215, 215)  grayshade   maps to [14] or N for shade of gray
    [16] P = (108, 145, 182)              maps to [14] or N for shade of gray
    [17] Q = (18, 18, 18)     grayshade   maps to [1]  or A for shade of gray
    [18] R = (54, 54, 54)     grayshade   maps to [17] or Q for shade of gray
    [19] S = (73, 73, 73)     grayshade   maps to [18] or R for shade of gray
    [20] T = (82, 82, 82)     grayshade   maps to [19] or S for shade of gray
    [21] U = (205,205,205)    grayshade   maps to [20] or T for shade of gray
    [22] V = (104, 104, 104)  grayshade   maps to [27] or 0 for shade of gray
    [23] W = (131, 131, 131)  grayshade   maps to [22] or V for shade of gray
    [24] X = (140, 140, 140)  grayshade   maps to [23] or W for shade of gray
    [25] Y = (149, 149, 149)  grayshade   maps to [24] or X for shade of gray
    [26] Z = (195, 195, 195)  grayshade   maps to [25] or Y for shade of gray
    [27] 0 = (100, 100, 100)  grayshade   maps to [20] or T for shade of gray
    [28] 1 = (72, 108, 108)               not mapped in graymappings
-----------------------------------------------------------------------------
*/

static int grayscale = 0;
/* grayscale color mapping */
static const int graymappings[] = {
 /* .  A  B   C   D   E   F   G   H   I   J   K   L   M   N   O   P */
    0, 1, 17, 18, 19, 20, 27, 22, 23, 24, 25, 26, 21, 15, 13, 14, 14,
 /* Q  R   S   T   U   V   W   X   Y   Z   0 */
    1, 17, 18, 19, 20, 27, 22, 23, 24, 25, 20
};

void
set_grayscale(int gs)
{
    grayscale = gs;
}

/* read next line; repeat until it's a non-comment; populates 'inbuf[]';
   the buffer might already have data (first line after colormap)  */
static int
get_next_line(FILE *txtfile, boolean force)
{
    int ch;

    for (;;) {
        if (force || !inbuf[0]) {
            /* skip leading whitespace */
            do {
                ch = fgetc(txtfile);
            } while (ch == ' ');
            ungetc(ch, txtfile);
            /* get rest of line */
            if (!fgets(inbuf, BUFSZ, txtfile))
                break;
            force = TRUE;
            /* ignore blank lines;
               the old fscanf() processing did that, possibly by accident */
            if (!inbuf[0] || (inbuf[0] == '\n' && !inbuf[1]))
                continue;
        }
        if (inbuf[0] != '#' || !strncmp(inbuf, "# tile ", 7))
            return 1;
    }
    inbuf[0] = '\0';
    return 0;
}

static void
read_text_colormap(FILE *txtfile)
{
    int i, r, g, b;
    char c[2];

    for (i = 0; i < MAXCOLORMAPSIZE; i++)
        color_index[i] = -1;

    num_colors = 0;
    while (get_next_line(txtfile, TRUE)) {
        if (sscanf(inbuf, FORMAT_STRING, c, &r, &g, &b) != 4)
            break;
        color_index[(int) c[0]] = num_colors;
        ColorMap[CM_RED][num_colors] = r;
        ColorMap[CM_GREEN][num_colors] = g;
        ColorMap[CM_BLUE][num_colors] = b;
        num_colors++;
    }
    colorsinmap = num_colors;
}

#undef FORMAT_STRING

static boolean
write_text_colormap(FILE *txtfile)
{
    int i;
    char c;

    num_colors = colorsinmainmap;
    if (num_colors > 62) {
        Fprintf(stderr, "too many colors (%d)\n", num_colors);
        return FALSE;
    }
    for (i = 0; i < num_colors; i++) {
        if (i < 26)
            c = 'A' + i;
        else if (i < 52)
            c = 'a' + i - 26;
        else
            c = '0' + i - 52;

        charcolors[i] = c;
        Fprintf(
            txtfile, "%c = (%d, %d, %d)\n", c, (int) MainColorMap[CM_RED][i],
            (int) MainColorMap[CM_GREEN][i], (int) MainColorMap[CM_BLUE][i]);
    }
    return TRUE;
}

/* read one tile from win/share/{monsters,objects,other}.txt */
static boolean
read_txttile(FILE *txtfile, pixel (*pixels)[TILE_X])
{
    static int gidx = 0;
    int ph, i = 0, j, k, reslt;
    char buf[BUFSZ], ttype[BUFSZ], gend[BUFSZ];
    const char *p;
    char *q;
    boolean res = FALSE;

    gend[0] = '\0';
    reslt = 0;
    if (get_next_line(txtfile, FALSE)) {
        if (tile_set == MONSTER_SET)
            reslt = sscanf(inbuf, "# %255s %d (%255[^,],%255[^)])",
                           ttype, &i, buf, gend);
        else
            reslt = sscanf(inbuf, "# %255s %d (%255[^)])",
                           ttype, &i, buf);
    }
    if (reslt <= 0)
        goto done;

    ttype[sizeof ttype - 1] = '\0';
    buf[sizeof buf - 1] = '\0';

    if (tile_set == MONSTER_SET && gend[0] == 'f')
        gidx = 1;

    ph = strcmp(ttype, "placeholder") == 0;

    if (!ph && strcmp(ttype, "tile") != 0)
        Fprintf(stderr, "Keyword \"%s\" unexpected for entry %d\n", ttype, i);

    /* check tile name, but not relative number, which will
     * change when tiles are added
     */
    p = tilename(tile_set, tile_set_indx, gidx);
    if (p && (q = strstr(p, " {")) != 0) {
        *q = '\0';
    }
    if (p && strcmp(p, buf)) {
        boolean other_mismatch =
            (tile_set == OTHER_SET
             && !acceptable_tilename(tile_set, tile_set_indx, buf, p));

        if (tile_set != OTHER_SET || other_mismatch) {
            Fprintf(stderr, "warning: for tile %d (numbered %d) of %s,\n",
                    tile_set_indx, i, text_sets[tile_set]);
            Fprintf(stderr, "\tfound '%s' while expecting '%s'\n", buf, p);
        }
    }
    tile_set_indx++;

    /* look for non-whitespace at each stage */
    if (!get_next_line(txtfile, TRUE)) {
        Fprintf(stderr, "unexpected EOF\n");
        goto done;
    }
    if (inbuf[0] != '{') {
        Fprintf(stderr, "didn't find expected '{'\n");
        goto done;
    }

    for (j = 0; j < TILE_Y; j++) {
        /* read next line of color indices */
        if (!get_next_line(txtfile, TRUE)) {
            Fprintf(stderr, "unexpected EOF\n");
            goto done;
        }
        if ((q = strchr(inbuf, '\n')) == 0)
            q = &inbuf[strlen(inbuf)]; /* eos(inbuf) */
        while (q < &inbuf[TILE_X])
            *q++ = '.';
        *q = '\0';

        for (i = 0; i < TILE_X; i++) {
            k = color_index[(unsigned char) inbuf[i]];
            if (grayscale && k >= 0) {
                if (k > (SIZE(graymappings) - 1))
                    Fprintf(stderr, "Gray mapping issue %d > %d.\n", k,
                            SIZE(graymappings) - 1);
                else
                    k = graymappings[k];
            }
            if (k == -1) {
                Fprintf(stderr, "color %c not in colormap!\n", inbuf[i]);
            } else if (k >= 0 && k < MAXCOLORMAPSIZE) {
                pixels[j][i].r = ColorMap[CM_RED][k];
                pixels[j][i].g = ColorMap[CM_GREEN][k];
                pixels[j][i].b = ColorMap[CM_BLUE][k];
            } else {
                Fprintf(stderr,
                        "%d exceeds array boundary for ColorMap[][%d]!\n",
                        k, MAXCOLORMAPSIZE);
            }
        }
    }
    if (ph) {
        /* remember it for later */
        memcpy(placeholder, pixels, sizeof placeholder);
    }
    if (!get_next_line(txtfile, TRUE)) {
        Fprintf(stderr, "unexpected EOF\n");
        goto done;
    }
    if (inbuf[0] != '}') {
        Fprintf(stderr, "didn't find expected '}'\n");
        goto done;
    }
    res = TRUE;
 done:
    inbuf[0] = '\0';
    return res;
}

static void
write_txttile(FILE *txtfile, pixel (*pixels)[TILE_X])
{
    const char *p;
    const char *type;
    int i = 0, j, k;

    if (memcmp(placeholder, pixels, sizeof(placeholder)) == 0)
        type = "placeholder";
    else
        type = "tile";

    if (tile_set == MONSTER_SET) {
        for (i = 0; i < 2; ++i) {
            Fprintf(txtfile, "# %s %d (unknown,%s)\n", type, tile_set_indx,
                    i ? "female" : "male");
            if (i == 0)
                tile_set_indx++;
        }
    } else {
            p = tilename(tile_set, tile_set_indx, i);
            if (p)
                Fprintf(txtfile, "# %s %d (%s)\n", type, tile_set_indx, p);
            else
                Fprintf(txtfile, "# %s %d (null)\n", type, tile_set_indx);
    }
    tile_set_indx++;

    Fprintf(txtfile, "{\n");
    for (j = 0; j < TILE_Y; j++) {
        Fprintf(txtfile, "  ");
        for (i = 0; i < TILE_X; i++) {
            for (k = 0; k < num_colors; k++) {
                if (ColorMap[CM_RED][k] == pixels[j][i].r
                    && ColorMap[CM_GREEN][k] == pixels[j][i].g
                    && ColorMap[CM_BLUE][k] == pixels[j][i].b)
                    break;
            }
            if (k >= num_colors)
                Fprintf(stderr, "color not in colormap!\n");
            (void) fputc(charcolors[k], txtfile);
        }
        Fprintf(txtfile, "\n");
    }
    Fprintf(txtfile, "}\n");
}

/* initialize main colormap from globally accessed ColorMap */
void
init_colormap(void)
{
    int i;

    colorsinmainmap = colorsinmap;
    for (i = 0; i < colorsinmap; i++) {
        MainColorMap[CM_RED][i] = ColorMap[CM_RED][i];
        MainColorMap[CM_GREEN][i] = ColorMap[CM_GREEN][i];
        MainColorMap[CM_BLUE][i] = ColorMap[CM_BLUE][i];
    }
}

/* merge new colors from ColorMap into MainColorMap */
void
merge_colormap(void)
{
    int i, j;

    for (i = 0; i < colorsinmap; i++) {
        for (j = 0; j < colorsinmainmap; j++) {
            if (MainColorMap[CM_RED][j] == ColorMap[CM_RED][i]
                && MainColorMap[CM_GREEN][j] == ColorMap[CM_GREEN][i]
                && MainColorMap[CM_BLUE][j] == ColorMap[CM_BLUE][i])
                break;
        }
        if (j >= colorsinmainmap) { /* new color */
            if (colorsinmainmap >= MAXCOLORMAPSIZE) {
                Fprintf(stderr,
                        "Too many colors to merge -- excess ignored.\n");
            }
            j = colorsinmainmap;
            MainColorMap[CM_RED][j] = ColorMap[CM_RED][i];
            MainColorMap[CM_GREEN][j] = ColorMap[CM_GREEN][i];
            MainColorMap[CM_BLUE][j] = ColorMap[CM_BLUE][i];
            colorsinmainmap++;
        }
    }
}

boolean
fopen_text_file(const char *filename, const char *type)
{
    const char *p;
    int i;

    if (tile_file != (FILE *) 0) {
        Fprintf(stderr, "can only open one text file at a time\n");
        return FALSE;
    }

    tile_file = fopen(filename, type);
    if (tile_file == (FILE *) 0) {
        Fprintf(stderr, "cannot open text file %s\n", filename);
        return FALSE;
    }

    p = strrchr(filename, '/');
    if (p)
        p++;
    else
        p = filename;

    tile_set = 0;
    for (i = 0; i < SIZE(text_sets); i++) {
        if (!strcmp(p, text_sets[i]))
            tile_set = i;
    }
    tile_set_indx = 0;

    if (!strcmp(type, RDTMODE)) {
        /* Fill placeholder with noise */
        if (!placeholder_init) {
            placeholder_init++;
            for (i = 0; i < (int) sizeof placeholder; i++)
                ((char *) placeholder)[i] = i % 256;
        }

        read_text_colormap(tile_file);
        if (!colorsinmainmap)
            init_colormap();
        else
            merge_colormap();
        return TRUE;
    } else if (!strcmp(type, WRTMODE)) {
        if (!colorsinmainmap) {
            Fprintf(stderr, "no colormap set yet\n");
            return FALSE;
        }
        return (write_text_colormap(tile_file));
    } else {
        Fprintf(stderr, "bad mode (%s) for fopen_text_file\n", type);
        return FALSE;
    }
}

boolean
read_text_tile(pixel (*pixels)[TILE_X])
{
    return read_txttile(tile_file, pixels);
}

boolean
write_text_tile(pixel (*pixels)[TILE_X])
{
    write_txttile(tile_file, pixels);
    return TRUE;
}

int
fclose_text_file(void)
{
    int ret;

    ret = fclose(tile_file);
    tile_file = (FILE *) 0;
    return ret;
}
