#include "Painter.h"
#include "Window.h"
#include "Widget.h"


#include <assert.h>
#define DEBUG_WIDGET  0
#if DEBUG_WIDGET
#  include <stdio.h>
#  include <typeinfo>
#endif


BEGIN_NAMESPACE


Widget::Widget(const char* a_name, bool a_root)
{
  assert(a_root);
  m_name = a_name ? a_name : "Unnamed";
  m_parent = 0;
  m_layoutDirection = true;
  m_windowX = m_windowY = 0;
  m_watchingMouse = false;
  m_x = m_y = 0;
  m_goalWidth = m_goalHeight = 1;
  layoutMargin = 5;
  layoutSpacing = 5;
  backgroundBrush = 0xF0F0F0;
  flags = WF_None;
}


Widget::Widget(Widget* a_parent, const char* a_name, bool a_layoutDirection, int a_x, int a_y, int a_width, int a_height)
	: m_parent(a_parent), m_layoutDirection(a_layoutDirection)
{
  // TODO: when automatically creating a parent window when parent is 0, we need
  // a way to automatically destroy things - currently destruction is not well tested.
  // Need to try create and destroy a bunch of widgets and see if the windows can be removed.
  // May require adding a flag to say if we automatically created the parent window or not.
  // Or perhaps using shared_ptr and then if the window has no children it destroys itself?
  if (!m_parent)
    m_parent = new Window(a_name, true);
  m_name = a_name ? a_name : "Unnamed";
  layoutMargin = 5;
  layoutSpacing = 5;
  backgroundBrush = 0xF0F0F0;
  m_windowX = m_windowY = 0;
  m_watchingMouse = false;
  setGeometry(a_x, a_y, a_width, a_height);
  flags = WF_None;
  if (m_parent)
    m_parent->addChild(this);
}


Widget::~Widget()
{
	Widget *w = (m_children.size()) ? m_children.front() : 0;
	while (w)
	{
		delete w;
		w = (m_children.size()) ? m_children.front() : 0;
	}

	if (m_parent)
		m_parent->m_children.remove(this);
}


void Widget::addChild(Widget* a_widget)
{
	m_children.push_back(a_widget);
	if (m_parent)
		m_parent->updateLayout();
	else
		updateLayout();
	repaint();
}


void Widget::updateLayout()
{
	// recalculate layout
	int childCount = (int)m_children.size();
	if (childCount)
	{
		SizeOptions childTotalSize;
		Vector<SizeOptions> childSizes;
		childSizes.resize(childCount);
		memset(&childTotalSize, 0, sizeof(childTotalSize));

		int i = 0;
		for (std::list<Widget*>::const_iterator it = m_children.begin(); it != m_children.end(); ++it)
		{
			(*it)->sizeOptions(childSizes[i]);

			childTotalSize.m_minimum.m_width    += childSizes[i].m_minimum.m_width;
			childTotalSize.m_minimum.m_height   += childSizes[i].m_minimum.m_height;

			childTotalSize.m_preferred.m_width  += childSizes[i].m_preferred.m_width;
			childTotalSize.m_preferred.m_height += childSizes[i].m_preferred.m_height;

			childTotalSize.m_maximum.m_width    += childSizes[i].m_maximum.m_width;
			childTotalSize.m_maximum.m_height   += childSizes[i].m_maximum.m_height;

			i++;
		}

		int x = 0, y = 0, wid = width(), heig = height();
		int extraSpaceForMarginsAndSpacing = (childCount - 1) * layoutSpacing() + layoutMargin() * 2;

		if (m_layoutDirection)
		{
			// horz
			if ( wid > childTotalSize.m_maximum.m_width )
			{
				// TODO: need to do padding horizontally
				wid = childTotalSize.m_maximum.m_width;
			}
			if ( wid < childTotalSize.m_minimum.m_width )
			{
				// TODO: can't meet the constraint
				wid = childTotalSize.m_minimum.m_width;
			}
			if ( wid < extraSpaceForMarginsAndSpacing )
			{
				// TODO: can't meet the constraint
				wid = extraSpaceForMarginsAndSpacing;
			}

			int w = wid - extraSpaceForMarginsAndSpacing;
			int h = heig - layoutMargin() * 2;
			int remainder = w - childTotalSize.m_preferred.m_width;
			Vector<int> proposedChildWidths;
			Vector<bool> childWidthDone;
			int childWidthDoneCount = 0;
			proposedChildWidths.resize(childCount);
			childWidthDone.resize(childCount);
			for (int i = 0; i < childCount; i++)
			{
				proposedChildWidths[i] = childSizes[i].m_preferred.m_width;
				childWidthDone[i] = false;
			}
			while (abs(remainder) > (childCount-childWidthDoneCount))
			{
				int distributedRemainder = remainder / (childCount-childWidthDoneCount);

				for (int i = 0; i < childCount; i++)
				{
					if (!childWidthDone[i]) {
						if (remainder < 0) {
							if (proposedChildWidths[i] + distributedRemainder < childSizes[i].m_minimum.m_width)
							{
								proposedChildWidths[i] = childSizes[i].m_minimum.m_width;
								childWidthDone[i] = true;
								childWidthDoneCount++;
							} else {
								proposedChildWidths[i] += distributedRemainder;
							}
						} else {
							if (proposedChildWidths[i] + distributedRemainder > childSizes[i].m_maximum.m_width)
							{
								proposedChildWidths[i] = childSizes[i].m_maximum.m_width;
								childWidthDone[i] = true;
								childWidthDoneCount++;
							} else {
								proposedChildWidths[i] += distributedRemainder;
							}
						}
					}
				}

				int newTotalProposedWidth = 0;
				for (int i = 0; i < childCount; i++)
					newTotalProposedWidth += proposedChildWidths[i];
				remainder = w - newTotalProposedWidth;

				if (childWidthDoneCount >= childCount)
					break;
			}

			int i = 0;
			y = x = layoutMargin();
			for (std::list<Widget*>::const_iterator it = m_children.begin(); it != m_children.end(); ++it, x += proposedChildWidths[i] + layoutSpacing(), i++)
				(*it)->setGeometry(x, y, proposedChildWidths[i], h);

		} else {
			// vert
			if ( heig > childTotalSize.m_maximum.m_height )
			{
				// TODO: need to do padding horizontally
				heig = childTotalSize.m_maximum.m_height;
			}
			if ( heig < childTotalSize.m_minimum.m_height )
			{
				// TODO: can't meet the constraint
				heig = childTotalSize.m_minimum.m_height;
			}
			if ( heig < extraSpaceForMarginsAndSpacing )
			{
				// TODO: can't meet the constraint
				heig = extraSpaceForMarginsAndSpacing;
			}

			int h = heig - extraSpaceForMarginsAndSpacing;
			int w = wid - layoutMargin() * 2;
			int remainder = h - childTotalSize.m_preferred.m_height;
			Vector<int> proposedChildHeight;
			Vector<bool> childHeightDone;
			int childHeightDoneCount = 0;
			proposedChildHeight.resize(childCount);
			childHeightDone.resize(childCount);
			for (int i = 0; i < childCount; i++)
			{
				proposedChildHeight[i] = childSizes[i].m_preferred.m_height;
				childHeightDone[i] = false;
			}
			while (abs(remainder) > (childCount-childHeightDoneCount))
			{
				int distributedRemainder = remainder / (childCount-childHeightDoneCount);

				for (int i = 0; i < childCount; i++)
				{
					if (!childHeightDone[i]) {
						if (remainder < 0) {
							if (proposedChildHeight[i] + distributedRemainder < childSizes[i].m_minimum.m_height)
							{
								proposedChildHeight[i] = childSizes[i].m_minimum.m_height;
								childHeightDone[i] = true;
								childHeightDoneCount++;
							} else {
								proposedChildHeight[i] += distributedRemainder;
							}
						} else {
							if (proposedChildHeight[i] + distributedRemainder > childSizes[i].m_maximum.m_height)
							{
								proposedChildHeight[i] = childSizes[i].m_maximum.m_height;
								childHeightDone[i] = true;
								childHeightDoneCount++;
							} else {
								proposedChildHeight[i] += distributedRemainder;
							}
						}
					}
				}

				int newTotalProposedHeight = 0;
				for (int i = 0; i < childCount; i++)
					newTotalProposedHeight += proposedChildHeight[i];
				remainder = h - newTotalProposedHeight;

				if (childHeightDoneCount >= childCount)
					break;
			}

			int i = 0;
			y = x = layoutMargin();
			for (std::list<Widget*>::const_iterator it = m_children.begin(); it != m_children.end(); ++it, y += proposedChildHeight[i] + layoutSpacing(), i++)
				(*it)->setGeometry(x, y, w, proposedChildHeight[i]);
		}
	}
}


void Widget::sizeOptions(SizeOptions& a_sizeOptions)
{
	a_sizeOptions.m_minimum.m_width    = 0;
	a_sizeOptions.m_minimum.m_height   = 0;
	a_sizeOptions.m_preferred.m_width  = 256;
	a_sizeOptions.m_preferred.m_height = 256;
	a_sizeOptions.m_maximum.m_width    = 65536;
	a_sizeOptions.m_maximum.m_height   = 65536;
	//return;

	int childCount = (int)m_children.size();
	if (!childCount)
	{
		// TODO
		// Bottom level widgets that have no children ought to implement sizeOptions
		return;
	}

	SizeOptions childSizes;
	if (m_layoutDirection)
	{
		// horiz
		a_sizeOptions.m_minimum.m_height = 0;
		a_sizeOptions.m_preferred.m_height = 0;
		a_sizeOptions.m_maximum.m_height = 65536;

		a_sizeOptions.m_minimum.m_width = 0;
		a_sizeOptions.m_preferred.m_width = 0;
		a_sizeOptions.m_maximum.m_width = 0;

		for (std::list<Widget*>::const_iterator it = m_children.begin(); it != m_children.end(); ++it)
		{
			(*it)->sizeOptions(childSizes);

			a_sizeOptions.m_minimum.m_width   += childSizes.m_minimum.m_width;
			a_sizeOptions.m_preferred.m_width += childSizes.m_preferred.m_width;
			a_sizeOptions.m_maximum.m_width   += childSizes.m_maximum.m_width;

			a_sizeOptions.m_minimum.m_height    = std::max(a_sizeOptions.m_minimum.m_height, childSizes.m_minimum.m_height);
			a_sizeOptions.m_preferred.m_height += childSizes.m_preferred.m_height;
			a_sizeOptions.m_maximum.m_height    = std::min(a_sizeOptions.m_maximum.m_height, childSizes.m_maximum.m_height);
		}

		a_sizeOptions.m_preferred.m_height /= childCount; // average of preferred heights

		int extraSpaceForMarginsAndSpacing = layoutMargin() * 2;
		a_sizeOptions.m_minimum.m_height   += extraSpaceForMarginsAndSpacing;
		a_sizeOptions.m_preferred.m_height += extraSpaceForMarginsAndSpacing;
		a_sizeOptions.m_maximum.m_height   += extraSpaceForMarginsAndSpacing;

		extraSpaceForMarginsAndSpacing += (childCount - 1) * layoutSpacing();
		a_sizeOptions.m_minimum.m_width   += extraSpaceForMarginsAndSpacing;
		a_sizeOptions.m_preferred.m_width += extraSpaceForMarginsAndSpacing;
		a_sizeOptions.m_maximum.m_width   += extraSpaceForMarginsAndSpacing;

	} else {
		// vert
		a_sizeOptions.m_minimum.m_width = 0;
		a_sizeOptions.m_preferred.m_width = 0;
		a_sizeOptions.m_maximum.m_width = 65536;

		a_sizeOptions.m_minimum.m_height = 0;
		a_sizeOptions.m_preferred.m_height = 0;
		a_sizeOptions.m_maximum.m_height = 0;

		for (std::list<Widget*>::const_iterator it = m_children.begin(); it != m_children.end(); ++it)
		{
			(*it)->sizeOptions(childSizes);

			a_sizeOptions.m_minimum.m_height   += childSizes.m_minimum.m_height;
			a_sizeOptions.m_preferred.m_height += childSizes.m_preferred.m_height;
			a_sizeOptions.m_maximum.m_height   += childSizes.m_maximum.m_height;

			a_sizeOptions.m_minimum.m_width    = std::max(a_sizeOptions.m_minimum.m_width, childSizes.m_minimum.m_width);
			a_sizeOptions.m_preferred.m_width += childSizes.m_preferred.m_width;
			a_sizeOptions.m_maximum.m_width    = std::min(a_sizeOptions.m_maximum.m_width, childSizes.m_maximum.m_width);
		}

		a_sizeOptions.m_preferred.m_width /= childCount; // average of preferred heights

		int extraSpaceForMarginsAndSpacing = layoutMargin() * 2;
		a_sizeOptions.m_minimum.m_width   += extraSpaceForMarginsAndSpacing;
		a_sizeOptions.m_preferred.m_width += extraSpaceForMarginsAndSpacing;
		a_sizeOptions.m_maximum.m_width   += extraSpaceForMarginsAndSpacing;

		extraSpaceForMarginsAndSpacing += (childCount - 1) * layoutSpacing();
		a_sizeOptions.m_minimum.m_height   += extraSpaceForMarginsAndSpacing;
		a_sizeOptions.m_preferred.m_height += extraSpaceForMarginsAndSpacing;
		a_sizeOptions.m_maximum.m_height   += extraSpaceForMarginsAndSpacing;
	}

	// ensure min is less than max and clamp the preferred to the min and max
	if ( a_sizeOptions.m_maximum.m_height < a_sizeOptions.m_minimum.m_height )
		a_sizeOptions.m_maximum.m_height = a_sizeOptions.m_minimum.m_height = (a_sizeOptions.m_maximum.m_height + a_sizeOptions.m_minimum.m_height) / 2;
	if ( a_sizeOptions.m_maximum.m_width < a_sizeOptions.m_minimum.m_width )
		a_sizeOptions.m_maximum.m_width = a_sizeOptions.m_minimum.m_width = (a_sizeOptions.m_maximum.m_width + a_sizeOptions.m_minimum.m_width) / 2;
	if ( a_sizeOptions.m_preferred.m_height < a_sizeOptions.m_minimum.m_height )
		a_sizeOptions.m_preferred.m_height = a_sizeOptions.m_minimum.m_height;
	if ( a_sizeOptions.m_preferred.m_width < a_sizeOptions.m_minimum.m_width )
		a_sizeOptions.m_preferred.m_width = a_sizeOptions.m_minimum.m_width;
	if ( a_sizeOptions.m_preferred.m_height > a_sizeOptions.m_maximum.m_height )
		a_sizeOptions.m_preferred.m_height = a_sizeOptions.m_maximum.m_height;
	if ( a_sizeOptions.m_preferred.m_width > a_sizeOptions.m_maximum.m_width )
		a_sizeOptions.m_preferred.m_width = a_sizeOptions.m_maximum.m_width;
}


void Widget::setGeometry(int a_x, int a_y, int a_width, int a_height)
{
	m_windowX = m_x = a_x;
	m_windowY = m_y = a_y;
	m_goalWidth = a_width;
	m_goalHeight = a_height;
	if (m_parent)
	{
		m_windowX = m_parent->m_windowX + a_x;
		m_windowY = m_parent->m_windowY + a_y;
		updateTarget(); // update target buffer
	}

  // TODO: if a child geometry changes, does that bubble up to the parent?
  // If so, layout updates need to ascend up to the root parent, and then do a full re-layout all the way down

  // TODO: also need to be careful. doing updateLayout will call setGeometry on widgets which will call updateLayout etc.
  // Also in the constructor, it calls setGeometry and it also does this before the widget it added to the parent's children
  // Adding a child widget causes a full re-layout from the root down

  // TODO: setGeometry on the root (ie: window) should re-size the underlying window. possibly need a virtual function for this
	updateLayout();
}


TimerId Widget::startTimer(int a_milliSeconds)
{
	if (m_parent)
		return m_parent->startTimer(a_milliSeconds);
	return 0;
}


void Widget::killTimer(TimerId a_timerId)
{
	if (m_parent)
		m_parent->killTimer(a_timerId);
}


void Widget::eraseBackground(int i)
{
	Painter p(this);
	p.setBrush(backgroundBrush());
#if DEBUG_WIDGET
	printf("draw rect: %i %i %i %i\n", 0, 0, width(), height());
#endif
	p.drawRectangle(0, 0, width(), height());
}


int Widget::width()
{
	return targetBuffer().m_width;
}


int Widget::height()
{
	return targetBuffer().m_height;
}


void Widget::sizeEvent(SizeEvent& a_event)
{
}


void Widget::paintEvent(PaintEvent& a_event)
{
	eraseBackground();
}


void Widget::timerEvent(TimerEvent& a_event)
{
}


void Widget::keyEvent(KeyEvent& a_event)
{
}


void Widget::mouseEvent(MouseEvent& a_event)
{
}


void Widget::mouseEnterEvent(MouseEvent& a_event)
{
}


void Widget::mouseLeaveEvent(MouseEvent& a_event)
{
}


void Widget::wheelEvent(WheelEvent& a_event)
{
}


void Widget::updateTarget()
{
	PixelBuffer& parentTarget = m_parent->targetBuffer();
	m_target.m_width = clamp(parentTarget.m_width - m_x - 1, 0, m_goalWidth);
	m_target.m_height = clamp(parentTarget.m_height - m_y - 1, 0, m_goalHeight);
	m_target.m_pixels = parentTarget.m_pixels + m_y * (parentTarget.m_strideBytes/4) + m_x;
	m_target.m_strideBytes = parentTarget.m_strideBytes;
	m_target.m_format = parentTarget.m_format;
}


void Widget::repaint()
{
	Rectangle rectangle = { { { 0, 0 } }, { { width(), height() } } };
	update(rectangle);
}


void Widget::update(Rectangle& a_rectangle)
{
	if (m_parent)
	{
		a_rectangle.m_x += m_x;
		a_rectangle.m_y += m_y;
		m_parent->update(a_rectangle); // This recursively calculates m_windowX and m_windowY
		// When this gets up to the top level widget it will be a window object with overloaded update function
	}
}


void Widget::event(Event& a_event)
{
	a_event.m_commonEvent.reject();

	switch (a_event.m_type)
	{
		case Event::ET_TimerEvent:
			timerEvent(a_event.m_timerEvent);
			break;
		case Event::ET_PaintEvent:
			if (flags() == WF_EraseBackground)
				eraseBackground();
			paintEvent(a_event.m_paintEvent);
			break;
		case Event::ET_SizeEvent:
			if (m_parent)
				updateTarget();
			sizeEvent(a_event.m_sizeEvent);
			break;
		default:
			break;
	}

	for (std::list<Widget*>::const_iterator it = m_children.begin(); it != m_children.end(); ++it)
	{
#if DEBUG_WIDGET
    printf("event, child class: %s\n", typeid(*(*it)).name());
#endif
		(*it)->event(a_event);
		if (a_event.m_commonEvent.m_accepted)
			return;
	}

	bool mouseInside = false;
	switch (a_event.m_type)
	{
		case Event::ET_MouseEvent:
			mouseInside = ((a_event.m_mouseEvent.m_position.m_x > m_windowX)
				&& (a_event.m_mouseEvent.m_position.m_y > m_windowY)
				&& (a_event.m_mouseEvent.m_position.m_x < (m_windowX + width()))
				&& (a_event.m_mouseEvent.m_position.m_y < (m_windowY + height())));
			if (m_mouseEntered && !mouseInside)
			{
				mouseLeaveEvent(a_event.m_mouseEvent);
			}
			if (!m_mouseEntered && mouseInside)
			{
				mouseEnterEvent(a_event.m_mouseEvent);
			}
			m_mouseEntered = mouseInside;
			if (mouseInside	|| m_watchingMouse )
			{
				a_event.m_mouseEvent.m_x -= m_windowX;
				a_event.m_mouseEvent.m_y -= m_windowY;
				a_event.m_mouseEvent.m_oldX -= m_windowX;
				a_event.m_mouseEvent.m_oldY -= m_windowY;
				if (!a_event.m_mouseEvent.m_oldButtons && a_event.m_mouseEvent.m_buttons)
					m_watchingMouse = true;
				if (!a_event.m_mouseEvent.m_buttons)
					m_watchingMouse = false;
				mouseEvent(a_event.m_mouseEvent);
				a_event.m_mouseEvent.m_x += m_windowX;
				a_event.m_mouseEvent.m_y += m_windowY;
				a_event.m_mouseEvent.m_oldX += m_windowX;
				a_event.m_mouseEvent.m_oldY += m_windowY;
			}
			break;
		case Event::ET_WheelEvent:
			wheelEvent(a_event.m_wheelEvent);
			break;
		case Event::ET_KeyEvent:
			keyEvent(a_event.m_keyEvent);
			break;
		default:
			break;
	}
}


END_NAMESPACE

