#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 "Window.h"

#include <algorithm>
#include <iterator>
#include <iostream>

Widget::Widget()
    : mLocation(DiscretePoint(0, 0)),
      mSize({100, 0, 0, 100, 0, 0}),
      mPadding(),
      mMargin(),
      mRootNode(std::make_unique<TransformNode>()),
      mChildren(),
      mBorderThickness(0),
      mBackgroundColor(Color(255, 255, 255)),
      mBorderColor(Color(0, 0, 0)),
      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));
}

Widget::BoundedSize Widget::getSize() const
{
    return mSize;
}

const DiscretePoint& Widget::getLocation() const
{
    return mLocation;
}

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

void Widget::setSize(const BoundedSize& size)
{
    if (size != mSize)
    {
        mSize = size;
        mTransformDirty = true;
    }
}

void Widget::setMargin(unsigned margin)
{
    setMargin({margin, margin, margin, margin});
}

void Widget::setMargin(const BoundaryOffset& margin)
{
    if (margin != mMargin)
    {
        mMargin = margin;
        mTransformDirty = true;
    }
}

void Widget::setPadding(unsigned padding)
{
    setPadding({padding, padding, padding, padding});
}

void Widget::setPadding(const BoundaryOffset& padding)
{
    if (padding != mPadding)
    {
        mPadding = padding;
        mTransformDirty = true;
    }
}

void Widget::setBounds(unsigned width, unsigned height)
{
    if (mSize.mMaxWidth > 0 && width > mSize.mMaxWidth)
    {
        width = mSize.mMaxWidth;
    }

    if (mSize.mMaxHeight > 0 && height > mSize.mMaxHeight)
    {
        height = mSize.mMaxHeight;
    }

    if (width != mSize.mWidth || height != mSize.mHeight)
    {
        mTransformDirty = true;
        mSize.mWidth = width;
        mSize.mHeight = height;
    }
}

void Widget::setBackgroundColor(const Color& color)
{
    if (mBackgroundColor != color)
    {
        mBackgroundColor = color;
        mMaterialDirty = true;
    }
}

void Widget::setLocation(const DiscretePoint& loc)
{
    if (mLocation != loc)
    {
        mLocation = loc;
        mTransformDirty = 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 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::setMaxWidth(unsigned maxWidth)
{
    if (mSize.mMaxWidth != maxWidth)
    {
        mTransformDirty = true;
        mSize.mMaxWidth = maxWidth;
    }
}

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

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

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

        doPaint(event);

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

        mTransformDirty = false;
        mMaterialDirty = false;
        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::contains(const DiscretePoint& loc) const
{
    if(loc.GetX() < mLocation.GetX())
    {
        return false;
    }
    if(loc.GetX() > mLocation.GetX() + mSize.mWidth)
    {
        return false;
    }
    if(loc.GetY() > mLocation.GetY() + mSize.mHeight)
    {
        return false;
    }
    if(loc.GetY() < mLocation.GetY())
    {
        return false;
    }
    return true;
}

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* event)
{
    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;
}

void Widget::onMyMouseEvent(const MouseEvent* event)
{

}

void Widget::updateBackground(const PaintEvent* event)
{
    unsigned locX = mLocation.GetX() + mMargin.mLeft;
    unsigned locY = mLocation.GetY() + mMargin.mTop;
    unsigned deltaX = mSize.mWidth - mMargin.mLeft - mMargin.mRight;
    unsigned deltaY = mSize.mHeight - mMargin.mTop - mMargin.mBottom;

    if (!mBackgroundNode)
    {
        mBackgroundNode = std::make_unique<RectangleNode>(DiscretePoint(locX, locY), deltaX, deltaY);
        mBackgroundNode->setName(mName + "_BackgroundNode");
        mRootNode->addChild(mBackgroundNode.get());
    }

    if (mTransformDirty)
    {
        mBackgroundNode->setWidth(deltaX);
        mBackgroundNode->setHeight(deltaY);
        mBackgroundNode->setLocation(DiscretePoint(locX, locY));
    }

    if (mMaterialDirty)
    {
        mBackgroundNode->setFillColor(mBackgroundColor);
    }

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

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

    std::cout << "I am " << getName() << std::endl;

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

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;
}
