/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "SkStackViewLayout.h"

SkStackViewLayout::SkStackViewLayout()
{
	fMargin.set(0, 0, 0, 0);
	fSpacer	= 0;
	fOrient	= kHorizontal_Orient;
	fPack	= kStart_Pack;
	fAlign	= kStart_Align;
	fRound	= false;
}

void SkStackViewLayout::setOrient(Orient ori)
{
	SkASSERT((unsigned)ori < kOrientCount);
	fOrient = SkToU8(ori);
}

void SkStackViewLayout::getMargin(SkRect* margin) const
{
	if (margin)
		*margin = fMargin;
}

void SkStackViewLayout::setMargin(const SkRect& margin)
{
	fMargin = margin;
}

void SkStackViewLayout::setSpacer(SkScalar spacer)
{
	fSpacer = spacer;
}

void SkStackViewLayout::setPack(Pack pack)
{
	SkASSERT((unsigned)pack < kPackCount);
	fPack = SkToU8(pack);
}

void SkStackViewLayout::setAlign(Align align)
{
	SkASSERT((unsigned)align < kAlignCount);
	fAlign = SkToU8(align);
}

void SkStackViewLayout::setRound(bool r)
{
	fRound = SkToU8(r);
}

////////////////////////////////////////////////////////////////////////////////

typedef SkScalar (*AlignProc)(SkScalar childLimit, SkScalar parentLimit);
typedef SkScalar (SkView::*GetSizeProc)() const;
typedef void (SkView::*SetLocProc)(SkScalar coord);
typedef void (SkView::*SetSizeProc)(SkScalar coord);

static SkScalar left_align_proc(SkScalar childLimit, SkScalar parentLimit) { return 0; }
static SkScalar center_align_proc(SkScalar childLimit, SkScalar parentLimit) { return SkScalarHalf(parentLimit - childLimit); }
static SkScalar right_align_proc(SkScalar childLimit, SkScalar parentLimit) { return parentLimit - childLimit; }
static SkScalar fill_align_proc(SkScalar childLimit, SkScalar parentLimit) { return 0; }

/*	Measure the main-dimension for all the children. If a child is marked flex in that direction
	ignore its current value but increment the counter for flexChildren
*/
static SkScalar compute_children_limit(SkView* parent, GetSizeProc sizeProc, int* count,
									   uint32_t flexMask, int* flexCount)
{
	SkView::B2FIter	iter(parent);
	SkView*			child;
	SkScalar		limit = 0;
	int				n = 0, flex = 0;

	while ((child = iter.next()) != NULL)
	{
		n += 1;
		if (child->getFlags() & flexMask)
			flex += 1;
		else
			limit += (child->*sizeProc)();
	}
	if (count)
		*count = n;
	if (flexCount)
		*flexCount = flex;
	return limit;
}

void SkStackViewLayout::onLayoutChildren(SkView* parent)
{
	static AlignProc gAlignProcs[] = {
		left_align_proc,
		center_align_proc,
		right_align_proc,
		fill_align_proc
	};

	SkScalar			startM, endM, crossStartM, crossLimit;
	GetSizeProc			mainGetSizeP, crossGetSizeP;
	SetLocProc			mainLocP, crossLocP;
	SetSizeProc			mainSetSizeP, crossSetSizeP;
	SkView::Flag_Mask	flexMask;

	if (fOrient == kHorizontal_Orient)
	{
		startM		= fMargin.fLeft;
		endM		= fMargin.fRight;
		crossStartM	= fMargin.fTop;
		crossLimit	= -fMargin.fTop - fMargin.fBottom;

		mainGetSizeP	= &SkView::width;
		crossGetSizeP	= &SkView::height;
		mainLocP	= &SkView::setLocX;
		crossLocP	= &SkView::setLocY;

		mainSetSizeP  = &SkView::setWidth;
		crossSetSizeP = &SkView::setHeight;

		flexMask	= SkView::kFlexH_Mask;
	}
	else
	{
		startM		= fMargin.fTop;
		endM		= fMargin.fBottom;
		crossStartM	= fMargin.fLeft;
		crossLimit	= -fMargin.fLeft - fMargin.fRight;

		mainGetSizeP	= &SkView::height;
		crossGetSizeP	= &SkView::width;
		mainLocP	= &SkView::setLocY;
		crossLocP	= &SkView::setLocX;

		mainSetSizeP  = &SkView::setHeight;
		crossSetSizeP = &SkView::setWidth;

		flexMask	= SkView::kFlexV_Mask;
	}
	crossLimit += (parent->*crossGetSizeP)();
	if (fAlign != kStretch_Align)
		crossSetSizeP = NULL;

	int			childCount, flexCount;
	SkScalar	childLimit = compute_children_limit(parent, mainGetSizeP, &childCount, flexMask, &flexCount);

	if (childCount == 0)
		return;

	childLimit += (childCount - 1) * fSpacer;

	SkScalar		parentLimit = (parent->*mainGetSizeP)() - startM - endM;
	SkScalar		pos = startM + gAlignProcs[fPack](childLimit, parentLimit);
	SkScalar		flexAmount = 0;
	SkView::B2FIter	iter(parent);
	SkView*			child;

	if (flexCount > 0 && parentLimit > childLimit)
		flexAmount = (parentLimit - childLimit) / flexCount;

	while ((child = iter.next()) != NULL)
	{
		if (fRound)
			pos = SkIntToScalar(SkScalarRound(pos));
		(child->*mainLocP)(pos);
		SkScalar crossLoc = crossStartM + gAlignProcs[fAlign]((child->*crossGetSizeP)(), crossLimit);
		if (fRound)
			crossLoc = SkIntToScalar(SkScalarRound(crossLoc));
		(child->*crossLocP)(crossLoc);

		if (crossSetSizeP)
			(child->*crossSetSizeP)(crossLimit);
		if (child->getFlags() & flexMask)
			(child->*mainSetSizeP)(flexAmount);
		pos += (child->*mainGetSizeP)() + fSpacer;
	}
}

//////////////////////////////////////////////////////////////////////////////////////

#ifdef SK_DEBUG
	static void assert_no_attr(const SkDOM& dom, const SkDOM::Node* node, const char attr[])
	{
		const char* value = dom.findAttr(node, attr);
		if (value)
			SkDebugf("unknown attribute %s=\"%s\"\n", attr, value);
	}
#else
	#define assert_no_attr(dom, node, attr)
#endif

void SkStackViewLayout::onInflate(const SkDOM& dom, const SkDOM::Node* node)
{
	int			index;
	SkScalar	value[4];

	if ((index = dom.findList(node, "orient", "horizontal,vertical")) >= 0)
		this->setOrient((Orient)index);
	else {
		assert_no_attr(dom, node, "orient");
        }

	if (dom.findScalars(node, "margin", value, 4))
	{
		SkRect	margin;
		margin.set(value[0], value[1], value[2], value[3]);
		this->setMargin(margin);
	}
	else {
		assert_no_attr(dom, node, "margin");
        }

	if (dom.findScalar(node, "spacer", value))
		this->setSpacer(value[0]);
	else {
		assert_no_attr(dom, node, "spacer");
        }

	if ((index = dom.findList(node, "pack", "start,center,end")) >= 0)
		this->setPack((Pack)index);
	else {
		assert_no_attr(dom, node, "pack");
        }

	if ((index = dom.findList(node, "align", "start,center,end,stretch")) >= 0)
		this->setAlign((Align)index);
	else {
		assert_no_attr(dom, node, "align");
        }
}

///////////////////////////////////////////////////////////////////////////////////////////

SkFillViewLayout::SkFillViewLayout()
{
	fMargin.setEmpty();
}

void SkFillViewLayout::getMargin(SkRect* r) const
{
	if (r)
		*r = fMargin;
}

void SkFillViewLayout::setMargin(const SkRect& margin)
{
	fMargin = margin;
}

void SkFillViewLayout::onLayoutChildren(SkView* parent)
{
	SkView::B2FIter	iter(parent);
	SkView*			child;

	while ((child = iter.next()) != NULL)
	{
		child->setLoc(fMargin.fLeft, fMargin.fTop);
		child->setSize(	parent->width() - fMargin.fRight - fMargin.fLeft,
						parent->height() - fMargin.fBottom - fMargin.fTop);
	}
}

void SkFillViewLayout::onInflate(const SkDOM& dom, const SkDOM::Node* node)
{
	this->INHERITED::onInflate(dom, node);
	(void)dom.findScalars(node, "margin", (SkScalar*)&fMargin, 4);
}