// Taken from: https://github.com/skyrpex/RichText

////////////////////////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////////////////////////
#include "RichText.hpp"

#include <cpp3ds/Graphics/Font.hpp>
#include <cpp3ds/Graphics/Rect.hpp>
#include <cpp3ds/Graphics/RenderTarget.hpp>

#include <cpp3ds/System/String.hpp>

namespace util3ds
{

////////////////////////////////////////////////////////////////////////////////
void RichText::Line::setCharacterSize(unsigned int size)
{
    for (cpp3ds::Text &text : m_texts)
        text.setCharacterSize(size);

    updateGeometry();
}


////////////////////////////////////////////////////////////////////////////////
void RichText::Line::setFont(const cpp3ds::Font &font)
{
    for (cpp3ds::Text &text : m_texts)
        text.setFont(font);

    updateGeometry();
}


////////////////////////////////////////////////////////////////////////////////
std::vector<cpp3ds::Text> &RichText::Line::getTexts() const
{
    return m_texts;
}


////////////////////////////////////////////////////////////////////////////////
void RichText::Line::appendText(cpp3ds::Text text)
{
    // Set text offset
    updateTextAndGeometry(text);

    // Push back
    m_texts.push_back(std::move(text));
}


////////////////////////////////////////////////////////////////////////////////
cpp3ds::FloatRect RichText::Line::getLocalBounds() const
{
    return m_bounds;
}


////////////////////////////////////////////////////////////////////////////////
cpp3ds::FloatRect RichText::Line::getGlobalBounds() const
{
    return getTransform().transformRect(getLocalBounds());
}


////////////////////////////////////////////////////////////////////////////////
void RichText::Line::draw(cpp3ds::RenderTarget &target, cpp3ds::RenderStates states) const
{
    states.transform *= getTransform();

    for (const cpp3ds::Text &text : m_texts)
        target.draw(text, states);
}


////////////////////////////////////////////////////////////////////////////////
void RichText::Line::updateGeometry() const
{
    m_bounds = cpp3ds::FloatRect();

    for (cpp3ds::Text &text : m_texts)
        updateTextAndGeometry(text);
}


////////////////////////////////////////////////////////////////////////////////
void RichText::Line::updateTextAndGeometry(cpp3ds::Text &text) const
{
    // Set text offset
    text.setPosition(m_bounds.width, 0.f);

    // Update bounds
    int lineSpacing = text.getFont()->getLineSpacing(text.getCharacterSize());
    m_bounds.height = std::max(m_bounds.height, static_cast<float>(lineSpacing));
    m_bounds.width += text.getGlobalBounds().width;
}


////////////////////////////////////////////////////////////////////////////////
RichText::RichText()
    : RichText(nullptr)
{

}


////////////////////////////////////////////////////////////////////////////////
RichText::RichText(const cpp3ds::Font &font)
    : RichText(&font)
{

}


////////////////////////////////////////////////////////////////////////////////
RichText & RichText::operator << (const cpp3ds::Color &color)
{
    m_currentColor = color;
    return *this;
}


////////////////////////////////////////////////////////////////////////////////
RichText & RichText::operator << (cpp3ds::Text::Style style)
{
    m_currentStyle = style;
    return *this;
}


////////////////////////////////////////////////////////////////////////////////
std::vector<cpp3ds::String> explode(const cpp3ds::String &string, cpp3ds::Uint32 delimiter) {
    if (string.isEmpty())
        return std::vector<cpp3ds::String>();

    // For each character in the string
    std::vector<cpp3ds::String> result;
    cpp3ds::String buffer;
    for (cpp3ds::Uint32 character : string) {
        // If we've hit the delimiter character
        if (character == delimiter) {
            // Add them to the result vector
            result.push_back(buffer);
            buffer.clear();
        } else {
            // Accumulate the next character into the sequence
            buffer += character;
        }
    }

    // Add to the result if buffer still has contents or if the last character is a delimiter
    if (!buffer.isEmpty() || string[string.getSize() - 1] == delimiter)
        result.push_back(buffer);

    return result;
}


////////////////////////////////////////////////////////////////////////////////
RichText & RichText::operator << (const cpp3ds::String &string)
{
    // Maybe skip
    if (string.isEmpty())
        return *this;

    // Explode into substrings
    std::vector<cpp3ds::String> subStrings = explode(string, '\n');

    // Append first substring using the last line
    auto it = subStrings.begin();
    if (it != subStrings.end()) {
        // If there isn't any line, just create it
        if (m_lines.empty())
            m_lines.resize(1);

        // Remove last line's height
        Line &line = m_lines.back();
        m_bounds.height -= line.getGlobalBounds().height;

        // Append text
        line.appendText(createText(*it));

        // Update bounds
        m_bounds.height += line.getGlobalBounds().height;
        m_bounds.width = std::max(m_bounds.width, line.getGlobalBounds().width);
    }

    // Append the rest of substrings as new lines
    while (++it != subStrings.end()) {
        Line line;
        line.setPosition(0.f, m_bounds.height);
        line.appendText(createText(*it));
        m_lines.push_back(std::move(line));

        // Update bounds
        m_bounds.height += line.getGlobalBounds().height;
        m_bounds.width = std::max(m_bounds.width, line.getGlobalBounds().width);
    }

    // Return
    return *this;
}


////////////////////////////////////////////////////////////////////////////////
void RichText::setCharacterSize(unsigned int size)
{
    // Maybe skip
    if (m_characterSize == size)
        return;

    // Update character size
    m_characterSize = size;

    // Set texts character size
    for (Line &line : m_lines)
        line.setCharacterSize(size);

    updateGeometry();
}


////////////////////////////////////////////////////////////////////////////////
void RichText::setFont(const cpp3ds::Font &font)
{
    // Maybe skip
    if (m_font == &font)
        return;

    // Update font
    m_font = &font;

    // Set texts font
    for (Line &line : m_lines)
        line.setFont(font);

    updateGeometry();
}


////////////////////////////////////////////////////////////////////////////////
void RichText::clear()
{
    // Clear texts
    m_lines.clear();

    // Reset bounds
    m_bounds = cpp3ds::FloatRect();
}


////////////////////////////////////////////////////////////////////////////////
void RichText::useSystemFont()
{
    m_useSystemFont = true;
    for (auto& line : m_lines)
        for (auto& text : line.getTexts())
            text.useSystemFont();
}


////////////////////////////////////////////////////////////////////////////////
const std::vector<RichText::Line> &RichText::getLines() const
{
    return m_lines;
}


////////////////////////////////////////////////////////////////////////////////
unsigned int RichText::getCharacterSize() const
{
    return m_characterSize;
}


////////////////////////////////////////////////////////////////////////////////
const cpp3ds::Font *RichText::getFont() const
{
    return m_font;
}


////////////////////////////////////////////////////////////
cpp3ds::FloatRect RichText::getLocalBounds() const
{
    return m_bounds;
}


////////////////////////////////////////////////////////////
cpp3ds::FloatRect RichText::getGlobalBounds() const
{
    return getTransform().transformRect(getLocalBounds());
}


////////////////////////////////////////////////////////////////////////////////
void RichText::draw(cpp3ds::RenderTarget& target, cpp3ds::RenderStates states) const
{
    states.transform *= getTransform();

    for (const Line &line : m_lines)
        target.draw(line, states);
}


////////////////////////////////////////////////////////////////////////////////
RichText::RichText(const cpp3ds::Font *font)
    : m_font(font),
      m_characterSize(30),
      m_currentColor(cpp3ds::Color::White),
      m_currentStyle(cpp3ds::Text::Regular),
      m_useSystemFont(false)
{

}


////////////////////////////////////////////////////////////////////////////////
cpp3ds::Text RichText::createText(const cpp3ds::String &string) const
{
    cpp3ds::Text text;
    text.setString(string);
    text.setFillColor(m_currentColor);
    text.setStyle(m_currentStyle);
    text.setCharacterSize(m_characterSize);
    if (m_useSystemFont)
        text.useSystemFont();
    else if (m_font)
        text.setFont(*m_font);

    return text;
}


////////////////////////////////////////////////////////////////////////////////
void RichText::updateGeometry() const
{
    m_bounds = cpp3ds::FloatRect();

    for (Line &line : m_lines) {
        line.setPosition(0.f, m_bounds.height);

        m_bounds.height += line.getGlobalBounds().height;
        m_bounds.width = std::max(m_bounds.width, line.getGlobalBounds().width);
    }
}


void RichText::setOpacity(cpp3ds::Uint8 alpha)
{
	for (auto& line : m_lines)
		line.setOpacity(alpha);
}


cpp3ds::Uint8 RichText::getOpacity() const
{
	if (m_lines.empty())
		return 0;

	return m_lines.front().getOpacity();
}


void RichText::Line::setOpacity(cpp3ds::Uint8 alpha)
{
	for (auto& text : m_texts) {
		cpp3ds::Color color = text.getFillColor();
		color.a = alpha;
		text.setFillColor(color);
	}
}


cpp3ds::Uint8 RichText::Line::getOpacity() const
{
	if (m_texts.empty())
		return 0;

	return m_texts.front().getFillColor().a;
}

} // namespace util3ds
