#include <GL/glew.h>
#include <string.h>
#include <string>
#include <stdarg.h>
#include <stdio.h>
#include <stack>

#include <locale>
#include <wchar.h>
#include "text.h"
#include <window.h>

int _getBytesCount(char);
bool _isStart(char);
bool _isASCII(char);
unsigned int _getSymbol(char*, int);
vec3f _parseColor(wchar_t *data);

using namespace std;

namespace render::text{

text::text(Font *i)
{
    color = color::black;
    scale = 1;
    justifyAlign = 0;

    align = align_left;
    valign = valign_top;

    _glyphs = 0;
    if(i)
        _font = i;
    else
        _font = 0;
}

text::text(const std::string &str, Font *i):text(i)
{
    make(str);
}

text::text(const std::wstring &str, Font *i):text(i)
{
    make(str);
}

void text::make(const std::wstring &str)
{
    _value = str;
    _remath();
}

void text::push_back(const std::wstring &str)
{
    _value += str;
    _remath();
}

void text::push_back(wchar_t sym)
{
    _value += sym;
    _remath();
}

void text::insert(size_t start, const std::wstring &str, size_t start2, int len)
{
    if(len < 0)
        len = str.length();

    _value.insert(start, str, start2, len);

    _remath();
}

void text::insert(size_t start, wchar_t val)
{
    _value.insert(start, &val, 1);
    _remath();
}

void text::format(const std::wstring &format, ...)
{
    if(format.find('%') == -1)
    make(format);
    else
    {
        wchar_t buffer[1024];

        va_list ap;
        va_start(ap, format);
        vsnwprintf(buffer, 1024, format.c_str(), ap);
        va_end(ap);

        make(buffer);
    }
}

void text::addf(const std::wstring &format, ...)
{
    wchar_t buffer[1024];
    va_list ap;
    va_start(ap, format);
    vsnwprintf(buffer, 1024, format.c_str(), ap);
    va_end(ap);
    push_back(buffer);
}

void text::make(const std::string &str)
{
    _value = UTF8toWide(str);
    _remath();
}

void text::push_back(const std::string &str)
{
    _value += UTF8toWide(str);
    _remath();
}

void text::insert(size_t start, const std::string &str, size_t start2, int len)
{
    if(len < 0)
        len = str.length();

    _value.insert(start, UTF8toWide(str), start2, len);

    _remath();
}

void text::addf(const std::string &format, ...)
{
    char buffer[1024];
    va_list ap;
    va_start(ap, format);
    vsprintf(buffer, format.c_str(), ap);
    va_end(ap);
    push_back(buffer);
}

void text::format(const std::string &format, ...)
{
    if(format.find('%') == -1)
        make(format);
    else
    {
        char buffer[1024];

        va_list ap;
        va_start(ap, format);
        vsprintf(buffer, format.c_str(), ap);
        va_end(ap);

        make(buffer);
    }
}


void text::pop_back()
{
    _value.pop_back();
    _remath();
}

void text::clear()
{
    _value.clear();
    _data.clear();
}

void text::erase(size_t start, size_t _size)
{
    _value.erase(start, _size);
    _remath();
}

size_t text::length()
{
    return _value.length();
}

size_t text::size()
{
    return _value.size();
}

void text::setFont(Font *f)
{
    _font = f;
}

void text::_remath()
{
    _data.clear();
    _data.reserve(_value.length());

    uint32_t temp;
    size_t fPos = 0;

    std::stack<vec3f> colors;

    for(uint32_t i = 0; i < _value.size(); i++)
    {
        switch(_value[i])
        {
        case L'<':
            switch(_value[i+1])
            {
            case L'#':
                colors.push(_parseColor(&_value[i+2]));
                i += 7;
                break;
            case L'!':
                /**< @TODO: Подстановка кастомных значений */
                break;
            }
            break;
        case L'>':
            if(!colors.empty())
                colors.pop();
            break;
        case L'&':

            break;
        default:
            _data.emplace_back(_value[i]);
            if(!colors.empty())
            {
                _data.back().color = colors.top();
                _data.back().useColor = true;
            }
            break;
        }
    }


    if(_glyphs)
        delete [] _glyphs;
    _glyphs = new Font::drawGlyphInfo[_data.size()];
}

struct stringContentInfo{
    std::vector<uint32_t> spacePos;
    float width;
    uint32_t start, end;
};

void text::draw(vec2f pos)
{
    if(!_font)
    {
        return;
    }

    uint32_t _drawCount = 0;
    uint32_t prev = 0;
    vec2f gPos;

    float maxWidth = 0;

    std::list<stringContentInfo*> stringContent;
    stringContentInfo *currentString = new stringContentInfo;
    currentString->start = 0;

    for(auto &v: _data)
    {
        uint32_t val = v.code;

        if(val == L'\n')
        {
            if(maxWidth < gPos.x)
                maxWidth = gPos.x;

            currentString->end = _drawCount;
            currentString->width = gPos.x;
            stringContent.push_back(currentString);
            currentString = new stringContentInfo;
            currentString->start = _drawCount;

            gPos.x = 0;
            gPos.y += _font->maxHeight();
            prev = '\n';
            continue;
        }

        if(!_font->glyphTable[val])
            continue;

        if(val == L' ')
        {
            currentString->spacePos.push_back(_drawCount);
            gPos.x += _font->glyphTable[val]->size.x;
            prev = ' ';
            continue;
        }

        if(prev)
        {
            auto k = _font->_kerningPairs.find(std::make_pair(prev, val));
            if(k != _font->_kerningPairs.end())
                gPos.x += k->second;
        }

        _glyphs[_drawCount].code = val;
        _glyphs[_drawCount].pos = pos+gPos*scale*_font->defaultScale;

        if(v.useColor)
            _glyphs[_drawCount].color = rgba(v.color, color.a);
        else
            _glyphs[_drawCount].color = color;

        gPos.x += _font->glyphTable[val]->size.x;

        _drawCount++;
        prev = val;
    }

    if(maxWidth < gPos.x)
        maxWidth = gPos.x;
    if(justifyAlign)
        for(auto i:stringContent)
            if(i->width < maxWidth && i->spacePos.size())
            {
                float correct = ((maxWidth - i->width)/i->spacePos.size())*scale*_font->defaultScale;
                float offset = 0;
                uint32_t sPos = 0;
                for(int c = i->spacePos[0]; c < i->end; c++)
                {
                    if(sPos != i->spacePos.size())
                        if(c == i->spacePos[sPos])
                        {
                            offset += correct;
                            sPos++;
                        }
                    _glyphs[c].pos.x += offset;
                }
            }
    currentString->width = gPos.x;
    currentString->end = _drawCount;
    stringContent.push_back(currentString);
    rect = vec2f(maxWidth, gPos.y+_font->maxHeight());
    rect *= scale*_font->defaultScale;

    if(valign != valign_top || align != align_left)
        for(auto i:stringContent)
        {
            vec2f offset(-i->width*scale*_font->defaultScale, -rect.y);
            offset /= 2;
            offset.x *= align;
            offset.y *= valign;
            for(int c = i->start; c < i->end; c++)
                _glyphs[c].pos += offset;
        }
    for(auto i:stringContent)
        delete i;
    _font->draw(_glyphs, _drawCount, scale);
}

const std::wstring &text::value()
{
    return _value;
}

std::string text::WidetoUTF8(const std::wstring &str)
{
    std::string result;
    char temp[6];
    for(int i = 0; i < str.size(); i++)
    {
        if(str[i] < 0x80)
            result += str[i]&0xFF;
        else
        {
            if(str[i]>0x7fffffff) continue; //вне диапазона UTF-8
            memset(temp, 0, 6);
            int len = 2;
            if(str[i]>0x7ff) len++;
            if(str[i]>0xffff) len++;
            if(str[i]>0x1fffff) len++;
            if(str[i]>0x3ffffff) len++;

            for(int j = 0; j < len; j++)
                temp[0] |= 1<<(7-j);

            uint32_t fbyte = 6+5*(len-1);

            for(int j = 0; j < 8-len; j++)
                temp[0] |= (((str[i]>>fbyte--)&1)<<(7-len-j));

            for(int j = 1; j < len; j++)
            {
                temp[j] |= 0x80;
                for(int k = 0; k < 6; k++)
                    temp[j] |= ((str[i]>>fbyte--)&1)<<(5-k);
            }
            result.append(temp, len);
        }
    }

    return result;
}

std::wstring text::UTF8toWide(const std::string &str)
{
    char temp[4];
    int len;
    std::wstring result;

    for(int i = 0; i < str.size(); i++)
        if(_isASCII(str[i]))
            result += (wchar_t)str[i];
        else
        {
            memset(temp, 0, 4);
            len = _getBytesCount(str[i])+1;
            for(int j = 0; j <= len; j++)
                temp[j] = str[i+j];

            i += len-1;
            result += (wchar_t)(_getSymbol(temp, len)&0xffff);
        }
    return result;
}

}

vec3f _parseColor(wchar_t *data)
{
    uint32_t ccolor = 0;
    for(int i = 0; i < 6; i++)
    {
        uint8_t tempColor = 0;
        if(data[i] >= L'0' && data[i] <= L'9')
            tempColor = data[i] - L'0';
        else
            switch(data[i])
            {
            case L'A': case L'a':
                tempColor = 0xA; break;
            case L'B': case L'b':
                tempColor = 0xB; break;
            case L'C': case L'c':
                tempColor = 0xC; break;
            case L'D': case L'd':
                tempColor = 0xD; break;
            case L'E': case L'e':
                tempColor = 0xE; break;
            case L'F': case L'f':
                tempColor = 0xF; break;
            }
        ccolor <<= 4;
        ccolor |= tempColor;
    }
    return color::uint2rgb(ccolor);
}

int _getBytesCount(char c)
{
    int ret = 0;
    for(int i = 0; i < 8; i++)
    {
        if((c>>(7-i))&1) ret++;
        else break;
    }
    if(ret > 0) ret--;
    return ret;
}

unsigned int _getSymbol(char *data, int size)
{
    unsigned int result = 0, mask;

    for(int i = 0; i < size; i++)
    {
        if(i) mask = 0x3f;
        else  mask = (0x1f>>(size-2))&0x1f;

        result |= (data[i]&mask)<<(6*(size-i-1));
    }
    return result;
}

bool _isASCII(char c)
{
    return !((c>>7)&1);
}

bool _isStart(char c)
{
    return (((c>>6)&3) == 2);
}
