/** 
 * @file llinspectobject.cpp
 *
 * $LicenseInfo:firstyear=2009&license=viewergpl$
 * 
 * Copyright (c) 2009, 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 "llviewerprecompiledheaders.h"

#include "llinspectobject.h"

// Viewer
#include "llinspect.h"
#include "llnotifications.h"	// *TODO: Eliminate, add LLNotificationsUtil wrapper
#include "llselectmgr.h"
#include "llslurl.h"
#include "llviewermenu.h"		// handle_object_touch(), handle_buy()
#include "llviewerobjectlist.h"	// to select the requested object

// Linden libraries
#include "llbutton.h"			// setLabel(), not virtual!
#include "llclickaction.h"
#include "llfloaterreg.h"
#include "llmenubutton.h"
#include "llresmgr.h"			// getMonetaryString
#include "llsafehandle.h"
#include "lltextbox.h"			// for description truncation
#include "lltrans.h"
#include "llui.h"				// positionViewNearMouse()
#include "lluictrl.h"

class LLViewerObject;

//////////////////////////////////////////////////////////////////////////////
// LLInspectObject
//////////////////////////////////////////////////////////////////////////////

// Object Inspector, a small information window used when clicking
// in the ambient inspector widget for objects in the 3D world.
class LLInspectObject : public LLInspect
{
	friend class LLFloaterReg;
	
public:
	// object_id - Root object ID for which to show information
	// Inspector will be positioned relative to current mouse position
	LLInspectObject(const LLSD& object_id);
	virtual ~LLInspectObject();
	
	/*virtual*/ BOOL postBuild(void);
	
	// Because floater is single instance, need to re-parse data on each spawn
	// (for example, inspector about same avatar but in different position)
	/*virtual*/ void onOpen(const LLSD& avatar_id);
	
	// Release the selection and do other cleanup
	/*virtual*/ void onClose(bool app_quitting);
	
private:
	// Refresh displayed data with information from selection manager
	void update();

	void hideButtons();
	void updateButtons(LLSelectNode* nodep);
	void updateSitLabel(LLSelectNode* nodep);
	void updateTouchLabel(LLSelectNode* nodep);

	void updateName(LLSelectNode* nodep);
	void updateDescription(LLSelectNode* nodep);
	void updatePrice(LLSelectNode* nodep);
	
	void updateCreator(LLSelectNode* nodep);
		
	void onClickBuy();
	void onClickPay();
	void onClickTakeFreeCopy();
	void onClickTouch();
	void onClickSit();
	void onClickOpen();
	void onClickMoreInfo();
	void onClickZoomIn();  
	
private:
	LLUUID				mObjectID;
	LLSafeHandle<LLObjectSelection> mObjectSelection;
};

LLInspectObject::LLInspectObject(const LLSD& sd)
:	LLInspect( LLSD() ),	// single_instance, doesn't really need key
	mObjectID(),			// set in onOpen()
	mObjectSelection()
{
	// can't make the properties request until the widgets are constructed
	// as it might return immediately, so do it in postBuild.
	mCommitCallbackRegistrar.add("InspectObject.Buy",	boost::bind(&LLInspectObject::onClickBuy, this));	
	mCommitCallbackRegistrar.add("InspectObject.Pay",	boost::bind(&LLInspectObject::onClickPay, this));	
	mCommitCallbackRegistrar.add("InspectObject.TakeFreeCopy",	boost::bind(&LLInspectObject::onClickTakeFreeCopy, this));	
	mCommitCallbackRegistrar.add("InspectObject.Touch",	boost::bind(&LLInspectObject::onClickTouch, this));	
	mCommitCallbackRegistrar.add("InspectObject.Sit",	boost::bind(&LLInspectObject::onClickSit, this));	
	mCommitCallbackRegistrar.add("InspectObject.Open",	boost::bind(&LLInspectObject::onClickOpen, this));	
	mCommitCallbackRegistrar.add("InspectObject.MoreInfo",	boost::bind(&LLInspectObject::onClickMoreInfo, this));	
	mCommitCallbackRegistrar.add("InspectObject.ZoomIn", boost::bind(&LLInspectObject::onClickZoomIn, this));
}


LLInspectObject::~LLInspectObject()
{
}

/*virtual*/
BOOL LLInspectObject::postBuild(void)
{
	// The XML file has sample data in it.  Clear that out so we don't
	// flicker when data arrives off network.
	getChild<LLUICtrl>("object_name")->setValue("");
	getChild<LLUICtrl>("object_creator")->setValue("");
	getChild<LLUICtrl>("object_description")->setValue("");

	// Set buttons invisible until we know what this object can do
	hideButtons();

	// Hide floater when name links clicked
	LLTextBox* textbox = getChild<LLTextBox>("object_creator");
	textbox->mURLClickSignal.connect(
		boost::bind(&LLInspectObject::closeFloater, this, false) );

	// Hook up functionality
	getChild<LLUICtrl>("buy_btn")->setCommitCallback(
		boost::bind(&LLInspectObject::onClickBuy, this));
	getChild<LLUICtrl>("pay_btn")->setCommitCallback(
		boost::bind(&LLInspectObject::onClickPay, this));
	getChild<LLUICtrl>("take_free_copy_btn")->setCommitCallback(
		boost::bind(&LLInspectObject::onClickTakeFreeCopy, this));
	getChild<LLUICtrl>("touch_btn")->setCommitCallback(
		boost::bind(&LLInspectObject::onClickTouch, this));
	getChild<LLUICtrl>("sit_btn")->setCommitCallback(
		boost::bind(&LLInspectObject::onClickSit, this));
	getChild<LLUICtrl>("open_btn")->setCommitCallback(
		boost::bind(&LLInspectObject::onClickOpen, this));
	getChild<LLUICtrl>("more_info_btn")->setCommitCallback(
		boost::bind(&LLInspectObject::onClickMoreInfo, this));

	// Watch for updates to selection properties off the network
	LLSelectMgr::getInstance()->mUpdateSignal.connect(
		boost::bind(&LLInspectObject::update, this) );

	return TRUE;
}


// Multiple calls to showInstance("inspect_avatar", foo) will provide different
// LLSD for foo, which we will catch here.
//virtual
void LLInspectObject::onOpen(const LLSD& data)
{
	// Start animation
	LLInspect::onOpen(data);

	// Extract appropriate avatar id
	mObjectID = data["object_id"];

	// Position the inspector relative to the mouse cursor
	// Similar to how tooltips are positioned
	// See LLToolTipMgr::createToolTip
	if (data.has("pos"))
	{
		LLUI::positionViewNearMouse(this, data["pos"]["x"].asInteger(), data["pos"]["y"].asInteger());
	}
	else
	{
		LLUI::positionViewNearMouse(this);
	}

	// Promote hovered object to a complete selection, which will also force
	// a request for selected object data off the network
	LLViewerObject* obj = gObjectList.findObject( mObjectID );
	if (obj)
	{
		LLSelectMgr::instance().deselectAll();
		mObjectSelection = LLSelectMgr::instance().selectObjectAndFamily(obj);

		// Mark this as a transient selection
		struct SetTransient : public LLSelectedNodeFunctor
		{
			bool apply(LLSelectNode* node)
			{
				node->setTransient(TRUE);
				return true;
			}
		} functor;
		mObjectSelection->applyToNodes(&functor);
	}
}

// virtual
void LLInspectObject::onClose(bool app_quitting)
{
	// Release selection to deselect
	mObjectSelection = NULL;

	getChild<LLMenuButton>("gear_btn")->hideMenu();
}


void LLInspectObject::update()
{
	// Performance optimization, because we listen to updates from select mgr
	// but we're never destroyed.
	if (!getVisible()) return;

	LLObjectSelection* selection = LLSelectMgr::getInstance()->getSelection();
	if (!selection) return;

	LLSelectNode* nodep = selection->getFirstRootNode();
	if (!nodep) return;

	updateButtons(nodep);
	updateName(nodep);
	updateDescription(nodep);
	updateCreator(nodep);
	updatePrice(nodep);
}

void LLInspectObject::hideButtons()
{
	getChild<LLUICtrl>("buy_btn")->setVisible(false);
	getChild<LLUICtrl>("pay_btn")->setVisible(false);
	getChild<LLUICtrl>("take_free_copy_btn")->setVisible(false);
	getChild<LLUICtrl>("touch_btn")->setVisible(false);
	getChild<LLUICtrl>("sit_btn")->setVisible(false);
	getChild<LLUICtrl>("open_btn")->setVisible(false);
}

// *TODO: Extract this method from lltoolpie.cpp and put somewhere shared
extern U8 final_click_action(LLViewerObject*);

// Choose the "most relevant" operation for this object, and show a button for
// that operation as the left-most button in the inspector.
void LLInspectObject::updateButtons(LLSelectNode* nodep)
{
	// We'll start with everyone hidden and show the ones we need
	hideButtons();
	
	LLViewerObject* object = nodep->getObject();
	LLViewerObject *parent = (LLViewerObject*)object->getParent();
	bool for_copy = anyone_copy_selection(nodep);
	bool for_sale = enable_buy_object();
	S32 price = nodep->mSaleInfo.getSalePrice();
	U8 click_action = final_click_action(object);

	if (for_copy
		|| (for_sale && price == 0))
	{
		// Free copies have priority over other operations
		getChild<LLUICtrl>("take_free_copy_btn")->setVisible(true);
	}
	else if (for_sale)
	{
		getChild<LLUICtrl>("buy_btn")->setVisible(true);
	}
	else if ( enable_pay_object() )
	{
		getChild<LLUICtrl>("pay_btn")->setVisible(true);
	}
	else if (click_action == CLICK_ACTION_SIT)
	{
		// Click-action sit must come before "open" because many objects on
		// which you can sit have scripts, and hence can be opened
		getChild<LLUICtrl>("sit_btn")->setVisible(true);
		updateSitLabel(nodep);
	}
	else if (object->flagHandleTouch()
		|| (parent && parent->flagHandleTouch()))
	{
		getChild<LLUICtrl>("touch_btn")->setVisible(true);
		updateTouchLabel(nodep);
	}
	else if ( enable_object_open() )
	{
		// Open is last because anything with a script in it can be opened
		getChild<LLUICtrl>("open_btn")->setVisible(true);
	}
	else
	{
		// By default, we can sit on anything
		getChild<LLUICtrl>("sit_btn")->setVisible(true);
		updateSitLabel(nodep);
	}

	// No flash
	focusFirstItem(FALSE, FALSE);
}

void LLInspectObject::updateSitLabel(LLSelectNode* nodep)
{
	LLButton* sit_btn = getChild<LLButton>("sit_btn");
	if (!nodep->mSitName.empty())
	{
		sit_btn->setLabel( nodep->mSitName );
	}
	else
	{
		sit_btn->setLabel( getString("Sit") );
	}
}

void LLInspectObject::updateTouchLabel(LLSelectNode* nodep)
{
	LLButton* sit_btn = getChild<LLButton>("touch_btn");
	if (!nodep->mTouchName.empty())
	{
		sit_btn->setLabel( nodep->mTouchName );
	}
	else
	{
		sit_btn->setLabel( getString("Touch") );
	}
}

void LLInspectObject::updateName(LLSelectNode* nodep)
{
	std::string name;
	if (!nodep->mName.empty())
	{
		name = nodep->mName;
	}
	else
	{
		name = LLTrans::getString("TooltipNoName");
	}
	getChild<LLUICtrl>("object_name")->setValue(name);
}

void LLInspectObject::updateDescription(LLSelectNode* nodep)
{
	const char* const DEFAULT_DESC = "(No Description)";
	std::string desc;
	if (!nodep->mDescription.empty()
		&& nodep->mDescription != DEFAULT_DESC)
	{
		desc = nodep->mDescription;
	}

	LLTextBox* textbox = getChild<LLTextBox>("object_description");
	textbox->setValue(desc);

	// Truncate description text to fit in widget
	// *HACK: OMG, use lower-left corner to truncate text
	// Don't round the position, we want the left of the character
	S32 corner_index = textbox->getDocIndexFromLocalCoord( 0, 0, FALSE);
	LLWString desc_wide = textbox->getWText();
	// index == length if position is past last character
	if (corner_index < (S32)desc_wide.length())
	{
		desc_wide = desc_wide.substr(0, corner_index);
		textbox->setWText(desc_wide);
	}
}

void LLInspectObject::updateCreator(LLSelectNode* nodep)
{
	// final information for display
	LLStringUtil::format_map_t args;
	std::string text;
	
	// Leave text blank until data loaded
	if (nodep->mValid)
	{
		// Utilize automatic translation of SLURL into name to display 
		// a clickable link		
		// Objects cannot be created by a group, so use agent URL format
		LLUUID creator_id = nodep->mPermissions->getCreator();
		std::string creator_url =
			LLSLURL::buildCommand("agent", creator_id, "about");
		args["[CREATOR]"] = creator_url;
				
		// created by one user but owned by another
		std::string owner_url;
		LLUUID owner_id;
		bool group_owned = nodep->mPermissions->isGroupOwned();
		if (group_owned)
		{
			owner_id = nodep->mPermissions->getGroup();
			owner_url =	LLSLURL::buildCommand("group", owner_id, "about");
		}
		else
		{
			owner_id = nodep->mPermissions->getOwner();
			owner_url =	LLSLURL::buildCommand("agent", owner_id, "about");
		}
		args["[OWNER]"] = owner_url;
		
		if (creator_id == owner_id)
		{
			// common case, created and owned by one user
			text = getString("Creator", args);
		}
		else
		{
			text = getString("CreatorAndOwner", args);
		}
	}
	getChild<LLUICtrl>("object_creator")->setValue(text);
}

void LLInspectObject::updatePrice(LLSelectNode* nodep)
{
	// *TODO: Only look these up once and use for both updateButtons and here
	bool for_copy = anyone_copy_selection(nodep);
	bool for_sale = enable_buy_object();
	S32 price = nodep->mSaleInfo.getSalePrice();
	
	bool show_price_icon = false;
	std::string line;
	if (for_copy
		|| (for_sale && price == 0))
	{
		line = getString("PriceFree");
		show_price_icon = true;
	}
	else if (for_sale)
	{
		LLStringUtil::format_map_t args;
		args["[AMOUNT]"] = LLResMgr::getInstance()->getMonetaryString(price);
		line = getString("Price", args);
		show_price_icon = true;
	}
	getChild<LLUICtrl>("price_text")->setValue(line);
	getChild<LLUICtrl>("price_icon")->setVisible(show_price_icon);
}

void LLInspectObject::onClickBuy()
{
	handle_buy();
	closeFloater();
}

void LLInspectObject::onClickPay()
{
	handle_give_money_dialog();
	closeFloater();
}

void LLInspectObject::onClickTakeFreeCopy()
{
	LLObjectSelection* selection = LLSelectMgr::getInstance()->getSelection();
	if (!selection) return;

	LLSelectNode* nodep = selection->getFirstRootNode();
	if (!nodep) return;

	// Figure out if this is a "free buy" or a "take copy"
	bool for_copy = anyone_copy_selection(nodep);
	// Prefer to just take a free copy
	if (for_copy)
	{
		handle_take_copy();
	}
	else
	{
		// Buy for free (confusing, but that's how it is)
		handle_buy();
	}
	closeFloater();
}

void LLInspectObject::onClickTouch()
{
	handle_object_touch();
	closeFloater();
}

void LLInspectObject::onClickSit()
{
	handle_object_sit_or_stand();
	closeFloater();
}

void LLInspectObject::onClickOpen()
{
	LLFloaterReg::showInstance("openobject");
	closeFloater();
}

void LLInspectObject::onClickMoreInfo()
{
	// *TODO: Show object info side panel, once that is implemented.
	LLNotifications::instance().add("ClickUnimplemented");
	closeFloater();
}

void LLInspectObject::onClickZoomIn() 
{
	handle_look_at_selection("zoom");
	closeFloater();
}

//////////////////////////////////////////////////////////////////////////////
// LLInspectObjectUtil
//////////////////////////////////////////////////////////////////////////////
void LLInspectObjectUtil::registerFloater()
{
	LLFloaterReg::add("inspect_object", "inspect_object.xml",
					  &LLFloaterReg::build<LLInspectObject>);
}