#include "WindowManager.h"

#include "DisplayDeviceManager.h"
#include "HostDisplayDriver.h"
#include "IGpDisplayDriver.h"
#include "IGpDisplayDriverSurface.h"
#include "PLCore.h"
#include "PLEventQueue.h"
#include "PLStandardColors.h"
#include "FontFamily.h"
#include "MemoryManager.h"
#include "MenuManager.h"
#include "QDGraf.h"
#include "QDManager.h"
#include "QDPixMap.h"
#include "PLTimeTaggedVOSEvent.h"
#include "Vec2i.h"
#include "WindowDef.h"

#include <algorithm>

struct GDevice;

namespace PortabilityLayer
{
	class CGrafImpl;
	class WindowImpl;

	struct WindowChromeTheme
	{
		virtual void GetChromePadding(const WindowImpl *window, uint16_t padding[WindowChromeSides::kCount]) const = 0;
		virtual void RenderChrome(WindowImpl *window, DrawSurface *surface, WindowChromeSide_t chromeSide) const = 0;
		virtual bool GetChromeInteractionZone(const WindowImpl *window, const Vec2i &point, RegionID_t &outRegion) const = 0;
	};

	template<class T>
	struct WindowChromeThemeSingleton : public WindowChromeTheme
	{
	public:
		static T *GetInstance();

	private:
		static T ms_instance;
	};

	class SimpleBoxChromeTheme final : public WindowChromeThemeSingleton<SimpleBoxChromeTheme>
	{
	public:
		void GetChromePadding(const WindowImpl *window, uint16_t padding[WindowChromeSides::kCount]) const override;
		void RenderChrome(WindowImpl *window, DrawSurface *surface, WindowChromeSide_t chromeSide) const override;
		bool GetChromeInteractionZone(const WindowImpl *window, const Vec2i &point, RegionID_t &outRegion) const override;
	};

	class GenericWindowChromeTheme final : public WindowChromeThemeSingleton<GenericWindowChromeTheme>
	{
	public:
		void GetChromePadding(const WindowImpl *window, uint16_t padding[WindowChromeSides::kCount]) const override;
		void RenderChrome(WindowImpl *window, DrawSurface *surface, WindowChromeSide_t chromeSide) const override;
		bool GetChromeInteractionZone(const WindowImpl *window, const Vec2i &point, RegionID_t &outRegion) const override;

	private:
		void RenderChromeTop(WindowImpl *window, DrawSurface *surface) const;
		void RenderChromeLeft(WindowImpl *window, DrawSurface *surface) const;
		void RenderChromeBottom(WindowImpl *window, DrawSurface *surface) const;
		void RenderChromeRight(WindowImpl *window, DrawSurface *surface) const;

		void RenderChromeTopMini(WindowImpl *window, DrawSurface *surface) const;
		void RenderChromeLeftMini(WindowImpl *window, DrawSurface *surface) const;
		void RenderChromeBottomMini(WindowImpl *window, DrawSurface *surface) const;
		void RenderChromeRightMini(WindowImpl *window, DrawSurface *surface) const;


		static const int kDarkGray = 85;
		static const int kMidGray = 187;
		static const int kLightGray = 221;
	};

	class WindowImpl final : public Window
	{
	public:
		WindowImpl();
		~WindowImpl();

		bool Init(const WindowDef &windowDef);
		bool Resize(int width, int height);

		WindowImpl *GetWindowAbove() const;
		WindowImpl *GetWindowBelow() const;

		void SetWindowAbove(WindowImpl *above);
		void SetWindowBelow(WindowImpl *below);

		bool IsVisible() const;
		void SetVisible(bool visible);

		void GetChromePadding(uint16_t padding[WindowChromeSides::kCount]) const;
		void GetChromeDimensions(int width, int height, Rect dimensions[WindowChromeSides::kCount]) const;
		bool GetChromeInteractionZone(const Vec2i &point, RegionID_t &outRegion) const;

		bool IsBorderless() const;
		uint16_t GetStyleFlags() const;

		void SetTitle(const PLPasStr &str);
		const PascalStr<255> &GetTitle() const;

	private:
		WindowImpl *m_windowAbove;
		WindowImpl *m_windowBelow;

		WindowChromeTheme *m_chromeTheme;
		uint16_t m_styleFlags;
		bool m_visible;

		PascalStr<255> m_title;
	};

	class WindowManagerImpl final : public WindowManager
	{
	public:
		WindowManagerImpl();

		Window *CreateWindow(const WindowDef &windowDef) override;
		void ResizeWindow(Window *window, int width, int height) override;
		void MoveWindow(Window *window, int x, int y) override;
		void PutWindowBehind(Window *window, Window *otherWindow) override;
		void ShowWindow(Window *window) override;
		void HideWindow(Window *window) override;
		void FindWindow(const Point &point, Window **outWindow, short *outRegion) const override;
		void DestroyWindow(Window *window) override;
		void DragWindow(Window *window, const Point &startPoint, const Rect &constraintRect) override;
		void SetWindowTitle(Window *window, const PLPasStr &title) override;

		void RenderFrame(IGpDisplayDriver *displayDriver) override;

		void HandleScreenResolutionChange(uint32_t prevWidth, uint32_t prevHeight, uint32_t newWidth, uint32_t newHeight) override;

		Window *GetPutInFrontSentinel() const override;

		static WindowManagerImpl *GetInstance();

	private:
		void RenderWindow(WindowImpl *window, IGpDisplayDriver *displayDriver);
		void DetachWindow(Window *window);

		WindowImpl *m_windowStackTop;
		WindowImpl *m_windowStackBottom;

		static WindowManagerImpl ms_instance;

		static uint8_t ms_putInFrontSentinel;
	};


	template<class T>
	inline T *WindowChromeThemeSingleton<T>::GetInstance()
	{

		return &ms_instance;
	}

	template<class T>
	T WindowChromeThemeSingleton<T>::ms_instance;

	//---------------------------------------------------------------------------
	void SimpleBoxChromeTheme::GetChromePadding(const WindowImpl *window, uint16_t padding[WindowChromeSides::kCount]) const
	{
		padding[WindowChromeSides::kTop] = 1;
		padding[WindowChromeSides::kBottom] = 1;
		padding[WindowChromeSides::kLeft] = 1;
		padding[WindowChromeSides::kRight] = 1;
	}

	bool SimpleBoxChromeTheme::GetChromeInteractionZone(const WindowImpl *window, const Vec2i &point, RegionID_t &outRegion) const
	{
		return false;
	}

	void SimpleBoxChromeTheme::RenderChrome(WindowImpl *window, DrawSurface *surface, WindowChromeSide_t chromeSide) const
	{
		surface->SetForeColor(StdColors::Black());
		surface->FillRect((*surface->m_port.GetPixMap())->m_rect);
	}

	//---------------------------------------------------------------------------
	void GenericWindowChromeTheme::GetChromePadding(const WindowImpl *window, uint16_t padding[WindowChromeSides::kCount]) const
	{
		if (window->GetStyleFlags() & WindowStyleFlags::kMiniBar)
		{
			padding[WindowChromeSides::kTop] = 13;
			padding[WindowChromeSides::kBottom] = 1;
			padding[WindowChromeSides::kLeft] = 1;
			padding[WindowChromeSides::kRight] = 1;

			//if (window->GetStyleFlags() & WindowStyleFlags::kResizable)
			//{
			//	padding[WindowChromeSides::kBottom] = 16;
			//	padding[WindowChromeSides::kRight] = 16;
			//}
		}
		else
		{
			padding[WindowChromeSides::kTop] = 22;
			padding[WindowChromeSides::kBottom] = 6;
			padding[WindowChromeSides::kLeft] = 6;
			padding[WindowChromeSides::kRight] = 6;

			//if (window->GetStyleFlags() & WindowStyleFlags::kResizable)
			//{
			//	padding[WindowChromeSides::kBottom] = 21;
			//	padding[WindowChromeSides::kRight] = 21;
			//}
		}
	}

	bool GenericWindowChromeTheme::GetChromeInteractionZone(const WindowImpl *window, const Vec2i &point, RegionID_t &outRegion) const
	{
		const DrawSurface *surface = window->GetDrawSurface();
		const Rect rect = (*surface->m_port.GetPixMap())->m_rect;

		if (window->GetStyleFlags() & WindowStyleFlags::kMiniBar)
		{
			if (point.m_x >= 0 && point.m_x < rect.Width() && point.m_y < 0 && point.m_y >= -13)
			{
				outRegion = RegionIDs::kTitleBar;
				return true;
			}
		}
		else
		{
			if (point.m_x >= 0 && point.m_x < rect.Width() && point.m_y < 0 && point.m_y >= -17)
			{
				outRegion = RegionIDs::kTitleBar;
				return true;
			}
		}

		return false;
	}

	void GenericWindowChromeTheme::RenderChrome(WindowImpl *window, DrawSurface *surface, WindowChromeSide_t chromeSide) const
	{
		if (window->GetStyleFlags() & WindowStyleFlags::kMiniBar)
		{
			switch (chromeSide)
			{
			case WindowChromeSides::kTop:
				RenderChromeTopMini(window, surface);
				break;
			case WindowChromeSides::kLeft:
				RenderChromeLeftMini(window, surface);
				break;
			case WindowChromeSides::kBottom:
				RenderChromeBottomMini(window, surface);
				break;
			case WindowChromeSides::kRight:
				RenderChromeRightMini(window, surface);
				break;
			default:
				break;
			}
		}
		else
		{
			switch (chromeSide)
			{
			case WindowChromeSides::kTop:
				RenderChromeTop(window, surface);
				break;
			case WindowChromeSides::kLeft:
				RenderChromeLeft(window, surface);
				break;
			case WindowChromeSides::kBottom:
				RenderChromeBottom(window, surface);
				break;
			case WindowChromeSides::kRight:
				RenderChromeRight(window, surface);
				break;
			default:
				break;
			}
		}
	}

	void GenericWindowChromeTheme::RenderChromeTop(WindowImpl *window, DrawSurface *surface) const
	{
		const Rect rect = (*surface->m_port.GetPixMap())->m_rect;

		surface->SetForeColor(PortabilityLayer::RGBAColor::Create(kMidGray, kMidGray, kMidGray, 255));
		surface->FillRect(rect);

		surface->SetForeColor(StdColors::Black());
		surface->FillRect(Rect::Create(rect.top, rect.left, rect.top + 1, rect.right));
		surface->FillRect(Rect::Create(rect.top, rect.left, rect.bottom, 1));
		surface->FillRect(Rect::Create(rect.top, rect.right - 1, rect.bottom, rect.right));
		surface->FillRect(Rect::Create(rect.bottom - 1, rect.left + 5, rect.bottom, rect.right - 5));

		surface->SetForeColor(PortabilityLayer::RGBAColor::Create(kDarkGray, kDarkGray, kDarkGray, 255));
		surface->FillRect(Rect::Create(rect.bottom - 2, rect.left + 4, rect.bottom - 1, rect.right - 5));
		surface->FillRect(Rect::Create(rect.bottom - 2, rect.left + 4, rect.bottom, rect.left + 5));
		surface->FillRect(Rect::Create(rect.top + 2, rect.right - 2, rect.bottom, rect.right - 1));

		surface->SetForeColor(StdColors::White());
		surface->FillRect(Rect::Create(rect.top + 1, rect.left + 1, rect.bottom, rect.left + 2));
		surface->FillRect(Rect::Create(rect.top + 1, rect.left + 1, rect.top + 2, rect.right - 2));
		surface->FillRect(Rect::Create(rect.bottom - 1, rect.right - 5, rect.bottom, rect.right - 4));

		surface->SetForeColor(StdColors::Black());
		surface->SetSystemFont(12, PortabilityLayer::FontFamilyFlags::FontFamilyFlag_Bold);
		int32_t ascender = surface->MeasureFontAscender();

		const PLPasStr titlePStr = window->GetTitle().ToShortStr();
		size_t titleWidth = surface->MeasureString(titlePStr);

		int32_t titleH = (rect.left + rect.right - static_cast<int32_t>(titleWidth) + 1) / 2;
		int32_t titleV = (rect.top + rect.bottom + ascender + 1) / 2;

		surface->DrawString(Point::Create(titleH, titleV), titlePStr, true);
	}

	void GenericWindowChromeTheme::RenderChromeLeft(WindowImpl *window, DrawSurface *surface) const
	{
		const Rect rect = (*surface->m_port.GetPixMap())->m_rect;

		surface->SetForeColor(StdColors::Black());
		surface->FillRect(Rect::Create(rect.top, rect.right - 6, rect.bottom, rect.right - 5));
		surface->FillRect(Rect::Create(rect.top, rect.right - 1, rect.bottom, rect.right));

		surface->SetForeColor(StdColors::White());
		surface->FillRect(Rect::Create(rect.top, rect.right - 5, rect.bottom, rect.right - 4));

		surface->SetForeColor(PortabilityLayer::RGBAColor::Create(kDarkGray, kDarkGray, kDarkGray, 255));
		surface->FillRect(Rect::Create(rect.top, rect.right - 2, rect.bottom, rect.right - 1));

		surface->SetForeColor(PortabilityLayer::RGBAColor::Create(kMidGray, kMidGray, kMidGray, 255));
		surface->FillRect(Rect::Create(rect.top, rect.right - 4, rect.bottom, rect.right - 2));
	}

	void GenericWindowChromeTheme::RenderChromeBottom(WindowImpl *window, DrawSurface *surface) const
	{
		const Rect rect = (*surface->m_port.GetPixMap())->m_rect;

		surface->SetForeColor(PortabilityLayer::RGBAColor::Create(kMidGray, kMidGray, kMidGray, 255));
		surface->FillRect(Rect::Create(rect.top, rect.left + 1, rect.bottom - 1, rect.left + 5));
		surface->FillRect(Rect::Create(rect.bottom - 4, rect.left + 5, rect.bottom - 2, rect.right - 4));
		surface->FillRect(Rect::Create(rect.top, rect.right - 4, rect.bottom - 2, rect.right - 2));

		surface->SetForeColor(StdColors::Black());
		surface->FillRect(Rect::Create(rect.top, rect.left, rect.bottom, rect.left + 1));
		surface->FillRect(Rect::Create(rect.bottom - 1, rect.left, rect.bottom, rect.right));
		surface->FillRect(Rect::Create(rect.top, rect.right - 1, rect.bottom, rect.right));
		surface->FillRect(Rect::Create(rect.top, rect.left + 5, rect.bottom - 5, rect.left + 6));
		surface->FillRect(Rect::Create(rect.top, rect.right - 6, rect.bottom - 5, rect.right - 5));
		surface->FillRect(Rect::Create(rect.bottom - 6, rect.left + 6, rect.bottom - 5, rect.right - 6));

		surface->SetForeColor(StdColors::White());
		surface->FillRect(Rect::Create(rect.top, rect.right - 5, rect.bottom - 5, rect.right - 4));
		surface->FillRect(Rect::Create(rect.top, rect.left + 1, rect.bottom - 2, rect.left + 2));
		surface->FillRect(Rect::Create(rect.bottom - 5, rect.left + 5, rect.bottom - 4, rect.right - 4));

		surface->SetForeColor(PortabilityLayer::RGBAColor::Create(kDarkGray, kDarkGray, kDarkGray, 255));
		surface->FillRect(Rect::Create(rect.bottom - 2, rect.left + 2, rect.bottom - 1, rect.right - 2));
		surface->FillRect(Rect::Create(rect.top, rect.left + 4, rect.bottom - 5, rect.left + 5));
		surface->FillRect(Rect::Create(rect.top, rect.right - 2, rect.bottom - 1, rect.right - 1));
	}

	void GenericWindowChromeTheme::RenderChromeRight(WindowImpl *window, DrawSurface *surface) const
	{
		const Rect rect = (*surface->m_port.GetPixMap())->m_rect;

		surface->SetForeColor(StdColors::Black());
		surface->FillRect(Rect::Create(rect.top, rect.right - 6, rect.bottom, rect.right - 5));
		surface->FillRect(Rect::Create(rect.top, rect.right - 1, rect.bottom, rect.right));

		surface->SetForeColor(StdColors::White());
		surface->FillRect(Rect::Create(rect.top, rect.right - 5, rect.bottom, rect.right - 4));

		surface->SetForeColor(PortabilityLayer::RGBAColor::Create(kDarkGray, kDarkGray, kDarkGray, 255));
		surface->FillRect(Rect::Create(rect.top, rect.right - 2, rect.bottom, rect.right - 1));

		surface->SetForeColor(PortabilityLayer::RGBAColor::Create(kMidGray, kMidGray, kMidGray, 255));
		surface->FillRect(Rect::Create(rect.top, rect.right - 4, rect.bottom, rect.right - 2));
	}

	void GenericWindowChromeTheme::RenderChromeTopMini(WindowImpl *window, DrawSurface *surface) const
	{
		const Rect rect = (*surface->m_port.GetPixMap())->m_rect;

		surface->SetForeColor(PortabilityLayer::RGBAColor::Create(kLightGray, kLightGray, kLightGray, 255));
		surface->FillRect(rect);

		surface->SetForeColor(StdColors::Black());
		surface->FillRect(Rect::Create(rect.top, rect.left, rect.top + 1, rect.right));
		surface->FillRect(Rect::Create(rect.top, rect.left, rect.bottom, 1));
		surface->FillRect(Rect::Create(rect.top, rect.right - 1, rect.bottom, rect.right));
		surface->FillRect(Rect::Create(rect.bottom - 1, rect.left + 1, rect.bottom, rect.right - 1));

		surface->SetForeColor(PortabilityLayer::RGBAColor::Create(kDarkGray, kDarkGray, kDarkGray, 255));
		surface->FillRect(Rect::Create(rect.bottom - 2, rect.left + 2, rect.bottom - 1, rect.right - 2));
		surface->FillRect(Rect::Create(rect.top + 2, rect.right - 2, rect.bottom - 1, rect.right - 1));

		surface->SetForeColor(StdColors::White());
		surface->FillRect(Rect::Create(rect.top + 1, rect.left + 1, rect.bottom - 2, rect.left + 2));
		surface->FillRect(Rect::Create(rect.top + 1, rect.left + 1, rect.top + 2, rect.right - 2));

		surface->SetForeColor(StdColors::Black());
		surface->SetApplicationFont(10, PortabilityLayer::FontFamilyFlags::FontFamilyFlag_Bold);
		int32_t ascender = surface->MeasureFontAscender();

		const PLPasStr titlePStr = window->GetTitle().ToShortStr();
		size_t titleWidth = surface->MeasureString(titlePStr);

		int32_t titleH = (rect.left + rect.right - static_cast<int32_t>(titleWidth) + 1) / 2;
		int32_t titleV = (rect.top + rect.bottom + ascender + 1) / 2;

		surface->DrawString(Point::Create(titleH, titleV), titlePStr, true);
	}

	void GenericWindowChromeTheme::RenderChromeLeftMini(WindowImpl *window, DrawSurface *surface) const
	{
		const Rect rect = (*surface->m_port.GetPixMap())->m_rect;

		surface->SetForeColor(StdColors::Black());
		surface->FillRect(rect);
	}

	void GenericWindowChromeTheme::RenderChromeBottomMini(WindowImpl *window, DrawSurface *surface) const
	{
		const Rect rect = (*surface->m_port.GetPixMap())->m_rect;

		surface->SetForeColor(StdColors::Black());
		surface->FillRect(rect);
	}

	void GenericWindowChromeTheme::RenderChromeRightMini(WindowImpl *window, DrawSurface *surface) const
	{
		const Rect rect = (*surface->m_port.GetPixMap())->m_rect;

		surface->SetForeColor(StdColors::Black());
		surface->FillRect(rect);
	}

	//---------------------------------------------------------------------------
	WindowImpl::WindowImpl()
		: m_windowAbove(nullptr)
		, m_windowBelow(nullptr)
		, m_chromeTheme(nullptr)
		, m_visible(true)
		, m_styleFlags(0)
	{
	}

	WindowImpl::~WindowImpl()
	{
		assert(m_windowAbove == nullptr && m_windowBelow == nullptr);
	}

	bool WindowImpl::Init(const WindowDef &windowDef)
	{
		const Rect bounds = windowDef.m_initialRect;

		if (!bounds.IsValid())
			return false;

		m_styleFlags = windowDef.m_styleFlags;

		const Rect adjustedBounds = Rect::Create(0, 0, bounds.Height(), bounds.Width());
		m_wmX = bounds.left;
		m_wmY = bounds.top;

		GpPixelFormat_t pixelFormat = PortabilityLayer::DisplayDeviceManager::GetInstance()->GetPixelFormat();

		if (int errorCode = m_surface.Init(adjustedBounds, pixelFormat))
			return false;

		m_title.Set(windowDef.m_title[0], reinterpret_cast<const char*>(windowDef.m_title + 1));

		// Resolve chrome
		if (!IsBorderless())
		{
			m_chromeTheme = SimpleBoxChromeTheme::GetInstance();

			if (m_styleFlags & WindowStyleFlags::kTitleBar)
				m_chromeTheme = GenericWindowChromeTheme::GetInstance();

			Rect chromeBounds[WindowChromeSides::kCount];
			GetChromeDimensions(bounds.Width(), bounds.Height(), chromeBounds);

			for (int chromeSide = 0; chromeSide < WindowChromeSides::kCount; chromeSide++)
			{
				if (int errorCode = m_chromeSurfaces[chromeSide].Init(chromeBounds[chromeSide], pixelFormat))
					return false;

				m_chromeTheme->RenderChrome(this, m_chromeSurfaces + chromeSide, static_cast<WindowChromeSide_t>(chromeSide));
			}
		}

		return true;
	}

	bool WindowImpl::Resize(int width, int height)
	{
		Rect rect = m_surface.m_port.GetRect();
		rect.right = rect.left + width;
		rect.bottom = rect.top + height;

		if (!m_surface.Resize(rect))
			return false;

		if (!IsBorderless())
		{
			Rect chromeDimensions[WindowChromeSides::kCount];

			GetChromeDimensions(width, height, chromeDimensions);

			bool resized = true;
			for (int i = 0; i < WindowChromeSides::kCount; i++)
			{
				if (!m_chromeSurfaces[i].Resize(chromeDimensions[i]))
					return false;

				m_chromeTheme->RenderChrome(this, m_chromeSurfaces + i, static_cast<WindowChromeSide_t>(i));
			}
		}

		return true;
	}

	WindowImpl *WindowImpl::GetWindowAbove() const
	{
		return m_windowAbove;
	}

	WindowImpl *WindowImpl::GetWindowBelow() const
	{
		return m_windowBelow;
	}

	void WindowImpl::SetWindowAbove(WindowImpl *above)
	{
		m_windowAbove = above;
	}

	void WindowImpl::SetWindowBelow(WindowImpl *below)
	{
		m_windowBelow = below;
	}

	bool WindowImpl::IsVisible() const
	{
		return m_visible;
	}

	void WindowImpl::SetVisible(bool visible)
	{
		m_visible = visible;
	}

	void WindowImpl::GetChromePadding(uint16_t padding[WindowChromeSides::kCount]) const
	{
		if (m_chromeTheme)
			m_chromeTheme->GetChromePadding(this, padding);
		else
		{
			for (int i = 0; i < WindowChromeSides::kCount; i++)
				padding[i] = 0;
		}
	}

	void WindowImpl::GetChromeDimensions(int width, int height, Rect dimensions[WindowChromeSides::kCount]) const
	{
		uint16_t padding[WindowChromeSides::kCount];
		GetChromePadding(padding);

		int32_t topAndBottomWidth = width + padding[WindowChromeSides::kLeft] + padding[WindowChromeSides::kRight];
		int32_t leftAndRightHeight = height;

		dimensions[WindowChromeSides::kTop] = Rect::Create(0, 0, padding[WindowChromeSides::kTop], topAndBottomWidth);
		dimensions[WindowChromeSides::kBottom] = Rect::Create(0, 0, padding[WindowChromeSides::kBottom], topAndBottomWidth);
		dimensions[WindowChromeSides::kLeft] = Rect::Create(0, 0, leftAndRightHeight, padding[WindowChromeSides::kLeft]);
		dimensions[WindowChromeSides::kRight] = Rect::Create(0, 0, leftAndRightHeight, padding[WindowChromeSides::kRight]);
	}

	bool WindowImpl::GetChromeInteractionZone(const Vec2i &point, RegionID_t &outRegion) const
	{
		if (!m_chromeTheme)
			return false;

		return m_chromeTheme->GetChromeInteractionZone(this, point, outRegion);
	}

	bool WindowImpl::IsBorderless() const
	{
		return (m_styleFlags & WindowStyleFlags::kBorderless) != 0;
	}

	uint16_t WindowImpl::GetStyleFlags() const
	{
		return m_styleFlags;
	}

	void WindowImpl::SetTitle(const PLPasStr &str)
	{
		m_title.Set(str.Length(), str.Chars());
		m_chromeTheme->RenderChrome(this, m_chromeSurfaces + WindowChromeSides::kTop, WindowChromeSides::kTop);
	}

	const PascalStr<255> &WindowImpl::GetTitle() const
	{
		return m_title;
	}

	WindowManagerImpl::WindowManagerImpl()
		: m_windowStackTop(nullptr)
		, m_windowStackBottom(nullptr)
	{
	}

	Window *WindowManagerImpl::CreateWindow(const WindowDef &windowDef)
	{
		void *windowMem = MemoryManager::GetInstance()->Alloc(sizeof(WindowImpl));
		if (!windowMem)
			return nullptr;

		Rect portRect = windowDef.m_initialRect;
		if (!portRect.IsValid())
			return nullptr;

		WindowImpl *window = new (windowMem) WindowImpl();
		if (!window->Init(windowDef))
		{
			window->~WindowImpl();
			MemoryManager::GetInstance()->Release(windowMem);
			return nullptr;
		}

		return window;
	}

	void WindowManagerImpl::PutWindowBehind(Window *windowBase, Window *behind)
	{
		WindowImpl *window = static_cast<WindowImpl*>(windowBase);

		if (window->GetWindowAbove() != behind)
			DetachWindow(window);

		if (behind == GetPutInFrontSentinel())
		{
			if (m_windowStackTop)
			{
				m_windowStackTop->SetWindowAbove(window);
				window->SetWindowBelow(m_windowStackTop);
				m_windowStackTop = window;
			}
			else
			{
				m_windowStackTop = m_windowStackBottom = window;
			}
		}
		else
		{
			WindowImpl *windowAbove = static_cast<WindowImpl*>(behind);
			WindowImpl *windowBelow = windowAbove->GetWindowBelow();

			window->SetWindowAbove(windowAbove);
			window->SetWindowBelow(windowBelow);

			if (windowBelow)
				windowBelow->SetWindowAbove(window);
			else
				m_windowStackBottom = window;

			windowAbove->SetWindowBelow(window);
		}
	}

	void WindowManagerImpl::ShowWindow(Window *window)
	{
		static_cast<WindowImpl*>(window)->SetVisible(true);
	}

	void WindowManagerImpl::HideWindow(Window *window)
	{
		WindowImpl *impl = static_cast<WindowImpl*>(window);
		impl->SetVisible(false);

		// Per spec, hiding a window brings the window below it to the front
		if (m_windowStackTop == impl)
		{
			if (WindowImpl *below = impl->GetWindowBelow())
				PutWindowBehind(below, GetPutInFrontSentinel());
		}
	}

	void WindowManagerImpl::FindWindow(const Point &point, Window **outWindow, short *outRegion) const
	{
		if (PortabilityLayer::MenuManager::GetInstance()->IsPointInMenuBar(PortabilityLayer::Vec2i(point.h, point.v)))
		{
			if (outWindow)
				*outWindow = nullptr;

			if (outRegion)
				*outRegion = RegionIDs::kMenuBar;

			return;
		}

		WindowImpl *window = m_windowStackTop;
		while (window)
		{
			const Rect windowRect = window->m_surface.m_port.GetRect();

			const int32_t localX = point.h - window->m_wmX;
			const int32_t localY = point.v - window->m_wmY;

			RegionID_t chromeInteractionZone = RegionIDs::kContent;
			if (window->GetChromeInteractionZone(Vec2i(localX, localY), chromeInteractionZone))
			{
				*outRegion = chromeInteractionZone;
				*outWindow = window;
				return;
			}

			if (localX >= 0 && localY >= 0 && localX < windowRect.right && localY < windowRect.bottom)
			{
				if (outWindow)
					*outWindow = window;

				if (outRegion)
					*outRegion = RegionIDs::kContent;

				return;
			}

			window = window->GetWindowBelow();
		}

		if (outWindow)
			*outWindow = nullptr;

		if (outRegion)
			*outRegion = 0;
	}

	void WindowManagerImpl::DestroyWindow(Window *window)
	{
		WindowImpl *windowImpl = static_cast<WindowImpl*>(window);

		DetachWindow(window);

		if (PortabilityLayer::QDManager::GetInstance()->GetPort() == &windowImpl->m_surface.m_port)
			PortabilityLayer::QDManager::GetInstance()->SetPort(nullptr);

		windowImpl->~WindowImpl();
		PortabilityLayer::MemoryManager::GetInstance()->Release(windowImpl);
	}

	void WindowManagerImpl::DragWindow(Window *window, const Point &startPointRef, const Rect &constraintRect)
	{
		int32_t baseX = startPointRef.h;
		int32_t baseY = startPointRef.v;

		for (;;)
		{
			TimeTaggedVOSEvent evt;
			if (WaitForEvent(&evt, 1))
			{
				if (evt.m_vosEvent.m_eventType == GpVOSEventTypes::kMouseInput)
				{
					const GpMouseInputEvent &mouseEvent = evt.m_vosEvent.m_event.m_mouseInputEvent;
					int32_t x = mouseEvent.m_x;
					int32_t y = mouseEvent.m_y;

					if (x < constraintRect.left)
						x = constraintRect.left;
					if (x >= constraintRect.right)
						x = constraintRect.right - 1;
					if (y < constraintRect.top)
						y = constraintRect.top;
					if (y >= constraintRect.bottom)
						y = constraintRect.bottom - 1;

					window->m_wmX += x - baseX;
					window->m_wmY += y - baseY;

					baseX = x;
					baseY = y;

					if (mouseEvent.m_eventType == GpMouseEventTypes::kUp)
						break;
				}
			}
		}
	}

	void WindowManagerImpl::SetWindowTitle(Window *window, const PLPasStr &title)
	{
		static_cast<WindowImpl*>(window)->SetTitle(title);
	}

	void WindowManagerImpl::RenderFrame(IGpDisplayDriver *displayDriver)
	{
		PortabilityLayer::DisplayDeviceManager *dd = PortabilityLayer::DisplayDeviceManager::GetInstance();

		dd->SyncPalette(displayDriver);

		WindowImpl *window = m_windowStackBottom;
		while (window)
		{
			RenderWindow(window, displayDriver);
			window = window->GetWindowAbove();
		}
	}

	void WindowManagerImpl::HandleScreenResolutionChange(uint32_t prevWidth, uint32_t prevHeight, uint32_t newWidth, uint32_t newHeight)
	{
		for (PortabilityLayer::WindowImpl *window = m_windowStackTop; window != nullptr; window = window->GetWindowBelow())
		{
			uint16_t chromePadding[WindowChromeSides::kCount];
			window->GetChromePadding(chromePadding);

			const Rect surfaceRect = window->GetDrawSurface()->m_port.GetRect();

			uint32_t paddedWidth = surfaceRect.Width() + chromePadding[WindowChromeSides::kLeft] + chromePadding[WindowChromeSides::kRight];
			uint32_t paddedHeight = surfaceRect.Height() + chromePadding[WindowChromeSides::kTop] + chromePadding[WindowChromeSides::kBottom];

			int64_t newX = 0;
			if (newWidth <= paddedWidth || prevWidth <= paddedWidth)
				newX = (static_cast<int64_t>(newWidth) - paddedWidth) / 2;
			else
			{
				uint32_t prevClearanceX = prevWidth - paddedWidth;
				uint32_t newClearanceX = newWidth - paddedWidth;
				newX = static_cast<int64_t>(window->m_wmX) * static_cast<int64_t>(newClearanceX) / static_cast<int64_t>(prevClearanceX);
			}

			int64_t newY = 0;
			if (newHeight <= paddedHeight || prevHeight <= paddedHeight)
				newY = (static_cast<int64_t>(newHeight) - paddedHeight) / 2;
			else
			{
				uint32_t prevClearanceY = prevHeight - paddedHeight;
				uint32_t newClearanceY = newHeight - paddedHeight;
				newY = static_cast<int64_t>(window->m_wmY) * static_cast<int64_t>(newClearanceY) / static_cast<int64_t>(prevClearanceY);
			}

			newX = std::max<int64_t>(0, std::min<int64_t>(newX, newWidth - 1));
			newY = std::max<int64_t>(0, std::min<int64_t>(newY, newHeight - 1));

			window->m_wmX = static_cast<int32_t>(newX);
			window->m_wmY = static_cast<int32_t>(newY);
		}
	}

	void WindowManagerImpl::ResizeWindow(Window *window, int width, int height)
	{
		static_cast<WindowImpl*>(window)->Resize(width, height);
	}

	void WindowManagerImpl::MoveWindow(Window *window, int x, int y)
	{
		window->m_wmX = x;
		window->m_wmY = y;
	}

	void WindowManagerImpl::DetachWindow(Window *window)
	{
		if (m_windowStackBottom == window)
			m_windowStackBottom = m_windowStackBottom->GetWindowAbove();
		if (m_windowStackTop == window)
			m_windowStackTop = m_windowStackTop->GetWindowBelow();

		WindowImpl *impl = static_cast<WindowImpl*>(window);

		if (WindowImpl *below = impl->GetWindowBelow())
			below->SetWindowAbove(impl->GetWindowAbove());

		if (WindowImpl *above = impl->GetWindowAbove())
			above->SetWindowBelow(impl->GetWindowBelow());

		impl->SetWindowAbove(nullptr);
		impl->SetWindowBelow(nullptr);
	}

	Window *WindowManagerImpl::GetPutInFrontSentinel() const
	{
		return reinterpret_cast<Window*>(&ms_putInFrontSentinel);
	}

	void WindowManagerImpl::RenderWindow(WindowImpl *window, IGpDisplayDriver *displayDriver)
	{
		if (!window->IsVisible())
			return;

		DrawSurface &graf = window->m_surface;

		graf.PushToDDSurface(displayDriver);

		const PixMap *pixMap = *graf.m_port.GetPixMap();
		const uint16_t width = pixMap->m_rect.Width();
		const uint16_t height = pixMap->m_rect.Height();
		displayDriver->DrawSurface(graf.m_ddSurface, window->m_wmX, window->m_wmY, width, height);


		if (!window->IsBorderless())
		{
			uint16_t chromePadding[WindowChromeSides::kCount];
			window->GetChromePadding(chromePadding);

			Vec2i chromeOrigins[WindowChromeSides::kCount];
			chromeOrigins[WindowChromeSides::kTop] = Vec2i(window->m_wmX - chromePadding[WindowChromeSides::kLeft], window->m_wmY - chromePadding[WindowChromeSides::kTop]);
			chromeOrigins[WindowChromeSides::kLeft] = Vec2i(window->m_wmX - chromePadding[WindowChromeSides::kLeft], window->m_wmY);
			chromeOrigins[WindowChromeSides::kRight] = Vec2i(window->m_wmX + width, window->m_wmY);
			chromeOrigins[WindowChromeSides::kBottom] = Vec2i(window->m_wmX - chromePadding[WindowChromeSides::kLeft], window->m_wmY + height);

			Vec2i chromeDimensions[WindowChromeSides::kCount];
			chromeDimensions[WindowChromeSides::kTop] = Vec2i(chromePadding[WindowChromeSides::kLeft] + chromePadding[WindowChromeSides::kRight] + width, chromePadding[WindowChromeSides::kTop]);
			chromeDimensions[WindowChromeSides::kLeft] = Vec2i(chromePadding[WindowChromeSides::kLeft], height);
			chromeDimensions[WindowChromeSides::kRight] = Vec2i(chromePadding[WindowChromeSides::kRight], height);
			chromeDimensions[WindowChromeSides::kBottom] = Vec2i(chromePadding[WindowChromeSides::kLeft] + chromePadding[WindowChromeSides::kRight] + width, chromePadding[WindowChromeSides::kBottom]);

			for (int i = 0; i < WindowChromeSides::kCount; i++)
			{
				DrawSurface *chromeSurface = window->GetChromeSurface(static_cast<WindowChromeSide_t>(i));

				chromeSurface->PushToDDSurface(displayDriver);

				displayDriver->DrawSurface(chromeSurface->m_ddSurface, chromeOrigins[i].m_x, chromeOrigins[i].m_y, chromeDimensions[i].m_x, chromeDimensions[i].m_y);
			}
		}
	}

	WindowManagerImpl *WindowManagerImpl::GetInstance()
	{
		return &ms_instance;
	}

	WindowManagerImpl WindowManagerImpl::ms_instance;
	uint8_t WindowManagerImpl::ms_putInFrontSentinel;

	WindowManager *WindowManager::GetInstance()
	{
		return WindowManagerImpl::GetInstance();
	}
}
