// rendertext.cpp: font rendering

#include "cube.h"

struct font
{
    struct charinfo
    {
        short x, y, w, h;
    };

    char *name;
    Texture *tex;
    vector<charinfo> chars;
    short defaultw, defaulth;
    short offsetx, offsety, offsetw, offseth;
};

static hashtable<char *, font> fonts;
static font *fontdef = NULL, *curfont = NULL;

void newfont(char *name, char *tex, char *defaultw, char *defaulth, char *offsetx, char *offsety, char *offsetw, char *offseth)
{
    font *f = fonts.access(name);
    if(!f)
    {
        name = newstring(name);
        f = &fonts[name];
        f->name = name;
    }

    f->tex = textureload(tex);
    f->chars.setsize(0);
    f->defaultw = ATOI(defaultw);
    f->defaulth = ATOI(defaulth);
    f->offsetx = ATOI(offsetx);
    f->offsety = ATOI(offsety);
    f->offsetw = ATOI(offsetw);
    f->offseth = ATOI(offseth);

    fontdef = f;
}

void fontchar(int x, int y, int w, int h)
{
    if(!fontdef) return;

    font::charinfo &c = fontdef->chars.add();
    c.x = x;
    c.y = y;
    c.w = w ? w : fontdef->defaultw;
    c.h = h ? h : fontdef->defaulth;
}

COMMANDN(font, newfont, ARG_8STR);
COMMANDN(fontchar, fontchar, ARG_4INT);

bool setfont(char *name)
{
    font *f = fonts.access(name);
    if(!f) return false;
    curfont = f;
    return true;
}

int char_width(int c, int x)
{
    if(!curfont) return x;
    else if(c=='\t') x = (x+PIXELTAB)/PIXELTAB*PIXELTAB;
    else if(c==' ') x += curfont->defaultw;
    else if(curfont->chars.inrange(c-33))
    {
        c -= 33;
        x += curfont->chars[c].w + 1;
    }
    return x;
}

static vector<int> *columns = NULL;

void text_startcolumns()
{
    if(!columns) columns = new vector<int>;
}

void text_endcolumns()
{
    DELETEP(columns);
}

int text_width(const char *str, int limit)
{
    int x = 0, col = 0;
    for(int i = 0; str[i] && (limit<0 || i<limit); i++)
    {
        switch(str[i])
        {
            case '\f':
                i++;
                break;

            case '\t':
                x = char_width('\t', x);
                if(columns)
                {
                    while(col>=columns->length()) columns->add(0);
                    x = max(x, (*columns)[col]);
                    (*columns)[col] = x;
                    col++;
                }
                break;

            default:
                x = char_width(str[i], x);
                break;
        }
    }
    return x;
}

int text_visible(const char *str, int max)
{
    int i = 0, x = 0;
    while(str[i])
    {
        if(str[i]=='\f')
        {
            i += 2;
            continue;
        }
        x = char_width(str[i], x);
        if(x > max) return i;
        ++i;
    }
    return i;
}

// cut strings to fit on screen
void text_block(const char *str, int max, vector<char *> &lines)
{
    if(!str) return;
    int visible;
    while((visible = text_visible(str, max)))
    {
        const char *newline = (const char *)memchr(str, '\n', visible);
        if(newline) visible = newline+1-str;
        else if(str[visible]) // wrap words
        {
            int v = visible;
            while(v > 0 && str[v] != ' ') v--;
            if(v) visible = v+1;
        }
        char *t = lines.add(newstring((size_t)visible));
        s_strncpy(t, str, visible+1);
        str += visible;
    }
}

void draw_textf(const char *fstr, int left, int top, ...)
{
    s_sprintfdlv(str, top, fstr);
    draw_text(str, left, top);
}

void draw_text(const char *str, int left, int top)
{
    if(!curfont) return;

    glBlendFunc(GL_ONE, GL_ONE);
    glBindTexture(GL_TEXTURE_2D, curfont->tex->id);
    glColor3ub(255, 255, 255);

    static float colorstack[8][4];
    int colorpos = 0, x = left, y = top, col = 0;

    glBegin(GL_QUADS);
    for(int i = 0; str[i]; i++)
    {
        int c = str[i];
        switch(c)
        {
            case '\t':
                if(columns && col<columns->length()) x = left + (*columns)[col++];
                else x = (x-left+PIXELTAB)/PIXELTAB*PIXELTAB+left; 
                continue;

            case '\f':
                switch(str[i+1])
		        {
			        case '0': glColor3ub(64,  255, 128); i++; continue;   // green: player talk
                    case '1': glColor3ub(96,  160, 255); i++; continue;   // blue: team chat
			        case '2': glColor3ub(255, 192, 64);  i++; continue;   // yellow: gameplay action messages, only actions done by players
			        case '3': glColor3ub(255, 64,  64);  i++; continue;   // red: important errors
                    case '4': glColor3ub(128, 128, 128); i++; continue;   // gray
			        case '5': glColor3ub(255, 255, 255); i++; continue;   // white: everything else
                    case '6': glColor3ub(192, 64,  192); i++; continue;   // magenta
                    case 's': // save color
                        if((size_t)colorpos<sizeof(colorstack)/sizeof(colorstack[0]))
                        {
                            glEnd();
                            glGetFloatv(GL_CURRENT_COLOR, colorstack[colorpos++]);
                            glBegin(GL_QUADS);
                        }
                        i++;
                        continue;
                    case 'r': // restore color
                        if(colorpos>0)
                            glColor4fv(colorstack[--colorpos]);
                        i++;
                        continue;
                    default: i++; continue;
		        }

            case ' ':
                x += curfont->defaultw;
                continue;
        }

        c -= 33;
        if(!curfont->chars.inrange(c)) continue;

        font::charinfo &info = curfont->chars[c];
        float tc_left    = (info.x + curfont->offsetx) / float(curfont->tex->xs);
        float tc_top     = (info.y + curfont->offsety) / float(curfont->tex->ys);
        float tc_right   = (info.x + info.w + curfont->offsetw) / float(curfont->tex->xs);
        float tc_bottom  = (info.y + info.h + curfont->offseth) / float(curfont->tex->ys);

        glTexCoord2f(tc_left,  tc_top   ); glVertex2i(x,          y);
        glTexCoord2f(tc_right, tc_top   ); glVertex2i(x + info.w, y);
        glTexCoord2f(tc_right, tc_bottom); glVertex2i(x + info.w, y + info.h);
        glTexCoord2f(tc_left,  tc_bottom); glVertex2i(x,          y + info.h);

        xtraverts += 4;
        x += info.w + 1;
    }
    glEnd();
}

