/*
 * This source file is part of libRocket, the HTML/CSS Interface Middleware
 *
 * For the latest information, see http://www.librocket.com
 *
 * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

#include "precompiled.h"
#include "LayoutBlockBox.h"
#include "LayoutBlockBoxSpace.h"
#include "LayoutEngine.h"
#include "../../Include/Rocket/Core/Element.h"
#include "../../Include/Rocket/Core/ElementUtilities.h"
#include "../../Include/Rocket/Core/ElementScroll.h"
#include "../../Include/Rocket/Core/Property.h"
#include "../../Include/Rocket/Core/StyleSheetKeywords.h"

namespace Rocket {
namespace Core {

// Creates a new block box for rendering a block element.
LayoutBlockBox::LayoutBlockBox(LayoutEngine* _layout_engine, LayoutBlockBox* _parent, Element* _element) : position(0, 0)
{
	space = new LayoutBlockBoxSpace(this);

	layout_engine = _layout_engine;
	parent = _parent;

	context = BLOCK;
	element = _element;
	interrupted_chain = NULL;

	box_cursor = 0;
	vertical_overflow = false;

	// Get our offset root from our parent, if it has one; otherwise, our element is the offset parent.
	if (parent != NULL &&
		parent->offset_root->GetElement() != NULL)
		offset_root = parent->offset_root;
	else
		offset_root = this;

	// Determine the offset parent for this element.
	LayoutBlockBox* self_offset_parent;
	if (parent != NULL &&
		parent->offset_parent->GetElement() != NULL)
		self_offset_parent = parent->offset_parent;
	else
		self_offset_parent = this;

	// Determine the offset parent for our children.
	if (parent != NULL &&
		parent->offset_parent->GetElement() != NULL &&
		(element == NULL || element->GetPosition() == POSITION_STATIC))
		offset_parent = parent->offset_parent;
	else
		offset_parent = this;

	// Build the box for our element, and position it if we can.
	if (parent != NULL)
	{
		space->ImportSpace(*parent->space);

		// Build our box if possible; if not, it will have to be set up manually.
		layout_engine->BuildBox(box, min_height, max_height, parent, element);

		// Position ourselves within our containing block (if we have a valid offset parent).
		if (parent->GetElement() != NULL)
		{
			if (self_offset_parent != this)
			{
				// Get the next position within our offset parent's containing block.
				parent->PositionBlockBox(position, box, element->GetProperty< int >(CLEAR));
				element->SetOffset(position - (self_offset_parent->GetPosition() - offset_root->GetPosition()), self_offset_parent->GetElement());
			}
			else
				element->SetOffset(position, NULL);
		}
	}

	if (element != NULL)
	{
		wrap_content = element->GetWhitespace() != WHITE_SPACE_NOWRAP;

		// Determine if this element should have scrollbars or not, and create them if so.
		element->GetOverflow(&overflow_x_property, &overflow_y_property);

		if (overflow_x_property == OVERFLOW_SCROLL)
			element->GetElementScroll()->EnableScrollbar(ElementScroll::HORIZONTAL, box.GetSize(Box::PADDING).x);
		else
			element->GetElementScroll()->DisableScrollbar(ElementScroll::HORIZONTAL);

		if (overflow_y_property == OVERFLOW_SCROLL)
			element->GetElementScroll()->EnableScrollbar(ElementScroll::VERTICAL, box.GetSize(Box::PADDING).x);
		else
			element->GetElementScroll()->DisableScrollbar(ElementScroll::VERTICAL);
	}
	else
	{
		wrap_content = true;
		overflow_x_property = OVERFLOW_VISIBLE;
		overflow_y_property = OVERFLOW_VISIBLE;
	}
}

// Creates a new block box in an inline context.
LayoutBlockBox::LayoutBlockBox(LayoutEngine* _layout_engine, LayoutBlockBox* _parent) : position(-1, -1)
{
	layout_engine = _layout_engine;
	parent = _parent;
	offset_parent = parent->offset_parent;
	offset_root = parent->offset_root;

	space = _parent->space;

	context = INLINE;
	line_boxes.push_back(new LayoutLineBox(this));
	wrap_content = parent->wrap_content;

	element = NULL;
	interrupted_chain = NULL;

	box_cursor = 0;
	vertical_overflow = false;

	layout_engine->BuildBox(box, min_height, max_height, parent, NULL);
	parent->PositionBlockBox(position, box, 0);
	box.SetContent(Vector2f(box.GetSize(Box::CONTENT).x, -1));

	// Reset the min and max heights; they're not valid for inline block boxes.
	min_height = 0;
	max_height = FLT_MAX;
}

// Releases the block box.
LayoutBlockBox::~LayoutBlockBox()
{
	for (size_t i = 0; i < block_boxes.size(); i++)
		delete block_boxes[i];

	for (size_t i = 0; i < line_boxes.size(); i++)
		delete line_boxes[i];

	if (context == BLOCK)
		delete space;
}

// Closes the box.
LayoutBlockBox::CloseResult LayoutBlockBox::Close()
{
	// If the last child of this block box is an inline box, then we haven't closed it; close it now!
	if (context == BLOCK)
	{
		CloseResult result = CloseInlineBlockBox();
		if (result != OK)
			return LAYOUT_SELF;
	}
	// Otherwise, we're an inline context box; so close our last line, which will still be open.
	else
	{
		line_boxes.back()->Close();

		// Expand our content area if any line boxes had to push themselves out.
		Vector2f content_area = box.GetSize();
		for (size_t i = 0; i < line_boxes.size(); i++)
			content_area.x = Math::Max(content_area.x, line_boxes[i]->GetDimensions().x);

		box.SetContent(content_area);
	}

	// Set this box's height, if necessary.
	if (box.GetSize(Box::CONTENT).y < 0)
	{
		Vector2f content_area = box.GetSize();
		content_area.y = Math::Clamp(box_cursor, min_height, max_height);

		if (element != NULL)
			content_area.y = Math::Max(content_area.y, space->GetDimensions().y);

		box.SetContent(content_area);
	}

	// Set the computed box on the element.
	if (element != NULL)
	{
		if (context == BLOCK)
		{
			// Calculate the dimensions of the box's *internal* content; this is the tightest-fitting box around all of the
			// internal elements, plus this element's padding.
			Vector2f content_box(0, 0);

			for (size_t i = 0; i < block_boxes.size(); i++)
				content_box.x = Math::Max(content_box.x, block_boxes[i]->GetBox().GetSize(Box::MARGIN).x);

			// Check how big our floated area is.
			Vector2f space_box = space->GetDimensions();
			content_box.x = Math::Max(content_box.x, space_box.x);

			// If our content is larger than our window, then we can either extend our box, if we're set to not wrap
			// our inline content, or enable the horizontal scrollbar, if we're set to auto-scrollbars. If we're set to
			// always use scrollbars, then the horiontal scrollbar will already have been enabled in the constructor.
			if (content_box.x > box.GetSize().x)
			{
				if (!wrap_content)
					box.SetContent(Vector2f(content_box.x, box.GetSize().y));
				else if (overflow_x_property == OVERFLOW_AUTO)
				{
					element->GetElementScroll()->EnableScrollbar(ElementScroll::HORIZONTAL, box.GetSize(Box::PADDING).x);

					if (!CatchVerticalOverflow())
						return LAYOUT_SELF;
				}
			}

			content_box.x += (box.GetEdge(Box::PADDING, Box::LEFT) + box.GetEdge(Box::PADDING, Box::RIGHT));

			content_box.y = box_cursor;
			content_box.y = Math::Max(content_box.y, space_box.y);
			if (!CatchVerticalOverflow(content_box.y))
				return LAYOUT_SELF;

			content_box.y += (box.GetEdge(Box::PADDING, Box::TOP) + box.GetEdge(Box::PADDING, Box::BOTTOM));

			element->SetBox(box);
			element->SetContentBox(space->GetOffset(), content_box);

			// Format any scrollbars which were enabled on this element.
			element->GetElementScroll()->FormatScrollbars();
		}
		else
			element->SetBox(box);
	}

	// Increment the parent's cursor.
	if (parent != NULL)
	{
		// If this close fails, it means this block box has caused our parent block box to generate an automatic
		// vertical scrollbar.
		if (!parent->CloseBlockBox(this))
			return LAYOUT_PARENT;
	}

	// If we represent a positioned element, then we can now (as we've been sized) act as the containing block for all
	// the absolutely-positioned elements of our descendants.
	if (context == BLOCK &&
		element != NULL)
	{
		if (element->GetPosition() != POSITION_STATIC)
			CloseAbsoluteElements();
	}

	return OK;
}

// Called by a closing block box child.
bool LayoutBlockBox::CloseBlockBox(LayoutBlockBox* child)
{
	ROCKET_ASSERT(context == BLOCK);
	box_cursor = (child->GetPosition().y - child->box.GetEdge(Box::MARGIN, Box::TOP) - (box.GetPosition().y + position.y)) + child->GetBox().GetSize(Box::MARGIN).y;

	return CatchVerticalOverflow();
}

// Called by a closing line box child.
LayoutInlineBox* LayoutBlockBox::CloseLineBox(LayoutLineBox* child, LayoutInlineBox* overflow, LayoutInlineBox* overflow_chain)
{
	ROCKET_ASSERT(context == INLINE);
	if (child->GetDimensions().x > 0)
		box_cursor = (child->GetPosition().y - (box.GetPosition().y + position.y)) + child->GetDimensions().y;

	// If we have any pending floating elements for our parent, then this would be an ideal time to position them.
	if (!float_elements.empty())
	{
		for (size_t i = 0; i < float_elements.size(); ++i)
			parent->PositionFloat(float_elements[i], box_cursor);

		float_elements.clear();
	}

	// Add a new line box.
	line_boxes.push_back(new LayoutLineBox(this));

	if (overflow_chain != NULL)
		line_boxes.back()->AddChainedBox(overflow_chain);

	if (overflow != NULL)
		return line_boxes.back()->AddBox(overflow);

	return NULL;
}

// Adds a new block element to this block box.
LayoutBlockBox* LayoutBlockBox::AddBlockElement(Element* element)
{
	ROCKET_ASSERT(context == BLOCK);

	// Check if our most previous block box is rendering in an inline context.
	if (!block_boxes.empty() &&
		block_boxes.back()->context == INLINE)
	{
		LayoutBlockBox* inline_block_box = block_boxes.back();
		LayoutInlineBox* open_inline_box = inline_block_box->line_boxes.back()->GetOpenInlineBox();
		if (open_inline_box != NULL)
		{
			// There's an open inline box chain, which means this block element is parented to it. The chain needs to
			// be positioned (if it hasn't already), closed and duplicated after this block box closes. Also, this
			// block needs to be aware of its parentage, so it can correctly compute its relative position. First of
			// all, we need to close the inline box; this will position the last line if necessary, but it will also
			// create a new line in the inline block box; we want this line to be in an inline box after our block
			// element.
			if (inline_block_box->Close() != OK)
				return NULL;

			interrupted_chain = open_inline_box;
		}
		else
		{
			// There are no open inline boxes, so this inline box just needs to be closed.
			if (CloseInlineBlockBox() != OK)
				return NULL;
		}
	}

	block_boxes.push_back(new LayoutBlockBox(layout_engine, this, element));
	return block_boxes.back();
}

// Adds a new inline element to this inline box.
LayoutInlineBox* LayoutBlockBox::AddInlineElement(Element* element, const Box& box)
{
	if (context == BLOCK)
	{
		LayoutInlineBox* inline_box;

		// If we have an open child rendering in an inline context, we can add this element into it.
		if (!block_boxes.empty() &&
			block_boxes.back()->context == INLINE)
			inline_box = block_boxes.back()->AddInlineElement(element, box);

		// No dice! Ah well, nothing for it but to open a new inline context block box.
		else
		{
			block_boxes.push_back(new LayoutBlockBox(layout_engine, this));

			if (interrupted_chain != NULL)
			{
				block_boxes.back()->line_boxes.back()->AddChainedBox(interrupted_chain);
				interrupted_chain = NULL;
			}

			inline_box = block_boxes.back()->AddInlineElement(element, box);
		}

		return inline_box;
	}
	else
	{
		// We're an inline context box, so we'll add this new inline element into our line boxes.
		return line_boxes.back()->AddElement(element, box);
	}
}

// Adds a line-break to this block box.
void LayoutBlockBox::AddBreak()
{
	int line_height = ElementUtilities::GetLineHeight(element);

	// Check for an inline box as our last child; if so, we can simply end its line and bail.
	if (!block_boxes.empty())
	{
		LayoutBlockBox* block_box = block_boxes.back();
		if (block_box->context == INLINE)
		{
			LayoutLineBox* last_line = block_box->line_boxes.back();
			if (last_line->GetDimensions().y < 0)
				block_box->box_cursor += line_height;
			else
				last_line->Close();

			return;
		}
	}

	// No inline box as our last child; no problem, just increment the cursor by the line height of this element.
	box_cursor += line_height;
}

// Adds an element to this block box to be handled as a floating element.
bool LayoutBlockBox::AddFloatElement(Element* element)
{
	// If we have an open inline block box, then we have to position the box a little differently.
	if (!block_boxes.empty() &&
		block_boxes.back()->context == INLINE)
		block_boxes.back()->float_elements.push_back(element);

	// Nope ... just place it!
	else
		PositionFloat(element);

	return true;
}

// Adds an element to this block box to be handled as an absolutely-positioned element.
void LayoutBlockBox::AddAbsoluteElement(Element* element)
{
	ROCKET_ASSERT(context == BLOCK);

	AbsoluteElement absolute_element;
	absolute_element.element = element;

	PositionBox(absolute_element.position, 0);

	// If we have an open inline-context block box as our last child, then the absolute element must appear after it,
	// but not actually close the box.
	if (!block_boxes.empty()
		&& block_boxes.back()->context == INLINE)
	{
		LayoutBlockBox* inline_context_box = block_boxes.back();
		float last_line_height = inline_context_box->line_boxes.back()->GetDimensions().y;

		absolute_element.position.y += (inline_context_box->box_cursor + Math::Max(0.0f, last_line_height));
	}

	// Find the positioned parent for this element.
	LayoutBlockBox* absolute_parent = this;
	while (absolute_parent != absolute_parent->offset_parent)
		absolute_parent = absolute_parent->parent;

	absolute_parent->absolute_elements.push_back(absolute_element);
}

// Lays out, sizes, and positions all absolute elements in this block relative to the containing block.
void LayoutBlockBox::CloseAbsoluteElements()
{
	if (!absolute_elements.empty())
	{
		// The size of the containing box, including the padding. This is used to resolve relative offsets.
		Vector2f containing_block = GetBox().GetSize(Box::PADDING);

		for (size_t i = 0; i < absolute_elements.size(); i++)
		{
			Element* absolute_element = absolute_elements[i].element;
			Vector2f absolute_position = absolute_elements[i].position;
			absolute_position -= position - offset_root->GetPosition();

			// Lay out the element.
			LayoutEngine layout_engine;
			layout_engine.FormatElement(absolute_element, containing_block);

			// Now that the element's box has been built, we can offset the position we determined was appropriate for
			// it by the element's margin. This is necessary because the coordinate system for the box begins at the
			// border, not the margin.
			absolute_position.x += absolute_element->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
			absolute_position.y += absolute_element->GetBox().GetEdge(Box::MARGIN, Box::TOP);

			// Set the offset of the element; the element itself will take care of any RCSS-defined positional offsets.
			absolute_element->SetOffset(absolute_position, element);
		}

		absolute_elements.clear();
	}
}

// Returns the offset from the top-left corner of this box that the next child box will be positioned at.
void LayoutBlockBox::PositionBox(Vector2f& box_position, float top_margin, int clear_property) const
{
	// If our element is establishing a new offset hierarchy, then any children of ours don't inherit our offset.
	box_position = GetPosition();
	box_position += box.GetPosition();
	box_position.y += box_cursor;

	float clear_margin = space->ClearBoxes(box_position.y + top_margin, clear_property) - (box_position.y + top_margin);
	if (clear_margin > 0)
		box_position.y += clear_margin;
	else
	{
		// Check for a collapsing vertical margin.
		if (!block_boxes.empty() &&
			block_boxes.back()->context == BLOCK)
		{
			float bottom_margin = block_boxes.back()->GetBox().GetEdge(Box::MARGIN, Box::BOTTOM);
			box_position.y -= Math::Min(top_margin, bottom_margin);
		}
	}
}

// Returns the offset from the top-left corner of this box's offset element the next child block box, of the given
// dimensions, will be positioned at. This will include the margins on the new block box.
void LayoutBlockBox::PositionBlockBox(Vector2f& box_position, const Box& box, int clear_property) const
{
	PositionBox(box_position, box.GetEdge(Box::MARGIN, Box::TOP), clear_property);
	box_position.x += box.GetEdge(Box::MARGIN, Box::LEFT);
	box_position.y += box.GetEdge(Box::MARGIN, Box::TOP);

	LayoutEngine::Round(box_position);
}

// Returns the offset from the top-left corner of this box for the next line.
void LayoutBlockBox::PositionLineBox(Vector2f& box_position, float& box_width, bool& _wrap_content, const Vector2f& dimensions) const
{
	Vector2f cursor;
	PositionBox(cursor);

	space->PositionBox(box_position, box_width, cursor.y, dimensions);

	// Also, probably shouldn't check for widths when positioning the box?
	_wrap_content = wrap_content;
}

// Returns the block box's element.
Element* LayoutBlockBox::GetElement() const
{
	return element;
}

// Returns the block box's parent.
LayoutBlockBox* LayoutBlockBox::GetParent() const
{
	return parent;
}

// Returns the position of the block box, relative to its parent's content area.
const Vector2f& LayoutBlockBox::GetPosition() const
{
	return position;
}

// Returns the element against which all positions of boxes in the hierarchy are calculated relative to.
LayoutBlockBox* LayoutBlockBox::GetOffsetParent() const
{
	return offset_parent;
}

// Returns the block box against which all positions of boxes in the hierarchy are calculated relative to.
LayoutBlockBox* LayoutBlockBox::GetOffsetRoot() const
{
	return offset_root;
}

// Returns the block box's dimension box.
Box& LayoutBlockBox::GetBox()
{
	return box;
}

// Returns the block box's dimension box.
const Box& LayoutBlockBox::GetBox() const
{
	return box;
}

void* LayoutBlockBox::operator new(size_t size)
{
	void* memory = LayoutEngine::AllocateLayoutChunk(size);
	return memory;
}

void LayoutBlockBox::operator delete(void* chunk)
{
	LayoutEngine::DeallocateLayoutChunk(chunk);
}

// Closes our last block box, if it is an open inline block box.
LayoutBlockBox::CloseResult LayoutBlockBox::CloseInlineBlockBox()
{
	if (!block_boxes.empty() &&
		block_boxes.back()->context == INLINE)
		return block_boxes.back()->Close();

	return OK;
}

// Positions a floating element within this block box.
void LayoutBlockBox::PositionFloat(Element* element, float offset)
{
	Vector2f box_position;
	PositionBox(box_position);

	space->PositionBox(box_position.y + offset, element);
}

// Checks if we have a new vertical overflow on an auto-scrolling element.
bool LayoutBlockBox::CatchVerticalOverflow(float cursor)
{
	if (cursor == -1)
		cursor = box_cursor;

	float box_height = box.GetSize().y;
	if (box_height < 0)
		box_height = max_height;

	// If we're auto-scrolling and our height is fixed, we have to check if this box has exceeded our client height.
	if (!vertical_overflow &&
		box_height >= 0 &&
		overflow_y_property == OVERFLOW_AUTO)
	{
		if (cursor > box_height - element->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL))
		{
			vertical_overflow = true;
			element->GetElementScroll()->EnableScrollbar(ElementScroll::VERTICAL, box.GetSize(Box::PADDING).x);

			for (size_t i = 0; i < block_boxes.size(); i++)
				delete block_boxes[i];
			block_boxes.clear();

			delete space;
			space = new LayoutBlockBoxSpace(this);

			box_cursor = 0;
			interrupted_chain = NULL;

			return false;
		}
	}

	return true;
}

}
}
