#include "gx/font/TextBlock.hpp"
#include "gx/Coordinate.hpp"
#include "gx/font/GxuFont.hpp"
#include <storm/Error.hpp>
#include <storm/Memory.hpp>

static TSHashTable<FONTHASHOBJ, HASHKEY_STR> s_fontHash;

FONTHASHOBJ::~FONTHASHOBJ() {
    if (this->font) {
        GxuFontDestroyFont(this->font);
    }
}

TEXTBLOCK::~TEXTBLOCK() {
    GxuFontDestroyString(this->string);
}

uint32_t ConvertStringFlags(uint32_t flags) {
    uint32_t convertedFlags = 0x0;

    if (flags & 0x100) {
        convertedFlags |= 0x1;
    }

    if (flags & 0x200) {
        convertedFlags |= 0x4;
    }

    if (flags & 0x400) {
        convertedFlags |= 0x8;
    }

    if (flags & 0x800) {
        convertedFlags |= 0x10;
    }

    if (flags & 0x40) {
        convertedFlags |= 0x2;
    }

    if (flags & 0x1000) {
        convertedFlags |= 0x40;
    }

    if (flags & 0x2000) {
        convertedFlags |= 0x100;
    }

    if (flags & 0x4000) {
        convertedFlags |= 0x200;
    }

    if (flags & 0x8000) {
        convertedFlags |= 0x400;
    }

    if (flags & 0x80) {
        convertedFlags |= 0x800;
    }

    if (flags & 0x10000) {
        convertedFlags |= 0x1000;
    }

    if (flags & 0x20000) {
        convertedFlags |= 0x2000;
    }

    return convertedFlags;
}

void TextBlockAddShadow(HTEXTBLOCK string, CImVector color, const C2Vector& offset) {
    STORM_ASSERT(string);

    C2Vector ndcOffset;
    DDCToNDC(offset.x, offset.y, &ndcOffset.x, &ndcOffset.y);

    GxuFontAddShadow(TextBlockGetStringPtr(string), color, ndcOffset);
}

HTEXTBLOCK TextBlockCreate(HTEXTFONT fontHandle, const char* text, const CImVector& color, const C3Vector& position, float fontHeight, float blockWidth, float blockHeight, uint32_t flags, float charSpacing, float lineSpacing, float scale) {
    STORM_VALIDATE_BEGIN;
    STORM_VALIDATE(fontHandle);
    STORM_VALIDATE(text);
    STORM_VALIDATE_END;

    auto textBlock = STORM_NEW(TEXTBLOCK)();

    C3Vector ndcPosition = { 0.0f, 0.0f, position.z };
    DDCToNDC(position.x, position.y, &ndcPosition.x, &ndcPosition.y);

    EGxFontHJusts hjust = GxHJ_Center;

    if (flags & 0x4) {
        hjust = GxHJ_Right;
    } else if (!(flags & 0x2) && flags & 0x1) {
        hjust = GxHJ_Left;
    }

    EGxFontVJusts vjust = GxVJ_Middle;

    if (flags & 0x8) {
        vjust = GxVJ_Top;
    } else if (flags & 0x20) {
        vjust = GxVJ_Bottom;
    }

    uint32_t stringFlags = ConvertStringFlags(flags);

    float ndcCharSpacing = DDCToNDCWidth(charSpacing);
    float ndcLineSpacing = DDCToNDCHeight(lineSpacing);
    float ndcBlockHeight = DDCToNDCHeight(blockHeight);
    float ndcBlockWidth = DDCToNDCWidth(blockWidth);
    float ndcFontHeight = DDCToNDCHeight(fontHeight);

    GxuFontCreateString(
        reinterpret_cast<FONTHASHOBJ*>(fontHandle)->font,
        text,
        ndcFontHeight,
        ndcPosition,
        ndcBlockWidth,
        ndcBlockHeight,
        ndcLineSpacing,
        textBlock->string,
        vjust,
        hjust,
        stringFlags,
        color,
        ndcCharSpacing,
        scale
    );

    return HandleCreate(textBlock);
}

HTEXTFONT TextBlockGenerateFont(const char* fontName, uint32_t fontFlags, float fontHeight) {
    STORM_ASSERT(fontName);
    STORM_ASSERT(*fontName);

    float fontHeightNDC = DDCToNDCHeight(fontHeight);

    if (fontHeightNDC >= 1.0) {
        fontHeightNDC = 1.0;
    }

    char hashKey[276];

    SStrPrintf(hashKey, 276, "%s-%d-%f", fontName, fontFlags, fontHeightNDC);

    auto v7 = s_fontHash.Ptr(hashKey);

    if (v7) {
        return HandleCreate(v7);
    }

    v7 = s_fontHash.New(hashKey, 0, 0);

    uint32_t v14 = 0;

    if (fontFlags & FONT_OUTLINE) {
        v14 |= 0x1;
    }

    if (fontFlags & FONT_THICKOUTLINE) {
        v14 |= 0x8;
    }

    if (fontFlags & FONT_MONOCHROME) {
        v14 |= 0x2;
    }

    if (GxuFontCreateFont(fontName, fontHeightNDC, v7->font, v14)) {
        return HandleCreate(v7);
    } else {
        // TODO
        // sub_723270(v12);
        // (s_fontHash)(&s_fontHash, v12);

        return nullptr;
    }
}

uint32_t TextBlockGetFontFlags(HTEXTFONT fontHandle) {
    STORM_ASSERT(fontHandle);

    uint32_t gxFlags = GxuFontGetFontFlags(TextBlockGetFontPtr(fontHandle));
    uint32_t flags = 0;

    if (gxFlags & 0x1) {
        flags |= 0x1;
    }

    if (gxFlags & 0x2) {
        flags |= 0x2;
    }

    if (gxFlags & 0x8) {
        flags |= 0x4;
    }

    return flags;
}

const char* TextBlockGetFontName(HTEXTFONT fontHandle) {
    STORM_ASSERT(fontHandle);

    return GxuFontGetFontName(reinterpret_cast<FONTHASHOBJ*>(fontHandle)->font);
}

CGxFont* TextBlockGetFontPtr(HTEXTFONT fontHandle) {
    STORM_ASSERT(fontHandle);

    return reinterpret_cast<FONTHASHOBJ*>(fontHandle)->font;
}

uint32_t TextBlockGetMaxCharsWithinWidth(HTEXTFONT fontHandle, const char* text, float height, float maxWidth, uint32_t lineBytes, float* extent, float a7, float scale, float a9, uint32_t flags) {
    STORM_ASSERT(fontHandle);
    STORM_ASSERT(text);

    return GxuFontGetMaxCharsWithinWidth(
        TextBlockGetFontPtr(fontHandle),
        text,
        DDCToNDCHeight(height),
        DDCToNDCWidth(maxWidth),
        lineBytes,
        extent,
        DDCToNDCWidth(a7),
        scale,
        DDCToNDCWidth(a9),
        ConvertStringFlags(flags)
    );
}

uint32_t TextBlockGetMaxCharsWithinWidthAndHeight(HTEXTFONT fontHandle, const char* text, float height, float maxWidth, float maxHeight, uint32_t lineBytes, float a7, float scale, float a9, uint32_t flags) {
    STORM_ASSERT(fontHandle);
    STORM_ASSERT(text);

    return GxuFontGetMaxCharsWithinWidthAndHeight(
        TextBlockGetFontPtr(fontHandle),
        text,
        DDCToNDCHeight(height),
        DDCToNDCWidth(maxWidth),
        DDCToNDCHeight(maxHeight),
        lineBytes,
        DDCToNDCWidth(a7),
        scale,
        DDCToNDCHeight(a9),
        ConvertStringFlags(flags)
    );
}

CGxString* TextBlockGetStringPtr(HTEXTBLOCK stringHandle) {
    STORM_ASSERT(stringHandle);

    return reinterpret_cast<TEXTBLOCK*>(stringHandle)->string;
}

void TextBlockGetTextExtent(HTEXTFONT fontHandle, const char* text, uint32_t numChars, float fontHeight, float* extent, float a6, float scale, float a8, uint32_t flags) {
    STORM_ASSERT(fontHandle);
    STORM_ASSERT(text);
    STORM_ASSERT(extent);

    *extent = 0.0f;

    GxuFontGetTextExtent(
        TextBlockGetFontPtr(fontHandle),
        text,
        numChars,
        DDCToNDCHeight(fontHeight),
        extent,
        DDCToNDCWidth(a6),
        scale,
        DDCToNDCWidth(a8),
        ConvertStringFlags(flags)
    );

    NDCToDDC(*extent, 0.0f, extent, nullptr);
}

float TextBlockGetWrappedTextHeight(HTEXTFONT fontHandle, const char* text, float a3, float a4, const C2Vector& a5, float a6, float a7, uint32_t flags) {
    STORM_ASSERT(fontHandle);
    STORM_ASSERT(text);

    float shadowWidth;
    float shadowHeight;
    DDCToNDC(a5.x, a5.y, &shadowWidth, &shadowHeight);
    C2Vector shadowSize  = { shadowWidth, shadowHeight };

    float height = GxuFontGetWrappedTextHeight(
        TextBlockGetFontPtr(fontHandle),
        text,
        DDCToNDCHeight(a3),
        DDCToNDCWidth(a4),
        shadowSize,
        a6,
        DDCToNDCHeight(a7),
        ConvertStringFlags(flags)
    );

    return NDCToDDCHeight(height);
}

void TextBlockSetStringPos(HTEXTBLOCK stringHandle, const C3Vector& pos) {
    STORM_ASSERT(stringHandle);

    C3Vector ndcPos = { 0.0f, 0.0f, pos.z };
    DDCToNDC(pos.x, pos.y, &ndcPos.x, &ndcPos.y);
    GxuFontSetStringPosition(TextBlockGetStringPtr(stringHandle), ndcPos);
}

void TextBlockUpdateColor(HTEXTBLOCK stringHandle, const CImVector& color) {
    STORM_ASSERT(stringHandle);

    GxuFontSetStringColor(TextBlockGetStringPtr(stringHandle), color);
}
