/** 
 * @file llscrollcolumnheader.cpp
 * @brief Scroll lists are composed of rows (items), each of which 
 * contains columns (cells).
 *
 * $LicenseInfo:firstyear=2007&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "llscrolllistcolumn.h"

#include "llbutton.h"
#include "llresizebar.h"
#include "llscrolllistcell.h"
#include "llscrolllistctrl.h"
#include "llscrolllistitem.h"
#include "lluictrlfactory.h"

const S32 MIN_COLUMN_WIDTH = 20;

// defaults for LLScrollColumnHeader param block pulled from widgets/scroll_column_header.xml
static LLWidgetNameRegistry::StaticRegistrar sRegisterColumnHeaderParams(&typeid(LLScrollColumnHeader::Params), "scroll_column_header");

//---------------------------------------------------------------------------
// LLScrollColumnHeader
//---------------------------------------------------------------------------
LLScrollColumnHeader::Params::Params()
:	column("column")
{}


LLScrollColumnHeader::LLScrollColumnHeader(const LLScrollColumnHeader::Params& p) 
:	LLButton(p), // use combobox params to steal images
	mColumn(p.column),
	mHasResizableElement(FALSE)
{
	setClickedCallback(boost::bind(&LLScrollColumnHeader::onClick, this, _2));
	
	// resize handles on left and right
	const S32 RESIZE_BAR_THICKNESS = 3;
	LLResizeBar::Params resize_bar_p;
	resize_bar_p.resizing_view(this);
	resize_bar_p.rect(LLRect(getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0));
	resize_bar_p.min_size(MIN_COLUMN_WIDTH);
	resize_bar_p.side(LLResizeBar::RIGHT);
	resize_bar_p.enabled(false);
	mResizeBar = LLUICtrlFactory::create<LLResizeBar>(resize_bar_p);
	addChild(mResizeBar);
}

LLScrollColumnHeader::~LLScrollColumnHeader()
{}

void LLScrollColumnHeader::draw()
{
	std::string sort_column = mColumn->mParentCtrl->getSortColumnName();
	BOOL draw_arrow = !mColumn->mLabel.empty() 
			&& mColumn->mParentCtrl->isSorted()
			// check for indirect sorting column as well as column's sorting name
			&& (sort_column == mColumn->mSortingColumn || sort_column == mColumn->mName);

	BOOL is_ascending = mColumn->mParentCtrl->getSortAscending();
	if (draw_arrow)
	{
		setImageOverlay(is_ascending ? "up_arrow.tga" : "down_arrow.tga", LLFontGL::RIGHT, LLColor4::white);
	}
	else
	{
		setImageOverlay(LLUUID::null);
	}

	// Draw children
	LLButton::draw();
}

BOOL LLScrollColumnHeader::handleDoubleClick(S32 x, S32 y, MASK mask)
{
	if (canResize() && mResizeBar->getRect().pointInRect(x, y))
	{
		// reshape column to max content width
		mColumn->mParentCtrl->calcMaxContentWidth();
		LLRect column_rect = getRect();
		column_rect.mRight = column_rect.mLeft + mColumn->mMaxContentWidth;
		setShape(column_rect, true);
	}
	else
	{
		onClick(LLSD());
	}
	return TRUE;
}

void LLScrollColumnHeader::onClick(const LLSD& data)
{
	if (mColumn)
	{
		LLScrollListCtrl::onClickColumn(mColumn);
	}
}

LLView*	LLScrollColumnHeader::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding)
{
	// this logic assumes dragging on right
	llassert(snap_edge == SNAP_RIGHT);

	// use higher snap threshold for column headers
	threshold = llmin(threshold, 10);

	LLRect snap_rect = getSnapRect();

	mColumn->mParentCtrl->calcMaxContentWidth();

	S32 snap_delta = mColumn->mMaxContentWidth - snap_rect.getWidth();

	// x coord growing means column growing, so same signs mean we're going in right direction
	if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 ) 
	{
		new_edge_val = snap_rect.mRight + snap_delta;
	}
	else 
	{
		LLScrollListColumn* next_column = mColumn->mParentCtrl->getColumn(mColumn->mIndex + 1);
		while (next_column)
		{
			if (next_column->mHeader)
			{
				snap_delta = (next_column->mHeader->getSnapRect().mRight - next_column->mMaxContentWidth) - snap_rect.mRight;
				if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 ) 
				{
					new_edge_val = snap_rect.mRight + snap_delta;
				}
				break;
			}
			next_column = mColumn->mParentCtrl->getColumn(next_column->mIndex + 1);
		}
	}

	return this;
}

void LLScrollColumnHeader::handleReshape(const LLRect& new_rect, bool by_user)
{
	S32 new_width = new_rect.getWidth();
	S32 delta_width = new_width - (getRect().getWidth() /*+ mColumn->mParentCtrl->getColumnPadding()*/);

	if (delta_width != 0)
	{
		S32 remaining_width = -delta_width;
		S32 col;
		for (col = mColumn->mIndex + 1; col < mColumn->mParentCtrl->getNumColumns(); col++)
		{
			LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
			if (!columnp) continue;

			if (columnp->mHeader && columnp->mHeader->canResize())
			{
				// how many pixels in width can this column afford to give up?
				S32 resize_buffer_amt = llmax(0, columnp->getWidth() - MIN_COLUMN_WIDTH);
				
				// user shrinking column, need to add width to other columns
				if (delta_width < 0)
				{
					if (columnp->getWidth() > 0)
					{
						// statically sized column, give all remaining width to this column
						columnp->setWidth(columnp->getWidth() + remaining_width);
						if (columnp->mRelWidth > 0.f)
						{
							columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
						}
						// all padding went to this widget, we're done
						break;
					}
				}
				else
				{
					// user growing column, need to take width from other columns
					remaining_width += resize_buffer_amt;

					if (columnp->getWidth() > 0)
					{
						columnp->setWidth(columnp->getWidth() - llmin(columnp->getWidth() - MIN_COLUMN_WIDTH, delta_width));
						if (columnp->mRelWidth > 0.f)
						{
							columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
						}
					}

					if (remaining_width >= 0)
					{
						// width sucked up from neighboring columns, done
						break;
					}
				}
			}
		}

		// clamp resize amount to maximum that can be absorbed by other columns
		if (delta_width > 0)
		{
			delta_width += llmin(remaining_width, 0);
		}

		// propagate constrained delta_width to new width for this column
		new_width = getRect().getWidth() + delta_width - mColumn->mParentCtrl->getColumnPadding();

		// use requested width
		mColumn->setWidth(new_width);

		// update proportional spacing
		if (mColumn->mRelWidth > 0.f)
		{
			mColumn->mRelWidth = (F32)new_width / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
		}

		// tell scroll list to layout columns again
		// do immediate update to get proper feedback to resize handle
		// which needs to know how far the resize actually went
		const bool force_update = true;
		mColumn->mParentCtrl->updateColumns(force_update);
	}
}

void LLScrollColumnHeader::setHasResizableElement(BOOL resizable)
{
	if (mHasResizableElement != resizable)
	{
		mColumn->mParentCtrl->dirtyColumns();
		mHasResizableElement = resizable;
	}
}

void LLScrollColumnHeader::updateResizeBars()
{
	S32 num_resizable_columns = 0;
	S32 col;
	for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
	{
		LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
		if (columnp && columnp->mHeader && columnp->mHeader->canResize())
		{
			num_resizable_columns++;
		}
	}

	S32 num_resizers_enabled = 0;

	// now enable/disable resize handles on resizable columns if we have at least two
	for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
	{
		LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
		if (!columnp || !columnp->mHeader) continue;
		BOOL enable = num_resizable_columns >= 2 && num_resizers_enabled < (num_resizable_columns - 1) && columnp->mHeader->canResize();
		columnp->mHeader->enableResizeBar(enable);
		if (enable)
		{
			num_resizers_enabled++;
		}
	}
}

void LLScrollColumnHeader::enableResizeBar(BOOL enable)
{
	mResizeBar->setEnabled(enable);
}

BOOL LLScrollColumnHeader::canResize()
{
	return getVisible() && (mHasResizableElement || mColumn->mDynamicWidth);
}

void LLScrollListColumn::SortNames::declareValues()
{
	declare("ascending", LLScrollListColumn::ASCENDING);
	declare("descending", LLScrollListColumn::DESCENDING);
}

//
// LLScrollListColumn
//
//static 
const LLScrollListColumn::Params& LLScrollListColumn::getDefaultParams()
{
	return LLUICtrlFactory::getDefaultParams<LLScrollListColumn>();
}


LLScrollListColumn::LLScrollListColumn(const Params& p, LLScrollListCtrl* parent)
:	mWidth(0),
	mIndex (-1),
	mParentCtrl(parent),
	mName(p.name),
	mLabel(p.header.label),
	mHeader(NULL),
	mMaxContentWidth(0),
	mDynamicWidth(p.width.dynamic_width),
	mRelWidth(p.width.relative_width),
	mFontAlignment(p.halign),
	mSortingColumn(p.sort_column)
{
	if (p.sort_ascending.isProvided())
	{
		mSortDirection = p.sort_ascending() ? ASCENDING : DESCENDING;
	}
	else
	{
		mSortDirection = p.sort_direction;
	}

	setWidth(p.width.pixel_width);
}

void LLScrollListColumn::setWidth(S32 width) 
{ 
	if (!mDynamicWidth && mRelWidth <= 0.f) 
	{
		mParentCtrl->updateStaticColumnWidth(this, width);
	}
	mWidth = width;
}