#include "PrecompiledHeader.h"

#include "WFont.h"
#include "WResourceManager.h"
#include "JFileSystem.h"
#include "GameApp.h"

#define ISGBK(c) ((c) > 0x80 || (c) < 0x30 || (c) == '-' || (c) == '/')

static PIXEL_TYPE gencolor(int id, PIXEL_TYPE color)
{
    unsigned int a, r, g, b, r0, g0, b0;
#if defined (PSP)
# if defined (ABGR8888)
    a = (color >> 24) & 0xFF;
    b = (color >> 16) & 0xFF;
    g = (color >> 8) & 0xFF;
    r = (color >> 0) & 0xFF;
# elif defined (ABGR5551)
    a = ((color >> 15) & 0x01) << 7;
    b = ((color >> 10) & 0x1F) << 3;
    g = ((color >> 5) & 0x1F) << 3;
    r = ((color >> 0) & 0x1F) << 3;
# elif defined (ABGR4444)
    a = ((color >> 12) & 0x0F) << 4;
    b = ((color >> 8) & 0x0F) << 4;
    g = ((color >> 4) & 0x0F) << 4;
    r = ((color >> 0) & 0x0F) << 4;
# endif
#else // non PSP
    a = (color >> 24) & 0xFF;
    r = (color >> 16) & 0xFF;
    g = (color >> 8) & 0xFF;
    b = (color >> 0) & 0xFF;
#endif
    r0 = g0 = b0 = 255;

    switch (id)
    {
    case Fonts::MAIN_FONT: // simon 245, 228, 156
        r0 = 245;
        g0 = 228;
        b0 = 156;
        break;
    case Fonts::MENU_FONT: // f3 255, 252, 175
        r0 = 255;
        g0 = 252;
        b0 = 175;
        break;
    case Fonts::MAGIC_FONT: // magic 219, 255, 151
        r0 = 219;
        g0 = 255;
        b0 = 151;
        break;
    case Fonts::SMALLFACE_FONT: // smallface 255, 255, 255
        r0 = 255;
        g0 = 255;
        b0 = 255;
        break;
    default:
        ;
    }
    r = r * r0 / 255;
    g = g * g0 / 255;
    b = b * b0 / 255;

    return ARGB(a,r,g,b);
}

static inline bool doubleWidthChar(const u8* const src)
{
    // Determination of this is done with < 0xC0, which is not
    // right but okay for now (in particular it will return 2
    // for european accented characters)
    return *src >= 0xC0;
}

static inline bool GBKDoubleWidthChar(const u8* const src)
{
    // Determination of this is done with < 0xC0, which is not
    // right but okay for now (in particular it will return 2
    // for european accented characters)
    return *src >= 0x80;
}

// This returns the size in bytes of a character.
// The 4 first bits of the leading byte indicate the length.
// The zeroes in the middle match the 10xx forms, which are
// only found in trailing bytes.
static inline int charWidth(const u8 s)
{
    // The following is the "correct" one, but if called on incorrect strings
    // the presence of 0's may create an infinite loop, so replace them with
    // 1's. This may happen when changing from chinese to japanese because the
    // encoding for chinese is not regular.
    //  static const int sizes[] = {1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 3, 4};
    static const int sizes[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 4 };
#if 0
    // Reactivate to support being called on trailing bytes. This is not needed at the moment
    // as this function will always be passed leading bytes.
    while (0 == sizes[s >> 4]) --s;
#endif
    return sizes[s >> 4];
}

//

JRenderer * WFBFont::mRenderer = NULL;

WLBFont::WLBFont(int inFontID, const char *fontname, int lineheight, bool useVideoRAM) :
    WFont(inFontID)
{
    string path(fontname);
    if (path.size() > 4) path = path.substr(0, path.size() - 4); //some stupid manipulation because of the way Font works in JGE
    it = NEW JLBFont(path.c_str(), lineheight, useVideoRAM);
}

void WLBFont::FormatText(string &s, vector<string>& output)
{
    // The way of CardPrimitive::formattedText() in r2081.
    std::string::size_type len = 30;
    while (s.length() > 0)
    {
        std::string::size_type cut = s.find_first_of("., \t)", 0);
        if (cut >= len || cut == string::npos)
        {
            output.push_back(s.substr(0,len));
            if (s.length() > len)
                s = s.substr(len, s.length() - len);
            else
                s = "";
        }
        else
        {
            std::string::size_type newcut = cut;
            while (newcut < len && newcut != string::npos)
            {
                cut = newcut;
                newcut = s.find_first_of("., \t)", newcut + 1);
            }
            output.push_back(s.substr(0,cut+1));
            if (s.length() > cut+1)
                s = s.substr(cut+1,s.length() - cut - 1);
            else
                s = "";
        }
    }
}

WFBFont::WFBFont(int inFontID, const char *fontname, int lineheight, bool) :
WFont(inFontID), mTexture(0)
{
    mRenderer = JRenderer::GetInstance();

    mCharBuffer = NULL;
    mSprites = NULL;
    mGBCode = NULL;
    mCurr = 0;

    char tmpFileName[32], engFileName[32];
    strcpy(tmpFileName, fontname);
    char * ep = strrchr(tmpFileName, '.');
    *ep = '\0';
    sprintf(engFileName, "%s.asc", tmpFileName);
    JFileSystem *fileSys = JFileSystem::GetInstance();
    int size = 0;
    struct
    {
        unsigned short chars;
        unsigned char width;
        unsigned char height;
    } sizeStr = { 0, 0, 0 };

    if (!fileSys->OpenFile(engFileName)) return;
    size = fileSys->GetFileSize();
    mStdFont = NEW u8[size];
    fileSys->ReadFile(mStdFont, size);
    fileSys->CloseFile();

    if (!fileSys->OpenFile(fontname)) return;
    fileSys->ReadFile(&sizeStr, 4); // Works only for little-endian machines (PSP and PC are)
    size = sizeStr.chars * sizeStr.width * sizeStr.height / 2;
    mExtraFont = NEW u8[size]; // 4 bits for a pixel
    mIndex = NEW u16[65536];
    fileSys->ReadFile(mIndex, 65536 * sizeof(u16));
    fileSys->ReadFile(mExtraFont, size);
    fileSys->CloseFile();

    mColor0 = ARGB(255, 255, 255, 255);
    mColor = mColor0;
    mFontSize = lineheight;
    mScale = 1.0f;

    // using 4-bit(half-byte) to store 1 pixel
    mBytesPerRow = static_cast<unsigned int> (mFontSize / 2);
    mBytesPerChar = static_cast<unsigned int> (mBytesPerRow * mFontSize);

    mCacheImageWidth = 256;
    mCacheImageHeight = 256;
    mCol = mCacheImageWidth / mFontSize;
    mRow = mCacheImageHeight / mFontSize;
    mCacheSize = mCol * mRow;

    mSprites = NEW JQuad*[mCacheSize];
    mGBCode = NEW int[mCacheSize];

#if !defined (PSP)
    mCharBuffer = NEW PIXEL_TYPE[mFontSize*mFontSize];
#endif

    mTexture = mRenderer->CreateTexture(mCacheImageWidth, mCacheImageHeight, true);

    int index = 0;
    for (int y = 0; y < mRow; y++)
    {
        for (int x = 0; x < mCol; x++)
        {
            mGBCode[index] = -1;
            mSprites[index] = NEW JQuad(mTexture, static_cast<float> (x * mFontSize), static_cast<float> (y * mFontSize),
                            static_cast<float> (mFontSize), static_cast<float> (mFontSize));
            mSprites[index]->SetHotSpot(static_cast<float> (mFontSize / 2), static_cast<float> (mFontSize / 2));
            index++;
        }
    }
}

WFBFont::~WFBFont()
{
    SAFE_DELETE_ARRAY(mStdFont);
    SAFE_DELETE_ARRAY(mExtraFont);
    SAFE_DELETE(mTexture);

    if (mSprites)
    {
        for (int i = 0; i < mCacheSize; i++)
        {
            if (mSprites[i]) delete mSprites[i];
        }
        delete[] mSprites;
    }

    if (NULL != mIndex) delete[] mIndex;

    if (mGBCode) delete[] mGBCode;

    if (mCharBuffer) delete[] mCharBuffer;
}

#if defined (PSP)
static void SwizzlePlot(u8* out, PIXEL_TYPE color, int i, int j, unsigned int width)
{
    unsigned int rowblocks = (width >> 4);

    unsigned int blockx = (i >> 4);
    unsigned int blocky = (j >> 3);

    unsigned int x = (i - (blockx << 4));
    unsigned int y = (j - (blocky << 3));
    unsigned int block_index = blockx + ((blocky) * rowblocks);
    unsigned int block_address = block_index << 7;

    u8* p = out + (block_address + x + (y << 4));
    PIXEL_TYPE* dest = (PIXEL_TYPE *) p;
    *dest = color;
}
#endif

int WFBFont::PreCacheChar(const u8 *ch)
{
    int code;
    int charLength = 1;
    u8 * src;
    unsigned int size, offset;
    u8 gray;

    code = this->GetCode(ch, &charLength);
    if (doubleWidthChar(ch) && mIndex) code = mIndex[code]; // mGBCode[] stores the final code.

    if (mGBCode[mCurr] != -1) for (int i = 0; i < mCacheSize; i++)
        if (mGBCode[i] == code) return i;

    int index = mCurr++;
    if (mCurr >= mCacheSize) mCurr = 0;

#if defined (PSP)
    u8* pTexture = (u8*) mTexture->mBits;
    int x;
    int y = (int) mSprites[index]->mY;
#else
    int x = 0;
    int y = 0;
    memset(mCharBuffer, 0, sizeof(u32) * mFontSize * mFontSize);
#endif

    if (doubleWidthChar(ch))
    {
        //if (mIndex) code = mIndex[code];
        size = mFontSize;
        src = mExtraFont + code * mBytesPerChar;
        offset = 0;
    }
    else
    {
        size = mFontSize / 2;
        src = mStdFont + code * (mFontSize * size / 2);
        offset = 0;
    }

    // set up the font texture buffer
    for (unsigned int i = 0; i < mFontSize; i++)
    {
#if defined (WIN32) || defined (LINUX)
        x = 0;
#else
        x = (int) mSprites[index]->mX;
#endif
        unsigned int j = 0;
#if 1
        for (; j < offset; j++)
        {
#if defined (PSP)
            SwizzlePlot(pTexture, ARGB(0, 0, 0, 0), x * PIXEL_SIZE, y, mTexture->mTexWidth * PIXEL_SIZE);
#else
            mCharBuffer[y * mFontSize + x] = ARGB(0, 0, 0, 0);
#endif
            x++;
        }
#endif
        for (; j < offset + size; j++)
        {
            // as 4-bit(half-byte) stores 1 pixel
            // get out the proper data according to the even or odd quality of the counter
            gray = src[(i * size + j - offset) / 2];
            gray = ((j - offset) & 1) ? (gray & 0xF0) : ((gray & 0x0F) << 4);
            if (gray) gray |= 0x0F;
#if defined (PSP)
            SwizzlePlot(pTexture, ARGB(gray, 255, 255, 255), x * PIXEL_SIZE, y, mTexture->mTexWidth * PIXEL_SIZE);
#else
            mCharBuffer[y * mFontSize + x] = ARGB(gray, 255, 255, 255);
#endif
            x++;
        }
        for (; j < mFontSize; j++)
        {
#if defined (PSP)
            SwizzlePlot(pTexture, ARGB(0, 0, 0, 0), x * PIXEL_SIZE, y, mTexture->mTexWidth * PIXEL_SIZE);
#else
            mCharBuffer[y * mFontSize + x] = ARGB(0, 0, 0, 0);
#endif
            x++;
        }
        y++;
    }

    mGBCode[index] = code;

#if defined (PSP)
    sceKernelDcacheWritebackAll();
#else
    x = (int)mSprites[index]->mX;
    y = (int)mSprites[index]->mY;
    mTexture->UpdateBits(x, y, mFontSize, mFontSize, mCharBuffer);
#endif
    return index;
}

void WFBFont::DrawString(const char *s, float x, float y, int align, float leftOffset, float width)
{
    u8 * str = (u8 *) s;
    if (*str < 0x80)
    {
        // tricky:  the single byte font is always mFontID + kSingleByteFontOffset!
        // See WResourceManager::InitFonts()
        WFont * mFont = WResourceManager::Instance()->GetWFont(mFontID + Fonts::kSingleByteFontOffset);
        mFont->SetScale(GetScale());
        mFont->SetColor(GetColor());
        mFont->DrawString(s, x, y, align, leftOffset, width);
        return;
    }

    // (0, 0) refers to the center of the word, so fix it to the upper-left corner
    x += (mFontSize * mScale) / 2;
    y += (mFontSize * mScale) / 2;
    // Warning : non-left alignment is not supported for multiline strings
    // (this is because GetStringWidth is not aware of line breaks).
    switch (align)
    {
    case JGETEXT_RIGHT:
        if (width) {
            x -= width;
            leftOffset += GetStringWidth(s) - width;
        }
        else
            x -= GetStringWidth(s);
        break;
    case JGETEXT_CENTER:
        if (width) {
            x -= width/2;
            leftOffset += GetStringWidth(s)/2 - width/2;
        }
        else
            x -= GetStringWidth(s)/2;
        break;
    case JGETEXT_LEFT:
    default:
        break;
    }

    mRenderer->BindTexture(mTexture);

    u8 * src = str;
    float xx = x;
    float yy = y;
    int index = 0;

    while (*src != 0)
    {
        if (yy > SCREEN_HEIGHT_F) // don't render or count outside the buttom of viewport
            return;
        else if (yy + mFontSize < 0.0f)
        { // don't render when outside the top of viewport, but counted
            if (*src < 0x20)
            { // control characters
                if (*src == 0x0a)
                { // NEWLINE
                    xx = x;
                    yy += (mFontSize * mScale);
                }
                src += 1;
            }
            else
            {
                xx += (mFontSize * mScale);

                if (xx >= width)
                {
                    xx = x;
                    yy += (mFontSize * mScale);
                }
            }
        }
        else
        {
            if (*src < 0x20)
            { // control characters
                if (*src == 0x0a)
                { // NEWLINE
                    xx = x;
                    yy += (mFontSize * mScale);
                }
                src += 1;
            }
            else
            {
                int mana = this->GetMana(src);
                bool doubleW = doubleWidthChar(src);
                index = PreCacheChar(src);
                src += charWidth(*src);

                // fix for leftoffset and width's setting
                float xPos, yPos, charW, charHeight;
                mSprites[index]->GetTextureRect(&xPos, &yPos, &charW, &charHeight);
                float xPos0 = xPos;
                float charW0 = charW;
                float delta = doubleW ? (charW * mScale) : (charW * mScale / 2);
                if (leftOffset)
                {
                    if (leftOffset < 0)
                    {
                        xx -= leftOffset;
                        leftOffset = 0;
                    }
                    else if (leftOffset - delta > 0)
                    {
                        leftOffset -= delta;
                        continue;
                    }
                    else
                    {
                        xPos += leftOffset / mScale;
                        delta -= leftOffset;
                        leftOffset = 0;
                        charW = delta / mScale;
                    }
                }
                if (width)
                {
                    if (xx > x + width) return;
                    if (xx + delta > x + width)
                    {
                        delta = x + width - xx;
                        charW = delta / mScale;
                    }
                }

                if (mana >= 0)
                {
                    int mana2 = -1;
                    if (*src == '/' && (mana2 = this->GetMana(src + 1)) >= 0)
                    { // hybrid mana cost
                        src += 1 + charWidth(*src);
                        unsigned char t = (JGE::GetInstance()->GetTime() / 3) & 0xFF;
                        unsigned char v = t + 127;
                        float scale = 0.05f * cosf(2 * M_PI * ((float) t) / 256.0f);
                        if (scale < 0)
                        {
                            mRenderer->RenderQuad(manaIcons[mana].get(), xx + 3 * sinf(2 * M_PI * ((float) t) / 256.0f), yy + 3 * cosf(2
                                            * M_PI * ((float) (t - 35)) / 256.0f), 0, 0.5f * mScale, 0.5f * mScale);
                            mRenderer->RenderQuad(manaIcons[mana2].get(), xx + 3 * sinf(2 * M_PI * ((float) v) / 256.0f), yy + 3 * cosf(2
                                            * M_PI * ((float) (v - 35)) / 256.0f), 0, 0.5f * mScale, 0.5f * mScale);
                        }
                        else
                        {
                            mRenderer->RenderQuad(manaIcons[mana2].get(), xx + 3 * sinf(2 * M_PI * ((float) v) / 256.0f), yy + 3 * cosf(2
                                            * M_PI * ((float) (v - 35)) / 256.0f), 0, 0.5f * mScale, 0.5f * mScale);
                            mRenderer->RenderQuad(manaIcons[mana].get(), xx + 3 * sinf(2 * M_PI * ((float) t) / 256.0f), yy + 3 * cosf(2
                                            * M_PI * ((float) (t - 35)) / 256.0f), 0, 0.5f * mScale, 0.5f * mScale);
                        }
                        mana = Constants::NB_Colors + 1; // do not draw colorless cost in hybrid mana cost
                    }
                    else
                        mRenderer->RenderQuad(manaIcons[mana].get(), xx, yy, 0, 0.5f * mScale, 0.5f * mScale);
                    mRenderer->BindTexture(mTexture); // manaIcons use different texture, so we need to rebind it.
                }

                if (mana <= 0)
                {
                    mSprites[index]->SetTextureRect(xPos, yPos, charW, charHeight);
                    mSprites[index]->SetColor(mColor);
                    mRenderer->RenderQuad(mSprites[index], xx, yy, 0, mScale, mScale);
                    mSprites[index]->SetTextureRect(xPos0, yPos, charW0, charHeight);
                }

                xx += delta;

                if (xx >= 480)
                {
                    xx = x;
                    yy += (mFontSize * mScale);
                }
            }
        }
    }
}

void WFBFont::DrawString(std::string s, float x, float y, int align, float leftOffset, float width)
{
    DrawString(s.c_str(), x, y, align, leftOffset, width);
}

void WFBFont::SetColor(PIXEL_TYPE color)
{
    mColor0 = color;
    mColor = gencolor(mFontID, color);
}

float WFBFont::GetStringWidth(const char *s) const
{
    u8 * src = (u8 *) s;
    int width = 0;

    if (doubleWidthChar(src))
    {
        while (*src != 0)
        {
            // Add the number of single-char widths according to whether
            // this is a single or double-width char.
            width += (doubleWidthChar(src)) ? 2 : 1;
            src += charWidth(*src);
        }
        return width * mFontSize * mScale / 2;
    }
    else
    {
        WFont * mFont = WResourceManager::Instance()->GetWFont(mFontID + Fonts::kSingleByteFontOffset);
        mFont->SetScale(GetScale());
        return mFont->GetStringWidth(s);
    }

}

void WFBFont::SetScale(float scale)
{
    mScale = scale;
}

float WFBFont::GetScale() const
{
    return mScale;
}
float WFBFont::GetHeight() const
{
    return (mFontSize * mScale);
}

// Legacy : GBK encoding
WGBKFont::WGBKFont(int inFontID, const char *fontname, int lineheight, bool) :
    WFBFont(inFontID)
{
    mRenderer = JRenderer::GetInstance();

    mCharBuffer = NULL;
    mSprites = NULL;
    mGBCode = NULL;
    mCurr = 0;

    char tmpFileName[32], engFileName[32];
    strcpy(tmpFileName, fontname);
    char * ep = strrchr(tmpFileName, '.');
    *ep = '\0';
    sprintf(engFileName, "%s.asc", tmpFileName);
    JFileSystem *fileSys = JFileSystem::GetInstance();
    int size = 0;

    if (!fileSys->OpenFile(fontname)) return;
    size = fileSys->GetFileSize();
    mExtraFont = NEW u8[size];
    fileSys->ReadFile(mExtraFont, size);
    fileSys->CloseFile();

    if (!fileSys->OpenFile(engFileName)) return;
    size = fileSys->GetFileSize();
    mStdFont = NEW u8[size];
    fileSys->ReadFile(mStdFont, size);
    fileSys->CloseFile();

    mIndex = 0;

    mColor0 = ARGB(255, 255, 255, 255);
    mColor = mColor0;
    mFontSize = lineheight;
    mScale = 1.0f;

    // using 4-bit(half-byte) to store 1 pixel
    mBytesPerRow = static_cast<unsigned int> (mFontSize / 2);
    mBytesPerChar = static_cast<unsigned int> (mBytesPerRow * mFontSize);

    mCacheImageWidth = 256;
    mCacheImageHeight = 256;
    mCol = mCacheImageWidth / mFontSize;
    mRow = mCacheImageHeight / mFontSize;
    mCacheSize = mCol * mRow;

    mSprites = NEW JQuad*[mCacheSize];
    mGBCode = NEW int[mCacheSize];

#if !defined (PSP)
    mCharBuffer = NEW PIXEL_TYPE[mFontSize*mFontSize];
#endif

    mTexture = mRenderer->CreateTexture(mCacheImageWidth, mCacheImageHeight, true);

    int index = 0;
    for (int y = 0; y < mRow; y++)
    {
        for (int x = 0; x < mCol; x++)
        {
            mGBCode[index] = -1;
            mSprites[index] = NEW JQuad(mTexture, static_cast<float> (x * mFontSize), static_cast<float> (y * mFontSize),
                            static_cast<float> (mFontSize), static_cast<float> (mFontSize));
            mSprites[index]->SetHotSpot(static_cast<float> (mFontSize / 2), static_cast<float> (mFontSize / 2));
            index++;
        }
    }
}

int WGBKFont::PreCacheChar(const u8 *ch)
{
    int code;
    int charLength = 1;
    u8 * src;
    unsigned int size, offset;
    u8 gray;

    code = this->GetCode(ch, &charLength);

    if (mGBCode[mCurr] != -1) for (int i = 0; i < mCacheSize; i++)
        if (mGBCode[i] == code) return i;

    int index = mCurr++;
    if (mCurr >= mCacheSize) mCurr = 0;

#if defined(PSP)
    u8* pTexture = (u8*) mTexture->mBits;
    int x;
    int y = (int) mSprites[index]->mY;
#else
    int x = 0;
    int y = 0;
    memset(mCharBuffer, 0, sizeof(u32) * mFontSize * mFontSize);
#endif

    if (mIndex) code = mIndex[code];

    if (GBKDoubleWidthChar(ch))
    {
        size = mFontSize;
        src = mExtraFont + code * mBytesPerChar;
        offset = 0;
    }
    else
    {
        size = mFontSize / 2;
        src = mStdFont + code * (mFontSize * size / 2);
        offset = 0;
    }

    // set up the font texture buffer
    for (unsigned int i = 0; i < mFontSize; i++)
    {
#if defined (PSP)
        x = (int) mSprites[index]->mX;
#else
        x = 0;
#endif
        unsigned int j = 0;
#if 1
        for (; j < offset; j++)
        {
#if defined (PSP)
            SwizzlePlot(pTexture, ARGB(0, 0, 0, 0), x * PIXEL_SIZE, y, mTexture->mTexWidth * PIXEL_SIZE);
#else
            mCharBuffer[y * mFontSize + x] = ARGB(0, 0, 0, 0);
#endif
            x++;
        }
#endif
        for (; j < offset + size; j++)
        {
            // as 4-bit(half-byte) stores 1 pixel
            // get out the proper data according to the even or odd quality of the counter
            gray = src[(i * size + j - offset) / 2];
            gray = ((j - offset) & 1) ? (gray & 0xF0) : ((gray & 0x0F) << 4);
            if (gray) gray |= 0x0F;
#if defined (PSP)
            SwizzlePlot(pTexture, ARGB(gray, 255, 255, 255), x * PIXEL_SIZE, y, mTexture->mTexWidth * PIXEL_SIZE);
#else
            mCharBuffer[y * mFontSize + x] = ARGB(gray, 255, 255, 255);
#endif
            x++;
        }
        for (; j < mFontSize; j++)
        {
#if defined (PSP)
            SwizzlePlot(pTexture, ARGB(0, 0, 0, 0), x * PIXEL_SIZE, y, mTexture->mTexWidth * PIXEL_SIZE);
#else
            mCharBuffer[y * mFontSize + x] = ARGB(0, 0, 0, 0);
#endif
            x++;
        }
        y++;
    }

    mGBCode[index] = code;

#if defined (PSP)
    sceKernelDcacheWritebackAll();
#else
    x = (int)mSprites[index]->mX;
    y = (int)mSprites[index]->mY;
    mTexture->UpdateBits(x, y, mFontSize, mFontSize, mCharBuffer);
#endif
    return index;
}

void WGBKFont::DrawString(const char *s, float x, float y, int align, float leftOffset, float width)
{
    unsigned char c = *(unsigned short *) s & 0xFF;
    if (ISGBK(c) || (s[1] == ':' && s[2] == ' '))
    {
    }
    else
    {
        // tricky:  the single byte font is always mFontID + kSingleByteFontOffset!
        // See WResourceManager::InitFonts()
        WFont * mFont = WResourceManager::Instance()->GetWFont(mFontID + Fonts::kSingleByteFontOffset);
        mFont->SetScale(GetScale());
        mFont->SetColor(GetColor());
        mFont->DrawString(s, x, y, align, leftOffset, width);
        return;
    }

    u8 * str = (u8 *) s;

    // (0, 0) refers to the center of the word, so fix it to the upper-left corner
    x += (mFontSize * mScale) / 2;
    y += (mFontSize * mScale) / 2;
    switch (align)
    {
    case JGETEXT_RIGHT:
        if (width) {
            x -= width;
            leftOffset += GetStringWidth(s) - width;
        }
        else
            x -= GetStringWidth(s);
        break;
    case JGETEXT_CENTER:
        if (width) {
            x -= width/2;
            leftOffset += GetStringWidth(s)/2 - width/2;
        }
        else
            x -= GetStringWidth(s)/2;
        break;
    case JGETEXT_LEFT:
    default:
        break;
    }

    mRenderer->BindTexture(mTexture);

    u8 * src = str;
    float xx = x;
    float yy = y;
    int index = 0;

    bool dualByteFont = true;

    while (*src != 0)
    {
        if (yy > SCREEN_HEIGHT_F) // don't render or count outside the buttom of viewport
            return;
        else if (yy + mFontSize < 0.0f)
        { // don't render when outside the top of viewport, but counted
            if (*src < 0x20)
            { // control characters
                if (*src == 0x0a)
                { // NEWLINE
                    xx = x;
                    yy += (mFontSize * mScale);
                }
                src += 1;
            }
            else
            {
                if (*src > 0x80) // 2-bytes char
                    src += 2;
                else
                    src += 1;

                xx += (mFontSize * mScale);

                if (xx >= 480)
                {
                    xx = x;
                    yy += (mFontSize * mScale);
                }
            }
        }
        else
        {
            if (*src < 0x20)
            { // control characters
                if (*src == 0x0a)
                { // NEWLINE
                    xx = x;
                    yy += (mFontSize * mScale);
                }
                src += 1;
            }
            else
            {
                int mana = -1;
                if (*src > 0x80)
                { // 2-bytes char
                    mana = this->GetMana(src);
                    index = PreCacheChar(src);
                    src += 2;
                    dualByteFont = true;
                }
                else
                {
                    index = PreCacheChar(src);
                    src += 1;
                    dualByteFont = false;
                }

                // fix for leftoffset and witdth's setting
                float xPos, yPos, charW, charHeight;
                mSprites[index]->GetTextureRect(&xPos, &yPos, &charW, &charHeight);
                float xPos0 = xPos;
                float charW0 = charW;
                float delta = (dualByteFont) ? (charW * mScale) : (charW * mScale / 2);
                if (leftOffset)
                {
                    if (leftOffset < 0)
                    {
                        xx -= leftOffset;
                        leftOffset = 0;
                    }
                    else if (leftOffset - delta > 0)
                    {
                        leftOffset -= delta;
                        continue;
                    }
                    else
                    {
                        xPos += leftOffset / mScale;
                        delta -= leftOffset;
                        leftOffset = 0;
                        charW = delta / mScale;
                    }
                }
                if (width)
                {
                    if (xx > x + width) return;
                    if (xx + delta > x + width)
                    {
                        delta = x + width - xx;
                        charW = delta / mScale;
                    }
                }

                if (mana >= 0)
                {
                    int mana2 = -1;
                    if (*src == '/' && (mana2 = this->GetMana(src + 1)) >= 0)
                    { // hybrid mana cost
                        src += 3;
                        unsigned char t = (JGE::GetInstance()->GetTime() / 3) & 0xFF;
                        unsigned char v = t + 127;
                        float scale = 0.05f * cosf(2 * M_PI * ((float) t) / 256.0f);
                        if (scale < 0)
                        {
                            mRenderer->RenderQuad(manaIcons[mana].get(), xx + 3 * sinf(2 * M_PI * ((float) t) / 256.0f), yy + 3 * cosf(2
                                            * M_PI * ((float) (t - 35)) / 256.0f), 0, 0.5f * mScale, 0.5f * mScale);
                            mRenderer->RenderQuad(manaIcons[mana2].get(), xx + 3 * sinf(2 * M_PI * ((float) v) / 256.0f), yy + 3 * cosf(2
                                            * M_PI * ((float) (v - 35)) / 256.0f), 0, 0.5f * mScale, 0.5f * mScale);
                        }
                        else
                        {
                            mRenderer->RenderQuad(manaIcons[mana2].get(), xx + 3 * sinf(2 * M_PI * ((float) v) / 256.0f), yy + 3 * cosf(2
                                            * M_PI * ((float) (v - 35)) / 256.0f), 0, 0.5f * mScale, 0.5f * mScale);
                            mRenderer->RenderQuad(manaIcons[mana].get(), xx + 3 * sinf(2 * M_PI * ((float) t) / 256.0f), yy + 3 * cosf(2
                                            * M_PI * ((float) (t - 35)) / 256.0f), 0, 0.5f * mScale, 0.5f * mScale);
                        }
                        mana = Constants::NB_Colors + 1; // donot draw colorless cost in hybrid mana cost
                    }
                    else
                        mRenderer->RenderQuad(manaIcons[mana].get(), xx, yy, 0, 0.5f * mScale, 0.5f * mScale);
                    mRenderer->BindTexture(mTexture); // manaIcons use different texture, so we need to rebind it.
                }

                if (mana <= 0)
                {
                    mSprites[index]->SetTextureRect(xPos, yPos, charW, charHeight);
                    mSprites[index]->SetColor(mColor);
                    mRenderer->RenderQuad(mSprites[index], xx, yy, 0, mScale, mScale);
                    mSprites[index]->SetTextureRect(xPos0, yPos, charW0, charHeight);
                }

                xx += delta;

                if (xx >= 480)
                {
                    xx = x;
                    yy += (mFontSize * mScale);
                }
            }
        }
    }
}

float WGBKFont::GetStringWidth(const char *s) const
{
    unsigned char c = *(unsigned short *) s & 0xFF;

    if (ISGBK(c))
    {
        u8 * src = (u8 *) s;
        float xx = 0;
        bool dualByteFont = true;

        while (*src != 0)
        {
            if (*src > 0x80)
            { // Chinese and Japanese
                src += 2;
                dualByteFont = true;
            }
            else
            { // Latin 1
                src += 1;
                dualByteFont = false;
            }
            if (dualByteFont)
                xx += (mFontSize * mScale);
            else
                xx += (mFontSize * mScale) / 2;
        }
        return xx;
    }
    else
    {
        WFont * mFont = WResourceManager::Instance()->GetWFont(mFontID + Fonts::kSingleByteFontOffset);
        mFont->SetScale(GetScale());
        return mFont->GetStringWidth(s);
    }
}

int WGBKFont::GetCode(const u8 *ch, int *charLength) const
{
    int code = 0;
    *charLength = 2;

    if (*ch > 0xA0 && *(ch + 1) > 0xA0)
    {
        // get offset to the proper character bits (GB2312 encoding)
        code = (((u32)(*ch - 0xA1)) * 0x5E + ((u32)(*(ch + 1) - 0xA1)));
    }
    else if (*ch > 0x80)
    {
        // get offset to the character space's bits (GBK encoding)
        code = 0;
    }
    else
    {
        code = ((u32) * ch);
        *charLength = 1;
    }
    return code;
}

int WGBKFont::GetMana(const u8 *ch) const
{
    int mana = -1;

    if (*ch != 0xa3) return mana;
    switch (*(ch + 1))
    {
    case 0xC7:
        mana = Constants::MTG_COLOR_GREEN;
        break;
    case 0xD5:
        mana = Constants::MTG_COLOR_BLUE;
        break;
    case 0xD2:
        mana = Constants::MTG_COLOR_RED;
        break;
    case 0xC2:
        mana = Constants::MTG_COLOR_BLACK;
        break;
    case 0xD7:
        mana = Constants::MTG_COLOR_WHITE;
        break;
    case 0xD4: // T
    case 0xD8: // X
    case 0xD9: // Y
        mana = Constants::MTG_UNCOLORED;
        break;
    default:
        if (*(ch + 1) >= 0xB0 && *(ch + 1) <= 0xB9) mana = Constants::MTG_UNCOLORED;
    }
    return mana;
}

void WGBKFont::FormatText(string &s, vector<string>& output)
{
    while (s.length() > 0)
    {
        std::string::size_type len = 24;
        std::string::size_type cut = s.find_first_of("., \t)", 0);
        if (cut >= len || cut == string::npos)
        {
            // Fix for single byte character in some complex language like Chinese
            u8 * src = (u8 *) s.c_str();
            //if (neofont)
            {
                len = 0;
                std::string::size_type limit = 24;
                while (*src != 0)
                {
                    if (*src > 0x80)
                    { // Non-ASCII
                        if (len + 2 > limit && !(((*src & 0xF0) == 0xA0) && ((*(src + 1) & 0xF0) == 0xA0)))
                            break;
                        src += 2;
                        len += 2;
                    }
                    else
                    { // ASCII
                        if (*src == '/' && (*(src + 1) & 0xF0) == 0xA0)
                            limit += 3;
                        if (len + 1 > limit && (*src == '+' || *src == '-' || *src == '/'))
                            break;
                        src += 1;
                        len += 1;
                    }
                }
            }
            output.push_back(s.substr(0, len));
            if (s.length() > len)
                s = s.substr(len, s.length() - len);
            else
                s = "";
        }
        else
        {
            std::string::size_type newcut = cut;
            while (newcut < len && newcut != string::npos)
            {
                // neofont use space to separate one line
                u8 * src = (u8 *) s.c_str();
                //if (neofont && *src > 0x80)
                if (*src > 0x80)
                    break;
                cut = newcut;
                newcut = s.find_first_of("., \t)", newcut + 1);
            }
            output.push_back(s.substr(0, cut + 1));
            if (s.length() > cut + 1)
                s = s.substr(cut + 1, s.length() - cut - 1);
            else
                s = "";
        }
    }
}

int WUFont::GetCode(const u8 *ch, int *charLength) const
{
    int code = 0;

    // This assumes the string is valid. We could test for
    // validity by ensuring all trailing bytes are & 0xC0 == F0
    // and that no first byte is.

    // For a description of the binary representation, look at wikipedia://utf-8

    if ((*ch & 0xF8) == 0xF0)
    { // Four bytes
        *charLength = 4;
        code = ((*ch & 0x7) << 18) + ((*(ch + 1) & 0x3F) << 12) + ((*(ch + 2) & 0x3F) << 6) + ((*(ch + 3) * 0x3F));
    }
    else if ((*ch & 0xF0) == 0xE0)
    { // Three bytes
        *charLength = 3;
        code = ((*ch & 0xF) << 12) + ((*(ch + 1) & 0x3F) << 6) + ((*(ch + 2) & 0x3F));
    }
    else if ((*ch & 0xE0) == 0xC0)
    { // Two bytes
        *charLength = 2;
        code = ((*ch & 0x1F) << 6) + ((*(ch + 1) & 0x3F));
    }
    else
    {
        *charLength = 1;
        code = *ch;
    }
    return code;
}

int WUFont::GetMana(const u8 *ch) const
{
    /*
     * Reactivate the following code to be able to use
     * single-width characters too, if needed.
     **/
    /*
     switch (*ch)
     {
     case 0x67: return Constants::MTG_COLOR_GREEN;
     case 0x75: return Constants::MTG_COLOR_BLUE;
     case 0x72: return Constants::MTG_COLOR_RED;
     case 0x62: return Constants::MTG_COLOR_BLACK;
     case 0x77: return Constants::MTG_COLOR_WHITE;
     case 0x74:
     case 0x78:
     case 0x79: return Constants::MTG_UNCOLORED;
     default:
     if (*ch >= 0x30 && *ch <= 0x39)
     return Constants::MTG_UNCOLORED;
     }
     */
    if (*ch != 0xef || *(ch + 1) != 0xbc) return -1;
    ch += 2;
    switch (*ch)
    {
    case 0xa7: // G: 0xefbca7
        return Constants::MTG_COLOR_GREEN;
    case 0xb5: // U: 0xefbcb5
        return Constants::MTG_COLOR_BLUE;
    case 0xb2: // R: 0xefbcb2
        return Constants::MTG_COLOR_RED;
    case 0xa2: // B: 0xefbca2
        return Constants::MTG_COLOR_BLACK;
    case 0xb7: // W: 0xefbcb7
        return Constants::MTG_COLOR_WHITE;
    case 0xb4: // T: 0xefbcb4
    case 0xb8: // X: 0xefbcb8
    case 0xb9: // Y: 0xefbcb9
        return Constants::MTG_UNCOLORED;
    default:
        if (*ch >= 0x90 && *ch <= 0x99) return Constants::MTG_UNCOLORED;
    }
    return -1;
}

void WUFont::FormatText(string &s, vector<string>& output)
{
    std::string::size_type limit = 22; //28
    string delim("., \t)");

    while (s.length() > 0)
    {
        u8 * src = (u8 *) s.c_str();
        std::string::size_type len = 0;
        std::string::size_type ctr = 0;
        std::string::size_type lastcut = 0;
        u8 ch = 0;
        while ((ch = src[len]) != 0)
        {
            ctr += 2;
            if      ((ch & 0xF8) == 0xF0) len += 4;
            else if ((ch & 0xF0) == 0xE0) len += 3;
            else if ((ch & 0xE0) == 0xC0) len += 2;
            else // ASCII
            {
                ctr--; len += 1;
                if (delim.find(ch) != string::npos) lastcut = len;
                if (ctr > limit && lastcut) {len = lastcut; break;}
                //if (ch == ' ') break; // if we use ' ' to break a line.
            }
            if (ctr > limit) break;
        }
        output.push_back(s.substr(0, len));
        if (s.length() > len)
            s = s.substr(len, s.length() - len);
        else
            s = "";
    }
}
