//
// Created by Matthew on 2024/1/4.
//

#include "TextPaint.h"

namespace puttext
{
    void RasterCallback(const int y, const int count, const FT_Span * const spans, void * const user)
    {
        Spans *sptr = (Spans *)user;
        for (int i = 0; i < count; ++i)
            sptr->push_back(Span(spans[i].x, y, spans[i].len, spans[i].coverage));
    }

    void RenderSpans(FT_Library &library, FT_Outline * const outline, Spans *spans)
    {
        FT_Raster_Params params;
        memset(&params, 0, sizeof(params));
        params.flags = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT;
        params.gray_spans = RasterCallback;
        params.user = spans;

        FT_Outline_Render(library, outline, &params);
    }

    TextPaint::TextPaint()
    {
        library = NULL;
        face = NULL;

        FT_Init_FreeType(&library);
    }

    TextPaint::~TextPaint()
    {
        // Clean up the library
        FT_Done_FreeType(library);
    }

    bool TextPaint::LoadFont(const char* fontFilePath)
    {
        return FT_New_Face(library, fontFilePath, 0, &face) == 0;
    }

    bool TextPaint::DrawText(Pixel32 *pxl, const wchar_t* str, int size, const Pixel32 &fontCol, const Pixel32 &outlineCol, float outlineWidth)
    {
        // Set the size to use.
        if (FT_Set_Char_Size(face, size << 6, size << 6, 90, 90) != 0) {
            return false;
        }

        const wchar_t* ch = str;
        for (; *ch != 0; ch++)
        {
            // Load the glyph we are looking for.
            FT_UInt gindex = FT_Get_Char_Index(face, *ch);
            if (FT_Load_Glyph(face, gindex, FT_LOAD_NO_BITMAP) == 0)
            {
                // Need an outline for this to work.
                if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE)
                {
                    // Render the basic glyph to a span list.
                    Spans spans;
                    RenderSpans(library, &face->glyph->outline, &spans);

                    // Next we need the spans for the outline.
                    Spans outlineSpans;

                    // Set up a stroker.
                    FT_Stroker stroker;
                    FT_Stroker_New(library, &stroker);
                    FT_Stroker_Set(stroker,(int)(outlineWidth * 64),FT_STROKER_LINECAP_ROUND,FT_STROKER_LINEJOIN_ROUND,0);

                    FT_Glyph glyph;
                    if (FT_Get_Glyph(face->glyph, &glyph) == 0)
                    {
                        FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1);
                        // Again, this needs to be an outline to work.
                        if (glyph->format == FT_GLYPH_FORMAT_OUTLINE)
                        {
                            // Render the outline spans to the span list
                            FT_Outline *o = &reinterpret_cast<FT_OutlineGlyph>(glyph)->outline;
                            RenderSpans(library, o, &outlineSpans);
                        }

                        // Clean up afterwards.
                        FT_Stroker_Done(stroker);
                        FT_Done_Glyph(glyph);

                        // Now we need to put it all together.
                        if (!spans.empty())
                        {
                            // Figure out what the bounding rect is for both the span lists.
                            Rect rect(spans.front().x, spans.front().y, spans.front().x, spans.front().y);
                            for (Spans::iterator s = spans.begin(); s != spans.end(); ++s)
                            {
                                rect.Include(Vec2(s->x, s->y));
                                rect.Include(Vec2(s->x + s->width - 1, s->y));
                            }
                            for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s)
                            {
                                rect.Include(Vec2(s->x, s->y));
                                rect.Include(Vec2(s->x + s->width - 1, s->y));
                            }

#if 0
                            // This is unused in this test but you would need this to draw
                                // more than one glyph.
                                float bearingX = face->glyph->metrics.horiBearingX >> 6;
                                float bearingY = face->glyph->metrics.horiBearingY >> 6;
                                float advance = face->glyph->advance.x >> 6;
#endif

                            // Get some metrics of our image.
                            int imgWidth = rect.Width(),
                                    imgHeight = rect.Height(),
                                    imgSize = imgWidth * imgHeight;

                            // Allocate data for our image and clear it out to transparent.
                            // Pixel32 *pxl = new Pixel32[imgSize];
                            // memset(pxl, 0, sizeof(Pixel32) * imgSize);

                            // Loop over the outline spans and just draw them into the
                            // image.
                            for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s)
                                for (int w = 0; w < s->width; ++w)
                                {
                                    pxl[(int)((imgHeight - 1 - (s->y - rect.ymin)) * imgWidth
                                              + s->x - rect.xmin + w)] =
                                            Pixel32(outlineCol.r, outlineCol.g, outlineCol.b,
                                                    s->coverage);
                                }

                            // Then loop over the regular glyph spans and blend them into
                            // the image.
                            for (Spans::iterator s = spans.begin(); s != spans.end(); ++s)
                            {
                                for (int w = 0; w < s->width; ++w) {
                                    Pixel32 &dst =
                                            pxl[(int) (
                                                    (imgHeight - 1 - (s->y - rect.ymin)) * imgWidth
                                                    + s->x - rect.xmin + w)];
                                    Pixel32 src = Pixel32(fontCol.r, fontCol.g, fontCol.b,
                                                          s->coverage);
                                    dst.r = (int) (dst.r + ((src.r - dst.r) * src.a) / 255.0f);
                                    dst.g = (int) (dst.g + ((src.g - dst.g) * src.a) / 255.0f);
                                    dst.b = (int) (dst.b + ((src.b - dst.b) * src.a) / 255.0f);
                                    dst.a = MIN(255, dst.a + src.a);
                                }
                            }
                            // Dump the image to disk.
                            // WriteTGA(fileName, pxl, imgWidth, imgHeight);

                            // delete [] pxl;
                        }
                    }
                }
            }
        }
    }
#if 0
    int TextPaint::DrawText(Pixel32 *pxl, const char * fontFilePath)
    {
            // Dump out a single glyph to a tga.
            WriteGlyphAsTGA(pxl, 100,
                            Pixel32(255, 90, 30),
                            Pixel32(255, 255, 255),
                            3.0f);

            // Now that we are done it is safe to delete the memory.

        }
    }
#endif
} // namespace puttext