/** 
 * @file llradiogroup.cpp
 * @brief LLRadioGroup base class
 *
 * $LicenseInfo:firstyear=2001&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 "llboost.h"

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

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

static LLDefaultChildRegistry::Register<LLRadioGroup> r1("radio_group");

/*
 * An invisible view containing multiple mutually exclusive toggling 
 * buttons (usually radio buttons).  Automatically handles the mutex
 * condition by highlighting only one button at a time.
 */
class LLRadioCtrl : public LLCheckBoxCtrl 
{
public:
	typedef LLRadioGroup::ItemParams Params;
	/*virtual*/ ~LLRadioCtrl();
	/*virtual*/ void setValue(const LLSD& value);

	/*virtual*/ BOOL postBuild();
	/*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask);

	LLSD getPayload() { return mPayload; }

	// Ensure label is in an attribute, not the contents
	static void setupParamsForExport(Params& p, LLView* parent);

protected:
	LLRadioCtrl(const LLRadioGroup::ItemParams& p);
	friend class LLUICtrlFactory;

	LLSD mPayload;	// stores data that this item represents in the radio group
};
static LLWidgetNameRegistry::StaticRegistrar register_radio_item(&typeid(LLRadioGroup::ItemParams), "radio_item");

LLRadioGroup::Params::Params()
:	allow_deselect("allow_deselect"),
	items("item") 
{
	addSynonym(items, "radio_item");

	// radio items are not tabbable until they are selected
	tab_stop = false;
}

LLRadioGroup::LLRadioGroup(const LLRadioGroup::Params& p)
:	LLUICtrl(p),
	mFont(p.font.isProvided() ? p.font() : LLFontGL::getFontSansSerifSmall()),
	mSelectedIndex(-1),
	mAllowDeselect(p.allow_deselect)
{}

void LLRadioGroup::initFromParams(const Params& p)
{
	for (LLInitParam::ParamIterator<ItemParams>::const_iterator it = p.items.begin();
		it != p.items.end();
		++it)
	{
		LLRadioGroup::ItemParams item_params(*it);

		if (!item_params.font.isProvided())
		{
			item_params.font = mFont; // apply radio group font by default
		}
		item_params.commit_callback.function = boost::bind(&LLRadioGroup::onClickButton, this, _1);
		item_params.from_xui = p.from_xui;
		if (p.from_xui)
		{
			applyXUILayout(item_params, this);
		}

		LLRadioCtrl* item = LLUICtrlFactory::create<LLRadioCtrl>(item_params, this);
		mRadioButtons.push_back(item);
	}

	// call this *after* setting up mRadioButtons so we can handle setValue() calls
	LLUICtrl::initFromParams(p);
}


LLRadioGroup::~LLRadioGroup()
{
}

// virtual
BOOL LLRadioGroup::postBuild()
{
	if (!mRadioButtons.empty())
	{
		mRadioButtons[0]->setTabStop(true);
	}
	return TRUE;
}

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 ((S32)mRadioButtons.size() <= index )
	{
		return FALSE;
	}

    if (index < -1)
    {
        // less then minimum value
        return FALSE;
    }

    if (index < 0 && mSelectedIndex >= 0 && !mAllowDeselect)
    {
        // -1 is "nothing selected"
        return FALSE;
    }

	if (mSelectedIndex >= 0)
	{
		LLRadioCtrl* old_radio_item = mRadioButtons[mSelectedIndex];
		old_radio_item->setTabStop(false);
		old_radio_item->setValue( FALSE );
	}
	else
	{
		mRadioButtons[0]->setTabStop(false);
	}

	mSelectedIndex = index;

	if (mSelectedIndex >= 0)
	{
		LLRadioCtrl* radio_item = mRadioButtons[mSelectedIndex];
		radio_item->setTabStop(true);
		radio_item->setValue( TRUE );

		if (hasFocus())
		{
			radio_item->focusFirstItem(FALSE, FALSE);
		}
	}

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

	return TRUE;
}

void LLRadioGroup::focusSelectedRadioBtn()
{
    if (mSelectedIndex >= 0)
    {
        LLRadioCtrl* radio_item = mRadioButtons[mSelectedIndex];
        if (radio_item->hasTabStop() && radio_item->getEnabled())
        {
            radio_item->focusFirstItem(FALSE, FALSE);
        }
    }
    else if (mRadioButtons[0]->hasTabStop() || hasTabStop())
    {
        focusFirstItem(FALSE, FALSE);
    }
}

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;
}

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

void LLRadioGroup::onClickButton(LLUICtrl* ctrl)
{
	// LL_INFOS() << "LLRadioGroup::onClickButton" << LL_ENDL;
	LLRadioCtrl* clicked_radio = dynamic_cast<LLRadioCtrl*>(ctrl);
	if (!clicked_radio)
	    return;
	S32 index = 0;
	for (button_list_t::iterator iter = mRadioButtons.begin();
		 iter != mRadioButtons.end(); ++iter)
	{
		LLRadioCtrl* radio = *iter;
		if (radio == clicked_radio)
		{
			if (index == mSelectedIndex && mAllowDeselect)
			{
				// don't select anything
				setSelectedIndex(-1);
			}
			else
			{
				setSelectedIndex(index);
			}
			
			// BUG: Calls click callback even if button didn't actually change
			onCommit();

			return;
		}

		index++;
	}

	LL_WARNS() << "LLRadioGroup::onClickButton - clicked button that isn't a child" << LL_ENDL;
}

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

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)->getPayload());
		++idx;
	}
	return LLSD();
}

// 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;
	for (button_list_t::const_iterator iter = mRadioButtons.begin();
		 iter != mRadioButtons.end(); ++iter)
	{
		if((*iter)->getPayload().asString() == value.asString())
		{
			setSelectedIndex(idx);
			return TRUE;
		}
		idx++;
	}

	return FALSE;
}

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

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

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

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

LLRadioGroup::ItemParams::ItemParams()
:	value("value")
{
	addSynonym(value, "initial_value");
}

LLRadioCtrl::LLRadioCtrl(const LLRadioGroup::ItemParams& p)
:	LLCheckBoxCtrl(p),
	mPayload(p.value)
{
	// use name as default "Value" for backwards compatibility
	if (!p.value.isProvided())
	{
		mPayload = p.name();
	}
}

BOOL LLRadioCtrl::postBuild()
{
	// Old-style radio_item used the text contents to indicate the label,
	// but new-style radio_item uses label attribute.
	std::string value = getValue().asString();
	if (!value.empty())
	{
		setLabel(value);
	}
	return TRUE;
}

BOOL LLRadioCtrl::handleMouseDown(S32 x, S32 y, MASK mask)
{
    // Grab focus preemptively, before button takes mousecapture
    if (hasTabStop() && getEnabled())
    {
        focusFirstItem(FALSE, FALSE);
    }
    else
    {
        // Only currently selected item in group has tab stop as result it is
        // unclear how focus should behave on click, just let the group handle
        // focus and LLRadioGroup::onClickButton() will set correct state later
        // if needed
        LLRadioGroup* parent = (LLRadioGroup*)getParent();
        if (parent)
        {
            parent->focusSelectedRadioBtn();
        }
    }

    return LLCheckBoxCtrl::handleMouseDown(x, y, mask);
}

LLRadioCtrl::~LLRadioCtrl()
{
}

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

// *TODO: Remove this function after the initial XUI XML re-export pass.
// static
void LLRadioCtrl::setupParamsForExport(Params& p, LLView* parent)
{
	std::string label = p.label;
	if (label.empty())
	{
		// We don't have a label attribute, so move the text contents
		// stored in "value" into the label
		std::string initial_value = p.LLUICtrl::Params::initial_value();
		p.label = initial_value;
		p.LLUICtrl::Params::initial_value = LLSD();
	}

	LLCheckBoxCtrl::setupParamsForExport(p, parent);
}