#include "Widget.h"

#include "RectangleNode.h"
#include "MouseEvent.h"
#include "KeyboardEvent.h"
#include "PaintEvent.h"

#include "AbstractVisualNode.h"
#include "TextNode.h"
#include "TransformNode.h"
#include "RootNode.h"
#include "ThemeManager.h"

#include "Window.h"
#include "FileLogger.h"

#include <algorithm>
#include <iterator>

Widget::Widget()
    : BoxGeometry(),
      mRootNode(std::make_unique<TransformNode>()),
      mChildren(),
      mBorder(Theme::Sys::Color::Outline),
      mBorderThickness(0),
      mBackground(Theme::Sys::Color::Primary),
      mVisible(true)
{
    mName = "Widget";
}

Widget::~Widget()
{

}

std::unique_ptr<Widget> Widget::Create()
{
    return std::make_unique<Widget>();
}

void Widget::addWidget(WidgetUPtr widget)
{
    widget->setParent(this);

    mPendingChildNodes.push_back(widget->getRootNode());
    mChildren.push_back(std::move(widget));
}

void Widget::setName(const std::string& name)
{
    mName = name;
}

const std::string& Widget::getName() const
{
    return mName;
}


TransformNode* Widget::getRootNode() const
{
    return mRootNode.get();
}

void Widget::setBackground(Theme::Sys::Color token)
{
    if (mBackground != token)
    {
        mBackground = token;
        mMaterialDirty = true;
    }
}

void Widget::setBackgroundTone(Theme::Sys::Color token)
{
    if (mBackgroundTone != token)
    {
        mBackgroundTone = token;
        mMaterialDirty = true;
    }
}

void Widget::setElevation(Theme::Sys::Elevation elevation)
{
    if (mElevation != elevation)
    {
        mElevation = elevation;
        mMaterialDirty = true;
    }
}

void Widget::setBackgroundOpacity(float opacity)
{
    if (mBackgroundOpacity != opacity)
    {
        mBackgroundOpacity = opacity;
        mMaterialDirty = true;
    }
}

void Widget::setOutlineThickness(double thickness)
{
    if (mBorderThickness != thickness)
    {
        mBorderThickness = thickness;
        mMaterialDirty = true;
    }
}

void Widget::setOutline(Theme::Sys::Color token)
{
    if (mBorder != token)
    {
        mBorder = token;
        mMaterialDirty = true;
    }
}

void Widget::setRadius(double radius)
{
    if (mRadius != radius)
    {
        mRadius = radius;
        mGeometryDirty = true;
    }
}

void Widget::setVisible(bool visible)
{
    if (mVisible != visible)
    {
        mVisibilityDirty = true;

        mVisible = visible;

        for (auto& child : mChildren)
        {
            child->setVisible(mVisible);
        }
    }
}

bool Widget::isDirty() const
{
    return mStateDirty || mGeometryDirty || mTransformDirty || mMaterialDirty || mVisibilityDirty || mPendingChildNodes.size() > 0;
}

bool Widget::needsUpdate() const
{
    if (isDirty())
    {
        return true;
    }
    for(auto& child: mChildren)
    {
        if (child->needsUpdate())
        {
            return true;
        }
    }
    return false;
}

void Widget::updateState()
{

}

void Widget::doPaint(const PaintEvent* event)
{
    updateBackground(event);
}

void Widget::onPaintEvent(const PaintEvent* event)
{
    if (!needsUpdate())
    {
        return;
    }

    if (isDirty())
    {
        mRootNode->setName(mName + "_RootNode");

        if (mStateDirty)
        {
            updateState();
            mStateDirty = false;
        }

        doPaint(event);
        mGeometryDirty = false;
        mMaterialDirty = false;
        mTransformDirty = false;

        if (mVisibilityDirty)
        {
            mRootNode->setIsVisible(mVisible);
            mVisibilityDirty = false;
        }     
    }

    for (auto node : mPendingChildNodes)
    {
        mRootNode->addChild(node);
    }
    mPendingChildNodes.clear();

    updateChildLocations();

    for(auto& child: mChildren)
    {
        child->onPaintEvent(event);
    }
}

void Widget::updateChildLocations()
{
    for(auto& child: mChildren)
    {
        child->setBounds(mSize.mWidth, mSize.mHeight);
        child->setLocation(mLocation);
    }
}

bool Widget::onKeyboardEvent(const KeyboardEvent* event)
{
    bool inChild = false;
    for(const auto& child : mChildren)
    {
        if(child->onKeyboardEvent(event))
        {
            inChild = true;
            break;
        }
    }

    if(!inChild)
    {
        return onMyKeyboardEvent(event);
    }
    return true;
}

bool Widget::onMyKeyboardEvent(const KeyboardEvent*)
{
    return false;
}

bool Widget::onMouseEvent(const MouseEvent* event)
{
    bool inChild = false;
    for(const auto& child : mChildren)
    {
        if(child->onMouseEvent(event))
        {
            inChild = true;
            break;
        }
    }
    if(!inChild)
    {
        if(mVisible && contains(event->getClientLocation()))
        {
            onMyMouseEvent(event);
            return true;
        }
        else
        {
            return false;
        }
    }
    return true;
}

bool Widget::onFocusInEvent(const FocusEvent* event)
{
    bool inChild = false;
    for (const auto& child : mChildren)
    {
        if (child->onFocusInEvent(event))
        {
            inChild = true;
            break;
        }
    }
    if (inChild)
    {
        return true;
    }
    else if (mAcceptsFocus)
    {
        setFocus(true);
        return true;
    }
    return false;
}

void Widget::setFocus(bool hasFocus)
{
    if (mHasFocus != hasFocus)
    {
        mHasFocus = hasFocus;
        mStateDirty = true;
    }
}

void Widget::onMyMouseEvent(const MouseEvent*)
{

}

void Widget::createOrUpdateGeometry()
{
    const auto deltaX = mSize.mWidth - mMargin.mLeft - mMargin.mRight;
    const auto deltaY = mSize.mHeight - mMargin.mTop - mMargin.mBottom;
    if (!mBackgroundNode)
    {
        Point margin_offset(mMargin.mLeft, mMargin.mTop);

        mBackgroundNode = std::make_unique<RectangleNode>(Transform(margin_offset), deltaX, deltaY);
        mBackgroundNode->setRadius(mRadius);

        mBackgroundNode->setName(mName + "_BackgroundNode");
        mRootNode->addChild(mBackgroundNode.get());
    }
    else
    {
        mBackgroundNode->setWidth(deltaX);
        mBackgroundNode->setHeight(deltaY);
        mBackgroundNode->setRadius(mRadius);
    }
}

void Widget::updateTransform()
{
    mRootNode->setTransform({ mLocation });
}

void Widget::updateMaterial(const PaintEvent* event)
{
    if (mBackground != Theme::Sys::Color::None)
    {
        //if (mBackgroundTone == Theme::Sys::Color::None || mElevation == Theme::Sys::Elevation::Level_0)
        //{
            auto background_color = event->getThemesManager()->getColor(mBackground);
            background_color.setAlpha(mBackgroundOpacity);
            mBackgroundNode->setFillColor(background_color);
        //}
        //else
        //{
            //event->getThemesManager()->getColor(mBackground);
        //}
    }
    else
    {
        mBackgroundNode->setHasFillColor(false);
    }

    if (mBorder != Theme::Sys::Color::None)
    {
        mBackgroundNode->setStrokeColor(event->getThemesManager()->getColor(mBorder));
        mBackgroundNode->setStrokeThickness(mBorderThickness);
    }
    else
    {
        mBackgroundNode->setHasStrokeColor(false);
    }
}

void Widget::updateBackground(const PaintEvent* event)
{
    if (mGeometryDirty)
    {
        createOrUpdateGeometry();
    }

    if (mTransformDirty)
    {
        updateTransform();
    }

    if (mMaterialDirty)
    {
        updateMaterial(event);
    }

    if (mVisibilityDirty)
    {
        mBackgroundNode->setIsVisible(mVisible);
    }
}

mt::Window* Widget::getTopLevelWindow() const
{
    if(mWindow)
    {
        return mWindow;
    }

    auto lastParent = mParent;
    auto nextParent = mParent;
    while(nextParent)
    {
        auto temp = lastParent;
        lastParent = nextParent;
        nextParent = temp->getParent();
        if (nextParent)
        {
            //std::cout << "Next is " << nextParent->getName() << std::endl;
        }
        else
        {
            //std::cout << "no next" << std::endl;
        }
    }
    if (lastParent)
    {
        return lastParent->getTopLevelWindow();
    }
    else
    {
        return nullptr;
    }
}

Widget* Widget::getParent() const
{
    return mParent;
}

mt::Window* Widget::getWindow() const
{
    return mWindow;
}

void Widget::setParent(Widget* parent)
{
    mParent = parent;
}

void Widget::setWindow(mt::Window* window)
{
    mWindow = window;
}
