#include "TextNode.h"

#include "Rectangle.h"
#include "FontsManager.h"
#include "IFontEngine.h"
#include "MeshPrimitives.h"
#include "FontItem.h"
#include "FontGlyph.h"
#include "SceneInfo.h"

#include "SceneText.h"
#include "SceneModel.h"
#include "Rectangle.h"

#include "Color.h"

#include "StringUtils.h"

TextNode::TextNode(const std::string& content, const Transform& transform)
    :   MaterialNode(transform)
{
    mTextData.mContent= content;
    setFillColor(Color(0, 0, 0));
    setHasStrokeColor(false);
}

TextNode::~TextNode()
{

}

std::unique_ptr<TextNode> TextNode::Create(const std::string& content, const Transform& transform)
{
    return std::make_unique<TextNode>(content, transform);
}

std::string TextNode::getFontLabel() const
{
    return {};
}

std::string TextNode::getContent() const
{
    return mTextData.mContent;
}

double TextNode::getWidth() const
{
    return mWidth;
}

double TextNode::getHeight() const
{
    return mHeight;
}

double TextNode::getContentWidth(SceneInfo* sceneInfo)
{
    if (mContentWidth == 1.0)
    {
        mContentWidth = sceneInfo->mFontsManager->getFontEngine()->getHorizontalAdvance(mTextData.mFont, mTextData.mContent);
    }
    return mContentWidth;
}

double TextNode::getContentHeight(SceneInfo*)
{
    if (mContentHeight == 1.0)
    {
        mContentHeight = mTextData.mFont.getSize();
    }
    return mContentHeight;
}

void TextNode::setWidth(double width)
{
    if (mWidth != width)
    {
        mTransformIsDirty = true;
        mWidth = width;
    }
}

void TextNode::setHeight(double height)
{
    if (mHeight != height)
    {
        mTransformIsDirty = true;
        mHeight = height;
    }
}

void TextNode::setContent(const std::string& content)
{
    if (mTextData.mContent != content)
    {
        mTextData.mContent = content;
        mContentIsDirty = true;
    }
}

void TextNode::setFont(const FontItem& font)
{
    if (mTextData.mFont != font)
    {
        mTextData.mFont = font;
        mContentIsDirty = true;
    }
}

SceneItem* TextNode::getSceneItem(std::size_t idx) const
{
    if (idx == 0)
    {
        return mTextItem.get();
    }
    else if (idx == 1)
    {
        if (mNodeBounds)
        {
            return mNodeBounds.get();
        }
        else
        {
            return mTextBounds.get();
        }
    }
    else if (idx == 2)
    {
        if (mTextBounds)
        {
            return mTextBounds.get();
        }
    }
    return nullptr;
}

std::size_t TextNode::getNumSceneItems() const
{
    auto count = 1;
    if (mNodeBounds)
    {
        count++;
    }
    if (mTextBounds)
    {
        count++;
    }
    return count;
}

void TextNode::updateLines(FontsManager* fontsManager)
{
    if (!fontsManager || !fontsManager->usesGlyphs())
    {
        return;
    }

    auto original_count = mTextData.mLines.size();
    std::vector<std::string> lines = StringUtils::toLines(mTextData.mContent);
    std::vector<std::string> output_lines;
    for (auto line : lines)
    {
        double running_width{0};
        std::string working_line;
        for (auto c : line)
        {
            auto glyph_advance = fontsManager->getGlyph(mTextData.mFont.getFaceName(), mTextData.mFont.getSize(), c)->getAdvanceX();
            if (false)
            //if (running_width + glyph_advance > mWidth)
            {
                output_lines.push_back(working_line);
                working_line = c;
                running_width = glyph_advance;
            }
            else
            {
                working_line += c;
                running_width += glyph_advance;
            }
        }
        output_lines.push_back(working_line);
        running_width = 0;
        (void)running_width;
    }
    mTextData.mLines = output_lines;

    if (original_count != mTextData.mLines.size())
    {
        mLinesAreDirty = true;
    }
}

void TextNode::setRenderNodeBounds(bool render)
{
    mRenderNodeBounds = render;
}

void TextNode::setRenderTextBounds(bool render)
{
    mRenderTextBounds = render;
}

void TextNode::update(SceneInfo* sceneInfo)
{
    if (!mTextItem)
    {
        mTextItem = std::make_unique<SceneText>();
        mTextItem->setName(mName + "_SceneText");

        getContentWidth(sceneInfo);
        getContentHeight(sceneInfo);

        if (mWidth == 1.0 && mHeight == 1.0)
        {
            mTextItem->setTextWidth(mContentWidth);
            mTextItem->setTextHeight(mContentHeight);
        }
        else
        {
            mTextItem->setTextWidth(mWidth);
            mTextItem->setTextHeight(mHeight);
        }

        if (mRenderNodeBounds)
        {
            auto rect = std::make_unique<ntk::Rectangle>(Point(0.0, 0.0), mWidth, mHeight);
            mNodeBounds = std::make_unique<SceneModel>();
            mNodeBounds->updateGeometry(std::move(rect));

            BasicMaterial bounds_material;
            bounds_material.setStrokeColor(Color(255, 0, 0));
            mNodeBounds->updateSolidMaterial(bounds_material);
        }

        if (mRenderTextBounds)
        {
            auto rect = std::make_unique<ntk::Rectangle>(Point(0.0, 0.0), mContentWidth, mContentHeight);
            mTextBounds = std::make_unique<SceneModel>();
            mTextBounds->updateGeometry(std::move(rect));

            BasicMaterial bounds_material;
            bounds_material.setStrokeColor(Color(0, 255, 0));
            mTextBounds->updateSolidMaterial(bounds_material);
        }
    }

    if (mTransformIsDirty || mContentIsDirty)
    {
        updateLines(sceneInfo->mFontsManager);
    }

    if (mContentIsDirty || mLinesAreDirty)
    {
        mTextItem->setTextData(mTextData);
        mContentIsDirty = false;
        mLinesAreDirty = false;
    }

    if (mTransformIsDirty)
    {
        if (mWidth == 1.0 && mHeight == 1.0)
        {
            mTextItem->setTextWidth(mContentWidth);
            mTextItem->setTextHeight(mContentHeight);
        }
        else
        {
            mTextItem->setTextWidth(mWidth);
            mTextItem->setTextHeight(mHeight);
        }
        mTransformIsDirty = false;
    }

    if (mMaterialIsDirty)
    {
        mTextItem->updateSolidMaterial(*mMaterial);
        mMaterialIsDirty = false;
    }
}
