/*
 *  File:       tilefont.cc
 *  Created by: ennewalker on Sat Apr 26 01:33:53 2008 UTC
 *
 *  Modified for Crawl Reference by $Author$ on $Date$
 */

#include "AppHdr.h"
REVISION("$Rev$");

#include "tilebuf.h"
#include "tilefont.h"
#include "defines.h"
#include "files.h"

#include <SDL.h>
#include <SDL_opengl.h>
#include <ft2build.h>
#include FT_FREETYPE_H

const VColour term_colours[MAX_TERM_COLOUR] =
{
    VColour(  0,   0,   0), // BLACK
    VColour(  0,  82, 255), // BLUE
    VColour(100, 185,  70), // GREEN
    VColour(  0, 180, 180), // CYAN
    VColour(255,  48,   0), // RED
    VColour(238,  92, 238), // MAGENTA
    VColour(165,  91,   0), // BROWN
    VColour(162, 162, 162), // LIGHTGREY
    VColour( 82,  82,  82), // DARKGREY
    VColour( 82, 102, 255), // LIGHTBLUE
    VColour( 82, 255,  82), // LIGHTGREEN
    VColour( 82, 255, 255), // LIGHTCYAN
    VColour(255,  82,  82), // LIGHTRED
    VColour(255,  82, 255), // LIGHTMAGENTA
    VColour(255, 255,  82), // YELLOW
    VColour(255, 255, 255)  // WHITE
};

FTFont::FTFont() :
    m_glyphs(NULL),
    m_max_advance(0, 0),
    m_min_offset(0)
{
}

FTFont::~FTFont()
{
    delete[] m_glyphs;
}

bool FTFont::load_font(const char *font_name, unsigned int font_size, bool outl)
{
    FT_Library library;
    FT_Face face;
    FT_Error error;

    error = FT_Init_FreeType(&library);
    if (error)
    {
        fprintf(stderr, "Failed to initialize freetype library.\n");
        return false;
    }

    // TODO enne - need to find a cross-platform way to also
    // attempt to locate system fonts by name...
    std::string font_path = datafile_path(font_name, false, true);
    if (font_path.c_str()[0] == 0)
    {
        fprintf(stderr, "Could not find font '%s'\n", font_name);
        return false;
    }

    error = FT_New_Face(library, font_path.c_str(), 0, &face);
    if (error == FT_Err_Unknown_File_Format)
    {
        fprintf(stderr, "Unknown font format for file '%s'\n",
                         font_path.c_str());
        return false;
    }
    else if (error)
    {
        fprintf(stderr, "Invalid font from file '%s'\n", font_path.c_str());
    }

    error = FT_Set_Pixel_Sizes(face, font_size, font_size);
    ASSERT(!error);

    // Get maximum advance
    m_max_advance = coord_def(0,0);
    int ascender  = face->ascender >> 6;
    int min_y     = 100000;
    int max_y     = 0;
    int max_width = 0;
    m_min_offset  = 0;
    m_glyphs      = new GlyphInfo[256];
    for (unsigned int c = 0; c < 256; c++)
    {
        m_glyphs[c].offset  = 0;
        m_glyphs[c].advance = 0;
        m_glyphs[c].renderable = false;

        FT_Int glyph_index = FT_Get_Char_Index(face, c);
        if (!glyph_index)
            continue;

        error = FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER);
        ASSERT(!error);

        FT_Bitmap *bmp = &face->glyph->bitmap;

        int advance = face->glyph->advance.x >> 6;

        m_max_advance.x = std::max(m_max_advance.x, advance);

        int bmp_width  = bmp->width;
        int bmp_top    = ascender - face->glyph->bitmap_top;
        int bmp_bottom = ascender + bmp->rows - face->glyph->bitmap_top;
        if (outl)
        {
            bmp_width  += 2;
            bmp_top    -= 1;
            bmp_bottom += 1;
        }

        max_width = std::max(max_width, bmp_width);
        min_y = std::min(min_y, bmp_top);
        max_y = std::max(max_y, bmp_bottom);

        m_glyphs[c].offset  = face->glyph->bitmap_left;
        m_glyphs[c].advance = advance;
        m_glyphs[c].width   = bmp_width;

        m_min_offset = std::min((char)m_min_offset, m_glyphs[c].offset);
    }

    // The ascender and text height given by FreeType2 is ridiculously large
    // (e.g. 37 pixels high for 14 pixel font).  Use min and max bounding
    // heights on the characters we care about to get better values for the
    // text height and the ascender.
    m_max_advance.y = max_y - min_y;
    ascender       -= min_y;

    int max_height  = m_max_advance.y;

    // Grow character size to power of 2
    coord_def charsz(1,1);
    while (charsz.x < max_width)
        charsz.x *= 2;
    while (charsz.y < max_height)
        charsz.y *= 2;

    // Fill out texture to be (16*charsz.x) X (16*charsz.y) X (32-bit)
    // Having to blow out 8-bit alpha values into full 32-bit textures is
    // kind of frustrating, but not all OpenGL implementations support the
    // "esoteric" ALPHA8 format and it's not like this texture is very large.
    unsigned int   width  = 16 * charsz.x;
    unsigned int   height = 16 * charsz.y;
    unsigned char *pixels = new unsigned char[4 * width * height];
    memset(pixels, 0, sizeof(unsigned char) * 4 * width * height);

    // Special case c = 0 for full block.
    {
        m_glyphs[0].renderable = false;
        for (int x = 0; x < max_width; x++)
            for (int y = 0; y < max_height; y++)
            {
                unsigned int idx = x + y * width;
                idx *= 4;
                pixels[idx] = 255;
                pixels[idx + 1] = 255;
                pixels[idx + 2] = 255;
                pixels[idx + 3] = 255;
            }
    }

    for (unsigned int c = 1; c < 256; c++)
    {
        FT_Int glyph_index = FT_Get_Char_Index(face, c);
        if (!glyph_index)
        {
            // If no mapping for this character, leave blank.
            continue;
        }

        error = FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER);
        ASSERT(!error);

        FT_Bitmap *bmp = &face->glyph->bitmap;
        ASSERT(bmp);
        // Some glyphs (e.g. ' ') don't get a buffer.
        if (!bmp->buffer)
            continue;

        m_glyphs[c].renderable = true;

        int vert_offset = ascender - face->glyph->bitmap_top;

        ASSERT(bmp->pixel_mode == FT_PIXEL_MODE_GRAY);
        ASSERT(bmp->num_grays == 256);

        // Horizontal offset stored in m_glyphs and handled when drawing
        unsigned int offset_x = (c % 16) * charsz.x;
        unsigned int offset_y = (c / 16) * charsz.y + vert_offset;

        if (outl)
        {
            const int charw = bmp->width;
            for (int x = -1; x <= bmp->width; x++)
                for (int y = -1; y <= bmp->rows; y++)
                {
                    bool x_valid = x >= 0 && x < bmp->width;
                    bool y_valid = y >= 0 && y < bmp->rows;
                    bool valid   = x_valid && y_valid;
                    unsigned char orig = valid ? bmp->buffer[x + charw * y] : 0;

                    unsigned char edge = 0;
                    if (y_valid && x > 0)
                        edge = std::max(bmp->buffer[(x-1) + charw * y], edge);
                    if (x_valid && y > 0)
                        edge = std::max(bmp->buffer[x + charw * (y-1)], edge);
                    if (y_valid && x < bmp->width - 1)
                        edge = std::max(bmp->buffer[(x+1) + charw * y], edge);
                    if (x_valid && y < bmp->rows - 1)
                        edge = std::max(bmp->buffer[x + charw * (y+1)], edge);

                    unsigned int idx = offset_x+x+1 + (offset_y+y+1) * width;
                    idx *= 4;

                    pixels[idx] = orig;
                    pixels[idx + 1] = orig;
                    pixels[idx + 2] = orig;
                    pixels[idx + 3] = std::min(orig + edge, 255);
                }
        }
        else
        {
            for (int x = 0; x < bmp->width; x++)
                for (int y = 0; y < bmp->rows; y++)
                {
                    unsigned int idx = offset_x + x + (offset_y + y) * width;
                    idx *= 4;
                    unsigned char alpha = bmp->buffer[x + bmp->width * y];
                    pixels[idx] = 255;
                    pixels[idx + 1] = 255;
                    pixels[idx + 2] = 255;
                    pixels[idx + 3] = alpha;
                }
        }
    }

    bool success = m_tex.load_texture(pixels, width, height,
                                      GenericTexture::MIPMAP_NONE);

    delete[] pixels;

    return success;
}

struct FontVertLayout
{
    float pos_x;
    float pos_y;
    float tex_x;
    float tex_y;
    unsigned char r;
    unsigned char g;
    unsigned char b;
    unsigned char a;
};

void FTFont::render_textblock(unsigned int x_pos, unsigned int y_pos,
                              unsigned char *chars, unsigned char *colours,
                              unsigned int width, unsigned int height,
                              bool drop_shadow)
{
    if (!chars || !colours || !width || !height || !m_glyphs)
        return;

    coord_def adv(std::max(-m_min_offset, 0), 0);
    unsigned int i = 0;

    std::vector<FontVertLayout> verts;
    // TODO enne - make this better
    // This is bad for the CRT.  Maybe we should just reserve some fixed limit?
    // Maybe we should just cache this in FTFont?
    verts.reserve(4 * width * height);

    float texcoord_dy = (float)m_max_advance.y / (float)m_tex.height();

    for (unsigned int y = 0; y < height; y++)
    {
        for (unsigned int x = 0; x < width; x++)
        {
            unsigned char c = chars[i];
            unsigned char col_bg = colours[i] >> 4;
            unsigned char col_fg = colours[i] & 0xF;

            if (col_bg != 0)
            {
                FontVertLayout v;
                v.tex_x = v.tex_y = 0;
                v.r = term_colours[col_bg].r;
                v.g = term_colours[col_bg].g;
                v.b = term_colours[col_bg].b;
                v.a = 255;

                v.pos_x = adv.x;
                v.pos_y = adv.y;
                verts.push_back(v);

                v.pos_x = adv.x;
                v.pos_y = adv.y + m_max_advance.y;
                verts.push_back(v);

                v.pos_x = adv.x + m_max_advance.x;
                v.pos_y = adv.y + m_max_advance.y;
                verts.push_back(v);

                v.pos_x = adv.x + m_max_advance.x;
                v.pos_y = adv.y;
                verts.push_back(v);
            }

            adv.x += m_glyphs[c].offset;

            if (m_glyphs[c].renderable)
            {
                int this_width = m_glyphs[c].width;

                float tex_x = (float)(c % 16) / 16.0f;
                float tex_y = (float)(c / 16) / 16.0f;
                float tex_x2 = tex_x + (float)this_width / (float)m_tex.width();
                float tex_y2 = tex_y + texcoord_dy;
                FontVertLayout v;
                v.r = term_colours[col_fg].r;
                v.g = term_colours[col_fg].g;
                v.b = term_colours[col_fg].b;
                v.a = 255;

                v.pos_x = adv.x;
                v.pos_y = adv.y;
                v.tex_x = tex_x;
                v.tex_y = tex_y;
                verts.push_back(v);

                v.pos_x = adv.x;
                v.pos_y = adv.y + m_max_advance.y;
                v.tex_x = tex_x;
                v.tex_y = tex_y2;
                verts.push_back(v);

                v.pos_x = adv.x + this_width;
                v.pos_y = adv.y + m_max_advance.y;
                v.tex_x = tex_x2;
                v.tex_y = tex_y2;
                verts.push_back(v);

                v.pos_x = adv.x + this_width;
                v.pos_y = adv.y;
                v.tex_x = tex_x2;
                v.tex_y = tex_y;
                verts.push_back(v);
            }

            i++;
            adv.x += m_glyphs[c].advance - m_glyphs[c].offset;
        }

        adv.x = 0;
        adv.y += m_max_advance.y;
    }

    if (!verts.size())
        return;

    GLState state;
    state.array_vertex = true;
    state.array_texcoord = true;
    state.blend = true;
    state.texture = true;
    GLStateManager::set(state);

    m_tex.bind();
    glVertexPointer(2, GL_FLOAT, sizeof(FontVertLayout), &verts[0].pos_x);
    glTexCoordPointer(2, GL_FLOAT, sizeof(FontVertLayout), &verts[0].tex_x);

    if (drop_shadow)
    {
        glColor3f(0.0f, 0.0f, 0.0f);

        glLoadIdentity();
        glTranslatef(x_pos + 1, y_pos + 1, 0.0f);
        glDrawArrays(GL_QUADS, 0, verts.size());

        glColor3f(1.0f, 1.0f, 1.0f);
    }

    glLoadIdentity();
    glTranslatef(x_pos, y_pos, 0.0f);

    glEnableClientState(GL_COLOR_ARRAY);
    glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(FontVertLayout), &verts[0].r);
    glDrawArrays(GL_QUADS, 0, verts.size());
    glDisableClientState(GL_COLOR_ARRAY);
}

struct box_vert
{
    float x;
    float y;
    unsigned char r;
    unsigned char g;
    unsigned char b;
    unsigned char a;
};

static void _draw_box(int x_pos, int y_pos, float width, float height,
                      float box_width, unsigned char box_colour,
                      unsigned char box_alpha)
{
    box_vert verts[4];
    for (unsigned int i = 0; i < 4; i++)
    {
        verts[i].r = term_colours[box_colour].r;
        verts[i].g = term_colours[box_colour].g;
        verts[i].b = term_colours[box_colour].b;
        verts[i].a = box_alpha;
    }
    verts[0].x = x_pos - box_width;
    verts[0].y = y_pos - box_width;
    verts[1].x = verts[0].x;
    verts[1].y = y_pos + height + box_width;
    verts[2].x = x_pos + width + box_width;
    verts[2].y = verts[1].y;
    verts[3].x = verts[2].x;
    verts[3].y = verts[0].y;

    glLoadIdentity();

    GLState state;
    state.array_vertex = true;
    state.array_colour = true;
    state.blend = true;
    GLStateManager::set(state);

    glVertexPointer(2, GL_FLOAT, sizeof(box_vert), &verts[0].x);
    glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(box_vert), &verts[0].r);
    glDrawArrays(GL_QUADS, 0, sizeof(verts) / sizeof(box_vert));
}

unsigned int FTFont::string_height(const formatted_string &str)
{
    std::string temp = str.tostring();
    return string_height(temp.c_str());
}

unsigned int FTFont::string_height(const char *text)
{
    int height = 1;
    for (const char *itr = text; (*itr); itr++)
        if (*itr == '\n')
            height++;

    return char_height() * height;
}

unsigned int FTFont::string_width(const formatted_string &str)
{
    std::string temp = str.tostring();
    return string_width(temp.c_str());
}

unsigned int FTFont::string_width(const char *text)
{
    unsigned int base_width = std::max(-m_min_offset, 0);
    unsigned int max_width = 0;

    unsigned int width = base_width;
    unsigned int adjust = 0;
    for (const char *itr = text; *itr; itr++)
    {
        if (*itr == '\n')
        {
            max_width = std::max(width + adjust, max_width);
            width = base_width;
            adjust = 0;
        }
        else
        {
            width += m_glyphs[*itr].advance;
            adjust = std::max(0, m_glyphs[*itr].width - m_glyphs[*itr].advance);
        }
    }

    max_width = std::max(width + adjust, max_width);
    return max_width;
}

int FTFont::find_index_before_width(const char *text, int max_width)
{
    int width = std::max(-m_min_offset, 0);

    for (int i = 0; text[i]; i++)
    {
        char c = text[i];
        width += m_glyphs[c].advance;
        int adjust = std::max(0, m_glyphs[c].width - m_glyphs[c].advance);
        if (width + adjust > max_width)
            return i;
    }

    return -1;
}

formatted_string FTFont::split(const formatted_string &str,
                               unsigned int max_width, unsigned int max_height)
{
    int max_lines = max_height / char_height();

    if (max_lines < 1)
        return formatted_string();

    formatted_string ret;
    ret += str;

    std::string base = str.tostring();
    int num_lines = 0;

    char *line = &base[0];
    while (true)
    {
        int line_end = find_index_before_width(line, max_width);
        if (line_end == -1)
            break;

        int space_idx = 0;
        for (char *search = &line[line_end]; search > line; search--)
        {
            if (*search == ' ')
            {
                space_idx = search - line;
                break;
            }
        }

        if (++num_lines >= max_lines || !space_idx)
        {
            int ellipses;
            if (space_idx && (space_idx - line_end > 2))
                ellipses = space_idx;
            else
                ellipses = line_end - 2;

            size_t idx = &line[ellipses] - &base[0];
            ret[idx] = '.';
            ret[idx+1] = '.';

            return ret.substr(0, idx + 2);
        }
        else
        {
            line[space_idx] = '\n';
            ret[&line[space_idx] - &base[0]] = '\n';
        }

        line = &line[space_idx+1];
    }

    return ret;
}

void FTFont::render_string(unsigned int px, unsigned int py,
                           const char *text,
                           const coord_def &min_pos, const coord_def &max_pos,
                           unsigned char font_colour, bool drop_shadow,
                           unsigned char box_alpha,
                           unsigned char box_colour,
                           unsigned int outline)
{
    ASSERT(text);

    // Determine extent of this text
    unsigned int max_rows = 1;
    unsigned int cols = 0;
    unsigned int max_cols = 0;
    for (const char *itr = text; *itr; itr++)
    {
        cols++;
        max_cols = std::max(cols, max_cols);

        // NOTE: only newlines should be used for tool tips.  Don't use EOL.
        ASSERT(*itr != '\r');

        if (*itr == '\n')
        {
            cols = 0;
            max_rows++;
        }
    }

    // Create the text block
    unsigned char *chars = (unsigned char *)alloca(max_rows * max_cols);
    unsigned char *colours = (unsigned char *)alloca(max_rows * max_cols);
    memset(chars, ' ', max_rows * max_cols);
    memset(colours, font_colour, max_rows * max_cols);

    // Fill the text block
    cols = 0;
    unsigned int rows = 0;
    for (const char *itr = text; *itr; itr++)
    {
        chars[cols + rows * max_cols] = *itr;
        cols++;

        if (*itr == '\n')
        {
            cols = 0;
            rows++;
        }
    }

    // Find a suitable location on screen
    const int buffer = 5;  // additional buffer size from edges

    int wx = string_width(text);
    int wy = max_rows * char_height();

    // text starting location
    int tx = px - wx / 2;
    int ty = py - wy - outline;

    // box with extra buffer to test against min_pos/max_pos window size
    int sx = tx - buffer;
    int sy = ty - buffer;
    int ex = tx + wx + buffer;
    int ey = ty + wy + buffer;

    if (ex > max_pos.x)
        tx += max_pos.x - ex;
    else if (sx < min_pos.x)
        tx -= sx - min_pos.x;

    if (ey > max_pos.y)
        ty += max_pos.y - ey;
    else if (sy < min_pos.y)
        ty -= sy - min_pos.y;

    if (box_alpha != 0)
        _draw_box(tx, ty, wx, wy, outline, box_colour, box_alpha);

    render_textblock(tx, ty, chars, colours, max_cols, max_rows, drop_shadow);
}

void FTFont::store(FontBuffer &buf, float &x, float &y,
                   const std::string &str, const VColour &col)
{
    store(buf, x, y, str, col, x);
}

void FTFont::store(FontBuffer &buf, float &x, float &y,
                   const std::string &str, const VColour &col, float orig_x)
{
    for (unsigned int i = 0; i < str.size(); i++)
    {
        char c = str[i];
        if (c == '\n')
        {
            x = orig_x;
            y += m_max_advance.y;
        }
        else
        {
            store(buf, x, y, c, col);
        }
    }
}

void FTFont::store(FontBuffer &buf, float &x, float &y,
                   const formatted_string &fs)
{
    store(buf, x, y, fs, x);
}

void FTFont::store(FontBuffer &buf, float &x, float &y,
                   const formatted_string &fs, float orig_x)
{
    int colour = LIGHTGREY;
    for (unsigned int i = 0; i < fs.ops.size(); i++)
    {
        switch (fs.ops[i].type)
        {
            case FSOP_COLOUR:
                // Only foreground colors for now...
                colour = fs.ops[i].x & 0xF;
                break;
            case FSOP_TEXT:
                store(buf, x, y, fs.ops[i].text, term_colours[colour], orig_x);
                break;
            default:
            case FSOP_CURSOR:
                break;
        }
    }
}

void FTFont::store(FontBuffer &buf, float &x, float &y,
                   char c, const VColour &col)
{
    if (!m_glyphs[c].renderable)
    {
        x += m_glyphs[c].advance;
        return;
    }

    int this_width = m_glyphs[c].width;

    float pos_sx = x + m_glyphs[c].offset;
    float pos_sy = y;
    float pos_ex = pos_sx + this_width;
    float pos_ey = y + m_max_advance.y;

    float tex_sx = (float)(c % 16) / 16.0f;
    float tex_sy = (float)(c / 16) / 16.0f;
    float tex_ex = tex_sx + (float)this_width / (float)m_tex.width();
    float tex_ey = tex_sy + (float)m_max_advance.y / (float)m_tex.height();

    {
        PTCVert &v = buf.get_next();
        v.col = col;
        v.pos_x = pos_sx;
        v.pos_y = pos_sy;
        v.tex_x = tex_sx;
        v.tex_y = tex_sy;
    }
    {
        PTCVert &v = buf.get_next();
        v.col = col;
        v.pos_x = pos_sx;
        v.pos_y = pos_ey;
        v.tex_x = tex_sx;
        v.tex_y = tex_ey;
    }
    {
        PTCVert &v = buf.get_next();
        v.col = col;
        v.pos_x = pos_ex;
        v.pos_y = pos_ey;
        v.tex_x = tex_ex;
        v.tex_y = tex_ey;
    }
    {
        PTCVert &v = buf.get_next();
        v.col = col;
        v.pos_x = pos_ex;
        v.pos_y = pos_sy;
        v.tex_x = tex_ex;
        v.tex_y = tex_sy;
    }

    x += m_glyphs[c].advance;
}
