/*Copyright (C) 2010-2023 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Hikou no mizu is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Text.hpp"
#include "Graphics/Resources/Font.hpp"
#include "Graphics/Resources/Glyph.hpp"

#include "Tools/Utf8.hpp"

Text::Text() : Drawable(), m_font(nullptr),
m_width(0.f), m_height(0.f), m_originHeight(0.f)
{

}

Text::Text(const Utf8::utf8_string &text, Font &font) :
Drawable(), m_font(&font), m_text(text)
{
    loadGlyphs();
}

void Text::draw()
{
    if (!m_glyphs.empty())
    {
        //Update matrix
        Drawable::pushMatrix();
        Drawable::updateMatrix();

        glColor4ub(m_color.red, m_color.green, m_color.blue, m_color.alpha);
        glTranslatef(0.f, m_originHeight, 0.f);

        for (const GlyphKerning &glyphData : m_glyphs)
        {
            const Glyph &glyph = glyphData.glyph;

            //Kerning.
            glTranslatef(glyphData.kerning, 0.f, 0.f);
            glyph.draw();

            //Advance
            glTranslatef(glyph.getAdvance().x, glyph.getAdvance().y, 0.f);
        }

        glColor4ub(255, 255, 255, 255); //Restore default color

        Drawable::popMatrix();
    }
}

void Text::setFont(Font &font)
{
    m_font = &font;
    loadGlyphs();
}

const Font *Text::getFont() const
{
    return m_font;
}

void Text::setText(const Utf8::utf8_string &text)
{
    m_text = text;
    loadGlyphs();
}

const std::string &Text::getText() const
{
    return m_text.get();
}

void Text::setColor(const Color &color)
{
    m_color = color;
}

float Text::getWidth() const
{
    return m_width;
}

float Text::getHeight() const
{
    return m_height;
}

float Text::getOriginHeight() const
{
    return m_originHeight;
}

BoundingBox Text::getBoundingBox(std::size_t ix_begin, std::size_t ix_end) const
{
    if (ix_begin >= ix_end || ix_end > m_glyphs.size())
        return BoundingBox();

    BoundingBox boundingBox;
    for (std::size_t ix = 0; ix < ix_end; ix++)
    {
        const Glyph &glyph = m_glyphs[ix].glyph;
        const Vector &bearing = glyph.getTopLeft();

        //Update left / width bounding box
        const float kerning = m_glyphs[ix].kerning;
        const float advance = glyph.getAdvance().x;

        if (ix < ix_begin) //Not reached the first (nor last) glyph yet
        {
            boundingBox.left += kerning + advance;
            boundingBox.right += kerning + advance;
        }
        else
        {
            if (ix == ix_begin) //Reach the first glyph, finalize .left
                boundingBox.left += kerning + bearing.x;

            //Increment .right based on whether the last glyph is considered
            //(with an exception for width = 0 character, typically spaces)
            if (ix + 1 < ix_end || glyph.getWidth() == 0)
                boundingBox.right += kerning + advance;
            else
                boundingBox.right += kerning + bearing.x +
                                     static_cast<float>(glyph.getWidth());
        }

        //Update top / bottom bounding box for the glyphs in range
        if (ix >= ix_begin)
        {
            if (-bearing.y < boundingBox.top)
                boundingBox.top = -bearing.y;

            const float glyphHeight = static_cast<float>(glyph.getHeight());
            if (glyphHeight - bearing.y > boundingBox.bottom)
                boundingBox.bottom = glyphHeight - bearing.y;
        }
    }

    return boundingBox;
}

void Text::loadGlyphs()
{
    if (m_font != nullptr)
    {
        //Clear current glyphs
        m_glyphs.clear();

        std::vector<unsigned long> utf32CodeUnits = m_text.get_utf32();
        m_glyphs.reserve(utf32CodeUnits.size());

        unsigned long lastChar = 0;
        for (unsigned long utf32char : utf32CodeUnits)
        {
            const Glyph *glyph = m_font->getGlyph(utf32char);
            if (glyph == nullptr)
                continue;

            if (lastChar == 0) //First glyph: No kerning value to compute
                m_glyphs.emplace_back((*glyph), 0.f);
            else
            {
                m_glyphs.emplace_back((*glyph), m_font->getKerning(lastChar,
                                                                utf32char));
            }

            lastChar = utf32char;
        }

        //Compute size
        BoundingBox boundingBox = getBoundingBox(0, m_glyphs.size());

        //Add the left space twice to the width for proper centering of texts,
        //i.e., (boundingBox.right - boundingBox.left) + 2 * boundingBox.left
        m_width = boundingBox.right + boundingBox.left;
        m_height = boundingBox.bottom - boundingBox.top;
        m_originHeight = -boundingBox.top;
    }
}
