// Copyright(c) 2015-2016 YamaArashi
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include "global.h"
#include "font.h"
#include "gfx.h"
#include "util.h"

unsigned char gFontPalette[][3] =
{
    {0xFF, 0xFF, 0xFF}, // bg (white)
    {0x38, 0x38, 0x38}, // fg (dark grey)
    {0xD8, 0xD8, 0xD8}, // shadow (light grey)
};

void ConvertFromTiles1Bpp(unsigned char *src, unsigned char *dest, int numGlyphs, int layout)
{
    for (int glyph = 0; glyph < numGlyphs; glyph++)
    {
        if (layout == 0)
        {
            for (int i = 0; i < 8; i++)
            {
                uint8_t srcRow = src[(glyph * 8) + i];

                for (int j = 0; j < 8; j++)
                {
                    int x = ((glyph % 16) * 8) + j;
                    int y = ((glyph / 16) * 8) + i;
                    dest[(y * 128) + x] = (srcRow >> (7 - j)) & 1;
                }
            }
        }
        else
        {
            // layout type 1

            int tile1Offset = glyph * 16;
            int tile2Offset = tile1Offset + 8;

            for (int i = 0; i < 8; i++)
            {
                uint8_t srcRow = src[tile1Offset + i];

                for (int j = 0; j < 8; j++)
                {
                    int x = ((glyph % 16) * 8) + j;
                    int y = ((glyph / 16) * 16) + i;
                    dest[(y * 128) + x] = (srcRow >> (7 - j)) & 1;
                }
            }

            for (int i = 0; i < 8; i++)
            {
                uint8_t srcRow = src[tile2Offset + i];

                for (int j = 0; j < 8; j++)
                {
                    int x = ((glyph % 16) * 8) + j;
                    int y = ((glyph / 16) * 16) + 8 + i;
                    dest[(y * 128) + x] = (srcRow >> (7 - j)) & 1;
                }
            }
        }
    }
}

void ConvertToTiles1Bpp(unsigned char *src, unsigned char *dest, int numGlyphs, int layout)
{
    for (int glyph = 0; glyph < numGlyphs; glyph++)
    {
        if (layout == 0)
        {
            for (int i = 0; i < 8; i++)
            {
                uint8_t destRow = 0;

                for (int j = 0; j < 8; j++)
                {
                    int x = ((glyph % 16) * 8) + j;
                    int y = ((glyph / 16) * 8) + i;
                    unsigned char color = src[(y * 128) + x];

                    if (color > 1)
                        FATAL_ERROR("More than 2 colors in 1 BPP font.\n");

                    destRow <<= 1;
                    destRow |= color;
                }

                dest[(glyph * 8) + i] = destRow;
            }
        }
        else
        {
            // layout type 1

            int tile1Offset = glyph * 16;
            int tile2Offset = tile1Offset + 8;

            for (int i = 0; i < 8; i++)
            {
                uint8_t destRow = 0;

                for (int j = 0; j < 8; j++)
                {
                    int x = ((glyph % 16) * 8) + j;
                    int y = ((glyph / 16) * 16) + i;
                    unsigned char color = src[(y * 128) + x];

                    if (color > 1)
                        FATAL_ERROR("More than 2 colors in 1 BPP font.\n");

                    destRow <<= 1;
                    destRow |= color;
                }

                dest[tile1Offset + i] = destRow;
            }

            for (int i = 0; i < 8; i++)
            {
                uint8_t destRow = 0;

                for (int j = 0; j < 8; j++)
                {
                    int x = ((glyph % 16) * 8) + j;
                    int y = ((glyph / 16) * 16) + 8 + i;
                    unsigned char color = src[(y * 128) + x];

                    if (color > 1)
                        FATAL_ERROR("More than 2 colors in 1 BPP font.\n");

                    destRow <<= 1;
                    destRow |= color;
                }

                dest[tile2Offset + i] = destRow;
            }
        }
    }
}

void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int numGlyphs, int layout)
{
    static unsigned char table[16] =
    {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1,
    };

    for (int glyph = 0; glyph < numGlyphs; glyph++)
    {
        if (layout == 0)
        {
            int offset = glyph * 32;

            for (int i = 0; i < 8; i++)
            {
                uint32_t srcRow = (src[offset + 3] << 24)
                    | (src[offset + 2] << 16)
                    | (src[offset + 1] << 8)
                    | src[offset];

                for (int j = 0; j < 8; j++)
                {
                    int x = ((glyph % 16) * 8) + j;
                    int y = ((glyph / 16) * 8) + i;
                    dest[(y * 128) + x] = table[srcRow & 0xF];
                    srcRow >>= 4;
                }

                offset += 4;
            }
        }
        else
        {
            int tile1Offset;
            int tile2Offset;

            if (layout == 1)
            {
                tile1Offset = glyph * 64;
                tile2Offset = tile1Offset + 32;
            }
            else
            {
                tile1Offset = ((glyph / 16) * 1024) + ((glyph % 16) * 32);
                tile2Offset = tile1Offset + 512;
            }

            for (int i = 0; i < 8; i++)
            {
                uint32_t srcRow = (src[tile1Offset + 3] << 24)
                    | (src[tile1Offset + 2] << 16)
                    | (src[tile1Offset + 1] << 8)
                    | src[tile1Offset];

                for (int j = 0; j < 8; j++)
                {
                    int x = ((glyph % 16) * 8) + j;
                    int y = ((glyph / 16) * 16) + i;
                    dest[(y * 128) + x] = table[srcRow & 0xF];
                    srcRow >>= 4;
                }

                tile1Offset += 4;
            }

            for (int i = 0; i < 8; i++)
            {
                uint32_t srcRow = (src[tile2Offset + 3] << 24)
                    | (src[tile2Offset + 2] << 16)
                    | (src[tile2Offset + 1] << 8)
                    | src[tile2Offset];

                for (int j = 0; j < 8; j++)
                {
                    int x = ((glyph % 16) * 8) + j;
                    int y = ((glyph / 16) * 16) + 8 + i;
                    dest[(y * 128) + x] = table[srcRow & 0xF];
                    srcRow >>= 4;
                }

                tile2Offset += 4;
            }
        }
    }
}

void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numGlyphs, int layout)
{
    static unsigned char table[3] =
    {
        0, 15, 14,
    };

    for (int glyph = 0; glyph < numGlyphs; glyph++)
    {
        if (layout == 0)
        {
            int offset = glyph * 32;

            for (int i = 0; i < 8; i++)
            {
                uint32_t destRow = 0;

                for (int j = 0; j < 8; j++)
                {
                    int x = ((glyph % 16) * 8) + j;
                    int y = ((glyph / 16) * 8) + i;
                    unsigned char color = src[(y * 128) + x];

                    if (color > 2)
                        FATAL_ERROR("More than 3 colors in 4 BPP font.\n");

                    destRow >>= 4;
                    destRow |= (table[color] << 28);
                }

                dest[offset] = destRow & 0xFF;
                dest[offset + 1] = (destRow >> 8) & 0xFF;
                dest[offset + 2] = (destRow >> 16) & 0xFF;
                dest[offset + 3] = (destRow >> 24) & 0xFF;

                offset += 4;
            }
        }
        else
        {
            int tile1Offset;
            int tile2Offset;

            if (layout == 1)
            {
                tile1Offset = glyph * 64;
                tile2Offset = tile1Offset + 32;
            }
            else
            {
                tile1Offset = ((glyph / 16) * 1024) + ((glyph % 16) * 32);
                tile2Offset = tile1Offset + 512;
            }

            for (int i = 0; i < 8; i++)
            {
                uint32_t destRow = 0;

                for (int j = 0; j < 8; j++)
                {
                    int x = ((glyph % 16) * 8) + j;
                    int y = ((glyph / 16) * 16) + i;
                    unsigned char color = src[(y * 128) + x];

                    if (color > 2)
                        FATAL_ERROR("More than 3 colors in 4 BPP font.\n");

                    destRow >>= 4;
                    destRow |= (table[color] << 28);
                }

                dest[tile1Offset] = destRow & 0xFF;
                dest[tile1Offset + 1] = (destRow >> 8) & 0xFF;
                dest[tile1Offset + 2] = (destRow >> 16) & 0xFF;
                dest[tile1Offset + 3] = (destRow >> 24) & 0xFF;

                tile1Offset += 4;
            }

            for (int i = 0; i < 8; i++)
            {
                uint32_t destRow = 0;

                for (int j = 0; j < 8; j++)
                {
                    int x = ((glyph % 16) * 8) + j;
                    int y = ((glyph / 16) * 16) + 8 + i;
                    unsigned char color = src[(y * 128) + x];

                    if (color > 2)
                        FATAL_ERROR("More than 3 colors in 4 BPP font.\n");

                    destRow >>= 4;
                    destRow |= (table[color] << 28);
                }

                dest[tile2Offset] = destRow & 0xFF;
                dest[tile2Offset + 1] = (destRow >> 8) & 0xFF;
                dest[tile2Offset + 2] = (destRow >> 16) & 0xFF;
                dest[tile2Offset + 3] = (destRow >> 24) & 0xFF;

                tile2Offset += 4;
            }
        }
    }
}

static void SetFontPalette(struct Image *image)
{
    image->hasPalette = true;

    image->palette.numColors = 3;

    for (int i = 0; i < image->palette.numColors; i++)
    {
        image->palette.colors[i].red = gFontPalette[i][0];
        image->palette.colors[i].green = gFontPalette[i][1];
        image->palette.colors[i].blue = gFontPalette[i][2];
    }

    image->hasTransparency = false;
}

int CalcFileSize(int numGlyphs, int bpp, int layout)
{
    if (layout == 2)
    {
        // assume 4 BPP
        int numFullRows = numGlyphs / 16;
        int remainder = numGlyphs % 16;
        int fullRowsSize = numFullRows * 1024;
        int remainderSize = 0;

        if (remainder != 0)
            remainderSize = 1024 - (16 - remainder) * 32;

        return fullRowsSize + remainderSize;
    }
    else
    {
        int tilesPerGlyph = layout > 0 ? 2 : 1;
        int bytesPerTile = 8 * bpp;
        return numGlyphs * tilesPerGlyph * bytesPerTile;
    }
}

void ReadFont(char *path, struct Image *image, int numGlyphs, int bpp, int layout)
{
    int fileSize;
    unsigned char *buffer = ReadWholeFile(path, &fileSize);

    int expectedFileSize = CalcFileSize(numGlyphs, bpp, layout);

    if (fileSize != expectedFileSize)
        FATAL_ERROR("The file size is %d but should be %d.\n", fileSize, expectedFileSize);

    int numRows = (numGlyphs + 15) / 16;
    int rowHeight = layout > 0 ? 16 : 8;

    image->width = 128;
    image->height = numRows * rowHeight;
    image->bitDepth = 8;
    image->pixels = calloc(image->width * image->height, 1);

    if (image->pixels == NULL)
        FATAL_ERROR("Failed to allocate memory for font.\n");

    if (bpp == 1)
        ConvertFromTiles1Bpp(buffer, image->pixels, numGlyphs, layout);
    else
        ConvertFromTiles4Bpp(buffer, image->pixels, numGlyphs, layout);

    free(buffer);

    SetFontPalette(image);
}

void WriteFont(char *path, struct Image *image, int numGlyphs, int bpp, int layout)
{
    if (image->width != 128)
        FATAL_ERROR("The width of the font image (%d) is not 128.\n", image->width);

    int numRows = (numGlyphs + 15) / 16;
    int rowHeight = layout > 0 ? 16 : 8;
    int expectedHeight = numRows * rowHeight;

    if (image->height < expectedHeight)
        FATAL_ERROR("The height of the font image (%d) is less than %d.\n", image->height, expectedHeight);

    int fileSize = CalcFileSize(numGlyphs, bpp, layout);

    unsigned char *buffer = calloc(fileSize, 1);

    if (buffer == NULL)
        FATAL_ERROR("Failed to allocate memory for font.\n");

    if (bpp == 1)
        ConvertToTiles1Bpp(image->pixels, buffer, numGlyphs, layout);
    else
        ConvertToTiles4Bpp(image->pixels, buffer, numGlyphs, layout);

    WriteWholeFile(path, buffer, fileSize);

    free(buffer);
}