/** 
 * @file llradiogroup.cpp
 * @brief LLRadioGroup base class
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 * 
 * Copyright (c) 2001-2008, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */


#include "linden_common.h"

#include "llboost.h"

#include "llradiogroup.h"
#include "indra_constants.h"

#include "llviewborder.h"
#include "llcontrol.h"
#include "llui.h"
#include "llfocusmgr.h"

static LLRegisterWidget<LLRadioGroup> r("radio_group");

LLRadioGroup::LLRadioGroup(const std::string& name, const LLRect& rect,
						   const std::string& control_name,
						   LLUICtrlCallback callback,
						   void* userdata,
						   BOOL border)
:	LLUICtrl(name, rect, TRUE, callback, userdata, FOLLOWS_LEFT | FOLLOWS_TOP),
	mSelectedIndex(0)
{
	setControlName(control_name, NULL);
	init(border);
}

LLRadioGroup::LLRadioGroup(const std::string& name, const LLRect& rect,
						   S32 initial_index,
						   LLUICtrlCallback callback,
						   void* userdata,
						   BOOL border) :
	LLUICtrl(name, rect, TRUE, callback, userdata, FOLLOWS_LEFT | FOLLOWS_TOP),
	mSelectedIndex(initial_index)
{
	init(border);
}

void LLRadioGroup::init(BOOL border)
{
	if (border)
	{
		addChild( new LLViewBorder( std::string("radio group border"), 
									LLRect(0, getRect().getHeight(), getRect().getWidth(), 0), 
									LLViewBorder::BEVEL_NONE, 
									LLViewBorder::STYLE_LINE, 
									1 ) );
	}
	mHasBorder = border;
}




LLRadioGroup::~LLRadioGroup()
{
}


// virtual
void LLRadioGroup::setEnabled(BOOL enabled)
{
	for (child_list_const_iter_t child_iter = getChildList()->begin();
		 child_iter != getChildList()->end(); ++child_iter)
	{
		LLView *child = *child_iter;
		child->setEnabled(enabled);
	}
	LLView::setEnabled(enabled);
}

void LLRadioGroup::setIndexEnabled(S32 index, BOOL enabled)
{
	S32 count = 0;
	for (button_list_t::iterator iter = mRadioButtons.begin();
		 iter != mRadioButtons.end(); ++iter)
	{
		LLRadioCtrl* child = *iter;
		if (count == index)
		{
			child->setEnabled(enabled);
			if (index == mSelectedIndex && enabled == FALSE)
			{
				setSelectedIndex(-1);
			}
			break;
		}
		count++;
	}
	count = 0;
	if (mSelectedIndex < 0)
	{
		// Set to highest enabled value < index,
		// or lowest value above index if none lower are enabled
		// or 0 if none are enabled
		for (button_list_t::iterator iter = mRadioButtons.begin();
			 iter != mRadioButtons.end(); ++iter)
		{
			LLRadioCtrl* child = *iter;
			if (count >= index && mSelectedIndex >= 0)
			{
				break;
			}
			if (child->getEnabled())
			{
				setSelectedIndex(count);
			}
			count++;
		}
		if (mSelectedIndex < 0)
		{
			setSelectedIndex(0);
		}
	}
}

BOOL LLRadioGroup::setSelectedIndex(S32 index, BOOL from_event)
{
	if (index < 0 || index >= (S32)mRadioButtons.size())
	{
		return FALSE;
	}

	mSelectedIndex = index;

	if (!from_event)
	{
		setControlValue(getSelectedIndex());
	}

	return TRUE;
}

BOOL LLRadioGroup::handleKeyHere(KEY key, MASK mask)
{
	BOOL handled = FALSE;
	// do any of the tab buttons have keyboard focus?
	if (mask == MASK_NONE)
	{
		switch(key)
		{
		case KEY_DOWN:
			if (!setSelectedIndex((getSelectedIndex() + 1)))
			{
				make_ui_sound("UISndInvalidOp");
			}
			else
			{
				onCommit();
			}
			handled = TRUE;
			break;
		case KEY_UP:
			if (!setSelectedIndex((getSelectedIndex() - 1)))
			{
				make_ui_sound("UISndInvalidOp");
			}
			else
			{
				onCommit();
			}
			handled = TRUE;
			break;
		case KEY_LEFT:
			if (!setSelectedIndex((getSelectedIndex() - 1)))
			{
				make_ui_sound("UISndInvalidOp");
			}
			else
			{
				onCommit();
			}
			handled = TRUE;
			break;
		case KEY_RIGHT:
			if (!setSelectedIndex((getSelectedIndex() + 1)))
			{
				make_ui_sound("UISndInvalidOp");
			}
			else
			{
				onCommit();
			}
			handled = TRUE;
			break;
		default:
			break;
		}
	}
	return handled;
}

void LLRadioGroup::draw()
{
	S32 current_button = 0;

	BOOL take_focus = FALSE;
	if (gFocusMgr.childHasKeyboardFocus(this))
	{
		take_focus = TRUE;
	}

	for (button_list_t::iterator iter = mRadioButtons.begin();
		 iter != mRadioButtons.end(); ++iter)
	{
		LLRadioCtrl* radio = *iter;
		BOOL selected = (current_button == mSelectedIndex);
		radio->setValue( selected );
		if (take_focus && selected && !gFocusMgr.childHasKeyboardFocus(radio))
		{
			// don't flash keyboard focus when navigating via keyboard
			BOOL DONT_FLASH = FALSE;
			radio->focusFirstItem(FALSE, DONT_FLASH);
		}
		current_button++;
	}

	LLView::draw();
}


// When adding a button, we need to ensure that the radio
// group gets a message when the button is clicked.
LLRadioCtrl* LLRadioGroup::addRadioButton(const std::string& name, const std::string& label, const LLRect& rect, const LLFontGL* font )
{
	// Highlight will get fixed in draw method above
	LLRadioCtrl* radio = new LLRadioCtrl(name, rect, label, font,
		onClickButton, this);
	addChild(radio);
	mRadioButtons.push_back(radio);
	return radio;
}

// Handle one button being clicked.  All child buttons must have this
// function as their callback function.

// static
void LLRadioGroup::onClickButton(LLUICtrl* ui_ctrl, void* userdata)
{
	// llinfos << "LLRadioGroup::onClickButton" << llendl;

	LLRadioCtrl* clickedRadio = (LLRadioCtrl*) ui_ctrl;
	LLRadioGroup* self = (LLRadioGroup*) userdata;

	S32 counter = 0;
	for (button_list_t::iterator iter = self->mRadioButtons.begin();
		 iter != self->mRadioButtons.end(); ++iter)
	{
		LLRadioCtrl* radio = *iter;
		if (radio == clickedRadio)
		{
			// llinfos << "clicked button " << counter << llendl;
			self->setSelectedIndex(counter);
			self->setControlValue(counter);
			
			// BUG: Calls click callback even if button didn't actually change
			self->onCommit();

			return;
		}

		counter++;
	}

	llwarns << "LLRadioGroup::onClickButton - clicked button that isn't a child" << llendl;
}

void LLRadioGroup::setValue( const LLSD& value )
{
	std::string value_name = value.asString();
	int idx = 0;
	for (button_list_t::const_iterator iter = mRadioButtons.begin();
		 iter != mRadioButtons.end(); ++iter)
	{
		LLRadioCtrl* radio = *iter;
		if (radio->getName() == value_name)
		{
			setSelectedIndex(idx);
			idx = -1;
			break;
		}
		++idx;
	}
	if (idx != -1)
	{
		// string not found, try integer
		if (value.isInteger())
		{
			setSelectedIndex((S32) value.asInteger(), TRUE);
		}
		else
		{
			llwarns << "LLRadioGroup::setValue: value not found: " << value_name << llendl;
		}
	}
}

LLSD LLRadioGroup::getValue() const
{
	int index = getSelectedIndex();
	int idx = 0;
	for (button_list_t::const_iterator iter = mRadioButtons.begin();
		 iter != mRadioButtons.end(); ++iter)
	{
		if (idx == index) return LLSD((*iter)->getName());
		++idx;
	}
	return LLSD();
}

// virtual
LLXMLNodePtr LLRadioGroup::getXML(bool save_children) const
{
	LLXMLNodePtr node = LLUICtrl::getXML();

	// Attributes

	node->createChild("draw_border", TRUE)->setBoolValue(mHasBorder);

	// Contents

	for (button_list_t::const_iterator iter = mRadioButtons.begin();
		 iter != mRadioButtons.end(); ++iter)
	{
		LLRadioCtrl* radio = *iter;

		LLXMLNodePtr child_node = radio->LLView::getXML();
		child_node->setStringValue(radio->getLabel());
		child_node->setName(std::string("radio_item"));

		node->addChild(child_node);
	}

	return node;
}

// static
LLView* LLRadioGroup::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
{
	std::string name("radio_group");
	node->getAttributeString("name", name);

	U32 initial_value = 0;
	node->getAttributeU32("initial_value", initial_value);

	BOOL draw_border = TRUE;
	node->getAttributeBOOL("draw_border", draw_border);

	LLRect rect;
	createRect(node, rect, parent, LLRect());

	LLRadioGroup* radio_group = new LLRadioGroup(name, 
		rect,
		initial_value,
		NULL,
		NULL,
		draw_border);

	const std::string& contents = node->getValue();

	LLRect group_rect = radio_group->getRect();

	LLFontGL *font = LLView::selectFont(node);

	if (contents.find_first_not_of(" \n\t") != contents.npos)
	{
		// ...old school default vertical layout
		typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
		boost::char_separator<char> sep("\t\n");
		tokenizer tokens(contents, sep);
		tokenizer::iterator token_iter = tokens.begin();
	
		const S32 HPAD = 4, VPAD = 4;
		S32 cur_y = group_rect.getHeight() - VPAD;
	
		while(token_iter != tokens.end())
		{
			const std::string& line = *token_iter;
			LLRect rect(HPAD, cur_y, group_rect.getWidth() - (2 * HPAD), cur_y - 15);
			cur_y -= VPAD + 15;
			radio_group->addRadioButton(std::string("radio"), line, rect, font);
			++token_iter;
		}
		llwarns << "Legacy radio group format used! Please convert to use <radio_item> tags!" << llendl;
	}
	else
	{
		// ...per pixel layout
		LLXMLNodePtr child;
		for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
		{
			if (child->hasName("radio_item"))
			{
				LLRect item_rect;
				createRect(child, item_rect, radio_group, rect);

				std::string radioname("radio");
				child->getAttributeString("name", radioname);
				std::string item_label = child->getTextContents();
				LLRadioCtrl* radio = radio_group->addRadioButton(radioname, item_label, item_rect, font);

				radio->initFromXML(child, radio_group);
			}
		}
	}

	radio_group->initFromXML(node, parent);

	return radio_group;
}

// LLCtrlSelectionInterface functions
BOOL	LLRadioGroup::setCurrentByID( const LLUUID& id )
{
	return FALSE;
}

LLUUID	LLRadioGroup::getCurrentID() const
{
	return LLUUID::null;
}

BOOL	LLRadioGroup::setSelectedByValue(const LLSD& value, BOOL selected)
{
	S32 idx = 0;
	std::string value_string = value.asString();
	for (button_list_t::const_iterator iter = mRadioButtons.begin();
		 iter != mRadioButtons.end(); ++iter)
	{
		if((*iter)->getName() == value_string)
		{
			setSelectedIndex(idx);
			return TRUE;
		}
		idx++;
	}

	return FALSE;
}

LLSD	LLRadioGroup::getSelectedValue()
{
	return getValue();	
}

BOOL	LLRadioGroup::isSelected(const LLSD& value) const
{
	S32 idx = 0;
	std::string value_string = value.asString();
	for (button_list_t::const_iterator iter = mRadioButtons.begin();
		 iter != mRadioButtons.end(); ++iter)
	{
		if((*iter)->getName() == value_string)
		{
			if (idx == mSelectedIndex) 
			{
				return TRUE;
			}
		}
		idx++;
	}
	return FALSE;
}

BOOL	LLRadioGroup::operateOnSelection(EOperation op)
{
	return FALSE;
}

BOOL	LLRadioGroup::operateOnAll(EOperation op)
{
	return FALSE;
}


LLRadioCtrl::~LLRadioCtrl()
{
}

void LLRadioCtrl::setValue(const LLSD& value)
{
	LLCheckBoxCtrl::setValue(value);
	mButton->setTabStop(value.asBoolean());
}