/* NetHack 5.0    tileset.c    $NHDT-Date: 1596498341 2020/08/03 23:45:41 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.3 $ */
/* Copyright (c) Ray Chason, 2016. */
/* NetHack may be freely redistributed.  See license for details. */

#include "config.h"
#include "objclass.h"
#include "flag.h"
#include "tileset.h"

static void get_tile_map(const char *);
static unsigned gcd(unsigned, unsigned);
static void split_tiles(const struct TileSetImage *);
static void free_image(struct TileSetImage *);

static struct TileImage *tiles;
static unsigned num_tiles;
static struct TileImage blank_tile; /* for graceful undefined tile handling */

static boolean have_palette;
static struct Pixel palette[256];

boolean
read_tiles(const char *filename, boolean true_color)
{
    static const unsigned char png_sig[] = {
        0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
    };
    struct TileSetImage image;
    FILE *fp;
    char header[16];
    boolean ok;

    /* Fill the image structure with known values */
    image.width = 0;
    image.height = 0;
    image.pixels = NULL;        /* custodial */
    image.indexes = NULL;       /* custodial */
    image.image_desc = NULL;    /* custodial */
    image.tile_width = 0;
    image.tile_height = 0;

    /* Identify the image type */
    fp = fopen(filename, "rb");
    if (!fp)
        goto error;
    memset(header, 0, sizeof(header));
    fread(header, 1, sizeof(header), fp);
    fclose(fp), fp = (FILE *) 0;

    /* Call the loader appropriate for the image */
    if (memcmp(header, "BM", 2) == 0) {
        ok = read_bmp_tiles(filename, &image);
    } else if (memcmp(header, "GIF87a", 6) == 0
           ||  memcmp(header, "GIF89a", 6) == 0) {
        ok = read_gif_tiles(filename, &image);
    } else if (memcmp(header, png_sig, sizeof(png_sig)) == 0) {
        ok = read_png_tiles(filename, &image);
    } else {
        ok = FALSE;
    }
    if (!ok)
        goto error;

    /* Reject if the interface cannot handle direct color and the image does
       not use a palette */
    if (!true_color && image.indexes == NULL)
        goto error;

    /* Save the palette if present */
    have_palette = image.indexes != NULL;
    memcpy(palette, image.palette, sizeof(palette));

    /* Set defaults for tile metadata */
    if (image.tile_width == 0) {
        image.tile_width = image.width / 40;
    }
    if (image.tile_height == 0) {
        image.tile_height = image.tile_width;
    }
    /* Set the tile dimensions if the user has not done so */
    if (iflags.wc_tile_width == 0) {
        iflags.wc_tile_width = image.tile_width;
    }
    if (iflags.wc_tile_height == 0) {
        iflags.wc_tile_height = image.tile_height;
    }

    /* Parse the tile map */
    get_tile_map(image.image_desc);

    /* Split the image into tiles */
    split_tiles(&image);

    free_image(&image);
    return TRUE;

error:
    if (fp)
        fclose(fp);
    free_image(&image);
    return FALSE;
}

/* Free tile memory not required by the chosen display mode */
void
set_tile_type(boolean true_color)
{
    unsigned i;

    if (tiles) {
        if (true_color) {
            for (i = 0; i < num_tiles; ++i) {
                free((genericptr_t) tiles[i].indexes);
                tiles[i].indexes = NULL;
            }
            have_palette = FALSE;
        } else {
            for (i = 0; i < num_tiles; ++i) {
                free((genericptr_t) tiles[i].pixels);
                tiles[i].pixels = NULL;
            }
        }
    }
}

const struct Pixel *
get_palette(void)
{
    return have_palette ? palette : NULL;
}

/* TODO: derive tile_map from image_desc */
static void
get_tile_map(const char *image_desc UNUSED)
{
    return;
}

void
free_tiles(void)
{
    unsigned i;

    if (tiles) {
        for (i = 0; i < num_tiles; ++i) {
            free((genericptr_t) tiles[i].pixels);
            free((genericptr_t) tiles[i].indexes);
        }
        free((genericptr_t) tiles), tiles = NULL;
    }
    num_tiles = 0;

    if (blank_tile.pixels)
        free((genericptr_t) blank_tile.pixels), blank_tile.pixels = NULL;
    if (blank_tile.indexes)
        free((genericptr_t) blank_tile.indexes), blank_tile.indexes = NULL;
}

static void
free_image(struct TileSetImage *image)
{
    if (image->pixels)
        free((genericptr_t) image->pixels), image->pixels = NULL;
    if (image->indexes)
        free((genericptr_t) image->indexes), image->indexes = NULL;
    if (image->image_desc)
        free((genericptr_t) image->image_desc), image->image_desc = NULL;
}

const struct TileImage *
get_tile(unsigned tile_index)
{
    if (tile_index >= num_tiles)
        return &blank_tile;
    return &tiles[tile_index];
}

/* Note that any tile returned by this function must be freed */
struct TileImage *
stretch_tile(const struct TileImage *inp_tile,
             unsigned out_width, unsigned out_height)
{
    unsigned x_scale_inp, x_scale_out, y_scale_inp, y_scale_out;
    unsigned divisor;
    unsigned size;
    struct TileImage *out_tile;
    unsigned x_inp, y_inp, x2, y2, x_out, y_out;
    unsigned pos;

    /* Derive the scale factors */
    divisor = gcd(out_width, inp_tile->width);
    x_scale_inp = inp_tile->width / divisor;
    x_scale_out = out_width / divisor;
    divisor = gcd(out_height, inp_tile->height);
    y_scale_inp = inp_tile->height / divisor;
    y_scale_out = out_height / divisor;

    /* Derive the stretched tile */
    out_tile = (struct TileImage *) alloc(sizeof(struct TileImage));
    out_tile->width = out_width;
    out_tile->height = out_height;
    size = out_width * out_height;
    if (inp_tile->pixels != NULL) {
        out_tile->pixels = (struct Pixel *) alloc(size * sizeof(struct Pixel));
        divisor = x_scale_inp * y_scale_inp;
        for (y_out = 0; y_out < out_height; ++y_out) {
            for (x_out = 0; x_out < out_width; ++x_out) {
                unsigned r, g, b, a;

                /* Derive output pixels by blending input pixels */
                r = 0;
                g = 0;
                b = 0;
                a = 0;
                for (y2 = 0; y2 < y_scale_inp; ++y2) {
                    y_inp = (y_out * y_scale_inp + y2) / y_scale_out;
                    for (x2 = 0; x2 < x_scale_inp; ++x2) {
                        x_inp = (x_out * x_scale_inp + x2) / x_scale_out;
                        pos = y_inp * inp_tile->width + x_inp;
                        r += inp_tile->pixels[pos].r;
                        g += inp_tile->pixels[pos].g;
                        b += inp_tile->pixels[pos].b;
                        a += inp_tile->pixels[pos].a;
                    }
                }

                pos = y_out * out_width + x_out;
                out_tile->pixels[pos].r = r / divisor;
                out_tile->pixels[pos].g = g / divisor;
                out_tile->pixels[pos].b = b / divisor;
                out_tile->pixels[pos].a = a / divisor;
            }
        }
    } else {
        out_tile->pixels = NULL;
    }

    /* If the output device uses a palette, we can't blend; just pick
       a subset of the pixels */
    if (inp_tile->indexes != NULL) {
        out_tile->indexes = (unsigned char *) alloc(size);
        for (y_out = 0; y_out < out_height; ++y_out) {
            for (x_out = 0; x_out < out_width; ++x_out) {
                pos = y_out * out_width + x_out;
                x_inp = x_out * x_scale_inp / x_scale_out;
                y_inp = y_out * y_scale_inp / y_scale_out;
                out_tile->indexes[pos] =
                        inp_tile->indexes[y_inp * inp_tile->width + x_inp];
            }
        }
    } else {
        out_tile->indexes = NULL;
    }
    return out_tile;
}

/* Free a tile returned by stretch_tile */
/* Do NOT use this with tiles returned by get_tile */
void
free_tile(struct TileImage *tile)
{
    if (tile != NULL) {
        free(tile->indexes);
        free(tile->pixels);
        free(tile);
    }
}

/* Return the greatest common divisor */
static unsigned
gcd(unsigned a, unsigned b)
{
    while (TRUE) {
        if (b == 0) return a;
        a %= b;
        if (a == 0) return b;
        b %= a;
    }
}

static void
split_tiles(const struct TileSetImage *image)
{
    unsigned tile_rows, tile_cols;
    size_t tile_size, i, j;
    unsigned x1, y1, x2, y2;

    /* Get the number of tiles */
    tile_rows = image->height / iflags.wc_tile_height;
    tile_cols = image->width / iflags.wc_tile_width;
    num_tiles = tile_rows * tile_cols;
    tile_size = (size_t) iflags.wc_tile_height * (size_t) iflags.wc_tile_width;

    /* Allocate the tile array */
    tiles = (struct TileImage *) alloc(num_tiles * sizeof(tiles[0]));
    memset(tiles, 0, num_tiles * sizeof(tiles[0]));

    /* Copy the pixels into the tile structures */
    for (y1 = 0; y1 < tile_rows; ++y1) {
        for (x1 = 0; x1 < tile_cols; ++x1) {
            struct TileImage *tile = &tiles[y1 * tile_cols + x1];

            tile->width = iflags.wc_tile_width;
            tile->height = iflags.wc_tile_height;
            tile->pixels = (struct Pixel *)
                    alloc(tile_size * sizeof (struct Pixel));
            if (image->indexes != NULL) {
                tile->indexes = (unsigned char *) alloc(tile_size);
            }
            for (y2 = 0; y2 < (unsigned) iflags.wc_tile_height; ++y2) {
                for (x2 = 0; x2 < (unsigned) iflags.wc_tile_width; ++x2) {
                    unsigned x = x1 * iflags.wc_tile_width + x2;
                    unsigned y = y1 * iflags.wc_tile_height + y2;

                    i = y * image->width + x;
                    j = y2 * tile->width + x2;
                    tile->pixels[j] = image->pixels[i];
                    if (image->indexes != NULL) {
                        tile->indexes[j] = image->indexes[i];
                    }
                }
            }
        }
    }

    /* Create a blank tile for use when the tile index is invalid */
    blank_tile.width = iflags.wc_tile_width;
    blank_tile.height = iflags.wc_tile_height;
    blank_tile.pixels = (struct Pixel *)
            alloc(tile_size * sizeof (struct Pixel));
    for (i = 0; i < tile_size; ++i) {
        blank_tile.pixels[i].r = 0;
        blank_tile.pixels[i].g = 0;
        blank_tile.pixels[i].b = 0;
        blank_tile.pixels[i].a = 255;
    }
    if (image->indexes) {
        blank_tile.indexes = (unsigned char *) alloc(tile_size);
        memset(blank_tile.indexes, 0, tile_size);
    }
}

boolean
read_png_tiles(const char *filename UNUSED, struct TileSetImage *image UNUSED)
{
    /* stub */
    return FALSE;
}
