
#include "core/widget.h"
#include "core/timer.h"
#include "core/colorf.h"
#include <stdexcept>

Widget::Widget(Widget* parent, WidgetType type) : Type(type)
{
	if (type != WidgetType::Child)
	{
		DispWindow = DisplayWindow::Create(this);
		DispCanvas = Canvas::create(DispWindow.get());
	}

	SetParent(parent);
}

Widget::~Widget()
{
	while (LastChildObj)
		delete LastChildObj;

	while (FirstTimerObj)
		delete FirstTimerObj;

	DetachFromParent();
}

void Widget::SetParent(Widget* newParent)
{
	if (ParentObj != newParent)
	{
		if (ParentObj)
			DetachFromParent();

		if (newParent)
		{
			PrevSiblingObj = newParent->LastChildObj;
			if (PrevSiblingObj) PrevSiblingObj->NextSiblingObj = this;
			newParent->LastChildObj = this;
			if (!newParent->FirstChildObj) newParent->FirstChildObj = this;
			ParentObj = newParent;
		}
	}
}

void Widget::MoveBefore(Widget* sibling)
{
	if (sibling && sibling->ParentObj != ParentObj) throw std::runtime_error("Invalid sibling passed to Widget.MoveBefore");
	if (!ParentObj) throw std::runtime_error("Widget must have a parent before it can be moved");

	if (NextSiblingObj != sibling)
	{
		Widget* p = ParentObj;
		DetachFromParent();

		ParentObj = p;
		if (sibling)
		{
			NextSiblingObj = sibling;
			PrevSiblingObj = sibling->PrevSiblingObj;
			sibling->PrevSiblingObj = this;
			if (PrevSiblingObj) PrevSiblingObj->NextSiblingObj = this;
			if (ParentObj->FirstChildObj == sibling) ParentObj->FirstChildObj = this;
		}
		else
		{
			PrevSiblingObj = ParentObj->LastChildObj;
			if (PrevSiblingObj) PrevSiblingObj->NextSiblingObj = this;
			ParentObj->LastChildObj = this;
			if (!ParentObj->FirstChildObj) ParentObj->FirstChildObj = this;
		}
	}
}

void Widget::DetachFromParent()
{
	for (Widget* cur = ParentObj; cur; cur = cur->ParentObj)
	{
		if (cur->FocusWidget == this)
			cur->FocusWidget = nullptr;
		if (cur->CaptureWidget == this)
			cur->CaptureWidget = nullptr;
		if (cur->HoverWidget == this)
			cur->HoverWidget = nullptr;

		if (cur->DispWindow)
			break;
	}

	if (PrevSiblingObj)
		PrevSiblingObj->NextSiblingObj = NextSiblingObj;
	if (NextSiblingObj)
		NextSiblingObj->PrevSiblingObj = PrevSiblingObj;
	if (ParentObj)
	{
		if (ParentObj->FirstChildObj == this)
			ParentObj->FirstChildObj = NextSiblingObj;
		if (ParentObj->LastChildObj == this)
			ParentObj->LastChildObj = PrevSiblingObj;
	}
	PrevSiblingObj = nullptr;
	NextSiblingObj = nullptr;
	ParentObj = nullptr;
}

std::string Widget::GetWindowTitle() const
{
	return WindowTitle;
}

void Widget::SetWindowTitle(const std::string& text)
{
	if (WindowTitle != text)
	{
		WindowTitle = text;
		if (DispWindow)
			DispWindow->SetWindowTitle(WindowTitle);
	}
}

Size Widget::GetSize() const
{
	return ContentGeometry.size();
}

Rect Widget::GetFrameGeometry() const
{
	if (Type == WidgetType::Child)
	{
		return FrameGeometry;
	}
	else
	{
		return DispWindow->GetWindowFrame();
	}
}

void Widget::SetNoncontentSizes(double left, double top, double right, double bottom)
{
	Noncontent.Left = left;
	Noncontent.Top = top;
	Noncontent.Right = right;
	Noncontent.Bottom = bottom;
}

void Widget::SetFrameGeometry(const Rect& geometry)
{
	if (Type == WidgetType::Child)
	{
		FrameGeometry = geometry;
		double left = FrameGeometry.left() + Noncontent.Left;
		double top = FrameGeometry.top() + Noncontent.Top;
		double right = FrameGeometry.right() - Noncontent.Right;
		double bottom = FrameGeometry.bottom() - Noncontent.Bottom;
		left = std::min(left, FrameGeometry.right());
		top = std::min(top, FrameGeometry.bottom());
		right = std::max(right, FrameGeometry.left());
		bottom = std::max(bottom, FrameGeometry.top());
		ContentGeometry = Rect::ltrb(left, top, right, bottom);
		OnGeometryChanged();
	}
	else
	{
		DispWindow->SetWindowFrame(geometry);
	}
}

void Widget::Show()
{
	if (Type != WidgetType::Child)
	{
		DispWindow->Show();
	}
	else if (HiddenFlag)
	{
		HiddenFlag = false;
		Update();
	}
}

void Widget::ShowFullscreen()
{
	if (Type != WidgetType::Child)
	{
		DispWindow->ShowFullscreen();
	}
}

void Widget::ShowMaximized()
{
	if (Type != WidgetType::Child)
	{
		DispWindow->ShowMaximized();
	}
}

void Widget::ShowMinimized()
{
	if (Type != WidgetType::Child)
	{
		DispWindow->ShowMinimized();
	}
}

void Widget::ShowNormal()
{
	if (Type != WidgetType::Child)
	{
		DispWindow->ShowNormal();
	}
}

void Widget::Hide()
{
	if (Type != WidgetType::Child)
	{
		if (DispWindow)
			DispWindow->Hide();
	}
	else if (!HiddenFlag)
	{
		HiddenFlag = true;
		Update();
	}
}

void Widget::ActivateWindow()
{
	if (Type != WidgetType::Child)
	{
		DispWindow->Activate();
	}
}

void Widget::Close()
{
	OnClose();
}

void Widget::SetWindowBackground(const Colorf& color)
{
	Widget* w = Window();
	if (w && w->WindowBackground != color)
	{
		w->WindowBackground = color;
		Update();
	}
}

void Widget::SetWindowBorderColor(const Colorf& color)
{
	Widget* w = Window();
	if (w)
	{
		w->DispWindow->SetBorderColor(color.toBgra8());
		w->DispWindow->Update();
	}
}

void Widget::SetWindowCaptionColor(const Colorf& color)
{
	Widget* w = Window();
	if (w)
	{
		w->DispWindow->SetCaptionColor(color.toBgra8());
		w->DispWindow->Update();
	}
}

void Widget::SetWindowCaptionTextColor(const Colorf& color)
{
	Widget* w = Window();
	if (w)
	{
		w->DispWindow->SetCaptionTextColor(color.toBgra8());
		w->DispWindow->Update();
	}
}

void Widget::Update()
{
	Widget* w = Window();
	if (w)
	{
		w->DispWindow->Update();
	}
}

void Widget::Repaint()
{
	Widget* w = Window();
	w->DispCanvas->begin(WindowBackground);
	w->Paint(w->DispCanvas.get());
	w->DispCanvas->end();
}

void Widget::Paint(Canvas* canvas)
{
	Point oldOrigin = canvas->getOrigin();
	canvas->pushClip(FrameGeometry);
	canvas->setOrigin(oldOrigin + FrameGeometry.topLeft());
	OnPaintFrame(canvas);
	canvas->setOrigin(oldOrigin);
	canvas->popClip();

	canvas->pushClip(ContentGeometry);
	canvas->setOrigin(oldOrigin + ContentGeometry.topLeft());
	OnPaint(canvas);
	for (Widget* w = FirstChild(); w != nullptr; w = w->NextSibling())
	{
		if (w->Type == WidgetType::Child && !w->HiddenFlag)
			w->Paint(canvas);
	}
	canvas->setOrigin(oldOrigin);
	canvas->popClip();
}

bool Widget::GetKeyState(EInputKey key)
{
	Widget* window = Window();
	return window ? window->DispWindow->GetKeyState(key) : false;
}

bool Widget::HasFocus()
{
	Widget* window = Window();
	return window ? window->FocusWidget == this : false;
}

bool Widget::IsEnabled()
{
	return true;
}

bool Widget::IsHidden()
{
	return !IsVisible();
}

bool Widget::IsVisible()
{
	if (Type != WidgetType::Child)
	{
		return true; // DispWindow->IsVisible();
	}
	else
	{
		return !HiddenFlag;
	}
}

void Widget::SetFocus()
{
	Widget* window = Window();
	if (window)
	{
		if (window->FocusWidget)
			window->FocusWidget->OnLostFocus();
		window->FocusWidget = this;
		window->FocusWidget->OnSetFocus();
		window->ActivateWindow();
	}
}

void Widget::SetEnabled(bool value)
{
}

void Widget::LockCursor()
{
	Widget* w = Window();
	if (w && w->CaptureWidget != this)
	{
		w->CaptureWidget = this;
		w->DispWindow->LockCursor();
	}
}

void Widget::UnlockCursor()
{
	Widget* w = Window();
	if (w && w->CaptureWidget != nullptr)
	{
		w->CaptureWidget = nullptr;
		w->DispWindow->UnlockCursor();
	}
}

void Widget::SetCursor(StandardCursor cursor)
{
	if (CurrentCursor != cursor)
	{
		CurrentCursor = cursor;
		if (HoverWidget == this || CaptureWidget == this)
		{
			Widget* w = Window();
			if (w)
			{
				w->DispWindow->SetCursor(CurrentCursor);
			}
		}
	}
}

void Widget::CaptureMouse()
{
	Widget* w = Window();
	if (w && w->CaptureWidget != this)
	{
		w->CaptureWidget = this;
		w->DispWindow->CaptureMouse();
	}
}

void Widget::ReleaseMouseCapture()
{
	Widget* w = Window();
	if (w && w->CaptureWidget != nullptr)
	{
		w->CaptureWidget = nullptr;
		w->DispWindow->ReleaseMouseCapture();
	}
}

std::string Widget::GetClipboardText()
{
	Widget* w = Window();
	if (w)
		return w->DispWindow->GetClipboardText();
	else
		return {};
}

void Widget::SetClipboardText(const std::string& text)
{
	Widget* w = Window();
	if (w)
		w->DispWindow->SetClipboardText(text);
}

Widget* Widget::Window()
{
	for (Widget* w = this; w != nullptr; w = w->Parent())
	{
		if (w->DispWindow)
			return w;
	}
	return nullptr;
}

Canvas* Widget::GetCanvas() const
{
	for (const Widget* w = this; w != nullptr; w = w->Parent())
	{
		if (w->DispCanvas)
			return w->DispCanvas.get();
	}
	return nullptr;
}

Widget* Widget::ChildAt(const Point& pos)
{
	for (Widget* cur = LastChild(); cur != nullptr; cur = cur->PrevSibling())
	{
		if (!cur->HiddenFlag && cur->FrameGeometry.contains(pos))
		{
			Widget* cur2 = cur->ChildAt(pos - cur->ContentGeometry.topLeft());
			return cur2 ? cur2 : cur;
		}
	}
	return nullptr;
}

Point Widget::MapFrom(const Widget* parent, const Point& pos) const
{
	Point p = pos;
	for (const Widget* cur = this; cur != nullptr; cur = cur->Parent())
	{
		if (cur == parent)
			return p;
		p -= cur->ContentGeometry.topLeft();
	}
	throw std::runtime_error("MapFrom: not a parent of widget");
}

Point Widget::MapFromGlobal(const Point& pos) const
{
	Point p = pos;
	for (const Widget* cur = this; cur != nullptr; cur = cur->Parent())
	{
		if (cur->DispWindow)
		{
			return p - cur->GetFrameGeometry().topLeft();
		}
		p -= cur->ContentGeometry.topLeft();
	}
	throw std::runtime_error("MapFromGlobal: no window widget found");
}

Point Widget::MapTo(const Widget* parent, const Point& pos) const
{
	Point p = pos;
	for (const Widget* cur = this; cur != nullptr; cur = cur->Parent())
	{
		if (cur == parent)
			return p;
		p += cur->ContentGeometry.topLeft();
	}
	throw std::runtime_error("MapTo: not a parent of widget");
}

Point Widget::MapToGlobal(const Point& pos) const
{
	Point p = pos;
	for (const Widget* cur = this; cur != nullptr; cur = cur->Parent())
	{
		if (cur->DispWindow)
		{
			return cur->GetFrameGeometry().topLeft() + p;
		}
		p += cur->ContentGeometry.topLeft();
	}
	throw std::runtime_error("MapFromGlobal: no window widget found");
}

void Widget::OnWindowPaint()
{
	Repaint();
}

void Widget::OnWindowMouseMove(const Point& pos)
{
	if (CaptureWidget)
	{
		DispWindow->SetCursor(CaptureWidget->CurrentCursor);
		CaptureWidget->OnMouseMove(CaptureWidget->MapFrom(this, pos));
	}
	else
	{
		Widget* widget = ChildAt(pos);
		if (!widget)
			widget = this;

		if (HoverWidget != widget)
		{
			if (HoverWidget)
			{
				for (Widget* w = HoverWidget; w != widget && w != this; w = w->Parent())
				{
					Widget* p = w->Parent();
					if (!w->FrameGeometry.contains(p->MapFrom(this, pos)))
					{
						w->OnMouseLeave();
					}
				}
			}
			HoverWidget = widget;
		}

		DispWindow->SetCursor(widget->CurrentCursor);

		do
		{
			widget->OnMouseMove(widget->MapFrom(this, pos));
			if (widget == this)
				break;
			widget = widget->Parent();
		} while (widget);
	}
}

void Widget::OnWindowMouseDown(const Point& pos, EInputKey key)
{
	if (CaptureWidget)
	{
		CaptureWidget->OnMouseDown(CaptureWidget->MapFrom(this, pos), key);
	}
	else
	{
		Widget* widget = ChildAt(pos);
		if (!widget)
			widget = this;
		while (widget)
		{
			bool stopPropagation = widget->OnMouseDown(widget->MapFrom(this, pos), key);
			if (stopPropagation || widget == this)
				break;
			widget = widget->Parent();
		}
	}
}

void Widget::OnWindowMouseDoubleclick(const Point& pos, EInputKey key)
{
	if (CaptureWidget)
	{
		CaptureWidget->OnMouseDoubleclick(CaptureWidget->MapFrom(this, pos), key);
	}
	else
	{
		Widget* widget = ChildAt(pos);
		if (!widget)
			widget = this;
		while (widget)
		{
			bool stopPropagation = widget->OnMouseDoubleclick(widget->MapFrom(this, pos), key);
			if (stopPropagation || widget == this)
				break;
			widget = widget->Parent();
		}
	}
}

void Widget::OnWindowMouseUp(const Point& pos, EInputKey key)
{
	if (CaptureWidget)
	{
		CaptureWidget->OnMouseUp(CaptureWidget->MapFrom(this, pos), key);
	}
	else
	{
		Widget* widget = ChildAt(pos);
		if (!widget)
			widget = this;
		while (widget)
		{
			bool stopPropagation = widget->OnMouseUp(widget->MapFrom(this, pos), key);
			if (stopPropagation || widget == this)
				break;
			widget = widget->Parent();
		}
	}
}

void Widget::OnWindowMouseWheel(const Point& pos, EInputKey key)
{
	if (CaptureWidget)
	{
		CaptureWidget->OnMouseWheel(CaptureWidget->MapFrom(this, pos), key);
	}
	else
	{
		Widget* widget = ChildAt(pos);
		if (!widget)
			widget = this;
		while (widget)
		{
			bool stopPropagation = widget->OnMouseWheel(widget->MapFrom(this, pos), key);
			if (stopPropagation || widget == this)
				break;
			widget = widget->Parent();
		}
	}
}

void Widget::OnWindowRawMouseMove(int dx, int dy)
{
	if (CaptureWidget)
	{
		CaptureWidget->OnRawMouseMove(dx, dy);
	}
	else if (FocusWidget)
	{
		FocusWidget->OnRawMouseMove(dx, dy);
	}
}

void Widget::OnWindowKeyChar(std::string chars)
{
	if (FocusWidget)
		FocusWidget->OnKeyChar(chars);
}

void Widget::OnWindowKeyDown(EInputKey key)
{
	if (FocusWidget)
		FocusWidget->OnKeyDown(key);
}

void Widget::OnWindowKeyUp(EInputKey key)
{
	if (FocusWidget)
		FocusWidget->OnKeyUp(key);
}

void Widget::OnWindowGeometryChanged()
{
	Size size = DispWindow->GetClientSize();
	FrameGeometry = Rect::xywh(0.0, 0.0, size.width, size.height);
	ContentGeometry = FrameGeometry;
	OnGeometryChanged();
}

void Widget::OnWindowClose()
{
	Close();
}

void Widget::OnWindowActivated()
{
}

void Widget::OnWindowDeactivated()
{
}

void Widget::OnWindowDpiScaleChanged()
{
}

Size Widget::GetScreenSize()
{
	return DisplayWindow::GetScreenSize();
}
