/** 
 * @file lltoolmgr.cpp
 * @brief LLToolMgr class implementation
 *
 * $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 "llviewerprecompiledheaders.h"

#include "lltoolmgr.h"

#include "lluictrl.h"
#include "llmenugl.h"
#include "llfloaterreg.h"

//#include "llfirstuse.h"
// tools and manipulators
#include "llfloaterinspect.h"
#include "lltool.h"
#include "llmanipscale.h"
#include "llmarketplacefunctions.h"
#include "llselectmgr.h"
#include "lltoolbrush.h"
#include "lltoolcomp.h"
#include "lltooldraganddrop.h"
#include "lltoolface.h"
#include "lltoolfocus.h"
#include "lltoolgrab.h"
#include "lltoolindividual.h"
#include "lltoolmorph.h"
#include "lltoolpie.h"
#include "lltoolselectland.h"
#include "lltoolobjpicker.h"
#include "lltoolpipette.h"
#include "llagent.h"
#include "llagentcamera.h"
#include "llviewercontrol.h"
#include "llviewerjoystick.h"
#include "llviewermenu.h"
#include "llviewerparcelmgr.h"


// Used when app not active to avoid processing hover.
LLTool*			gToolNull	= NULL;

LLToolset*		gBasicToolset		= NULL;
LLToolset*		gCameraToolset		= NULL;
//LLToolset*		gLandToolset		= NULL;
LLToolset*		gMouselookToolset	= NULL;
LLToolset*		gFaceEditToolset	= NULL;

/////////////////////////////////////////////////////
// LLToolMgr

LLToolMgr::LLToolMgr()
	:
	mBaseTool(NULL), 
	mSavedTool(NULL),
	mTransientTool( NULL ),
	mOverrideTool( NULL ),
	mSelectedTool( NULL ),
	mCurrentToolset( NULL )
{
	// Not a panel, register these callbacks globally.
	LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.Active", boost::bind(&LLToolMgr::inEdit, this));
	LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.Enabled", boost::bind(&LLToolMgr::canEdit, this));
	LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.EnabledOrActive", boost::bind(&LLToolMgr::buildEnabledOrActive, this));
	LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Build.Toggle", boost::bind(&LLToolMgr::toggleBuildMode, this, _2));
	LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Marketplace.Enabled", boost::bind(&LLToolMgr::canAccessMarketplace, this));
	LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Marketplace.Toggle", boost::bind(&LLToolMgr::toggleMarketplace, this, _2));
	
	gToolNull = new LLTool(LLStringUtil::null);  // Does nothing
	setCurrentTool(gToolNull);

	gBasicToolset		= new LLToolset();
	gCameraToolset		= new LLToolset();
//	gLandToolset		= new LLToolset();
	gMouselookToolset	= new LLToolset();
	gFaceEditToolset	= new LLToolset();
	gMouselookToolset->setShowFloaterTools(false);
	gFaceEditToolset->setShowFloaterTools(false);
}

void LLToolMgr::initTools()
{
	static BOOL initialized = FALSE;
	if(initialized)
	{
		return;
	}
	initialized = TRUE;
	gBasicToolset->addTool( LLToolPie::getInstance() );
	gBasicToolset->addTool( LLToolCamera::getInstance() );
	gCameraToolset->addTool( LLToolCamera::getInstance() );
	gBasicToolset->addTool( LLToolGrab::getInstance() );
	gBasicToolset->addTool( LLToolCompTranslate::getInstance() );
	gBasicToolset->addTool( LLToolCompCreate::getInstance() );
	gBasicToolset->addTool( LLToolBrushLand::getInstance() );
	gMouselookToolset->addTool( LLToolCompGun::getInstance() );
	gBasicToolset->addTool( LLToolCompInspect::getInstance() );
	gFaceEditToolset->addTool( LLToolCamera::getInstance() );

	// On startup, use "select" tool
	setCurrentToolset(gBasicToolset);

	gBasicToolset->selectTool( LLToolPie::getInstance() );
}

LLToolMgr::~LLToolMgr()
{
	delete gBasicToolset;
	gBasicToolset = NULL;

	delete gMouselookToolset;
	gMouselookToolset = NULL;

	delete gFaceEditToolset;
	gFaceEditToolset = NULL;

	delete gCameraToolset;
	gCameraToolset = NULL;
	
	delete gToolNull;
	gToolNull = NULL;
}

BOOL LLToolMgr::usingTransientTool()
{
	return mTransientTool ? TRUE : FALSE;
}

void LLToolMgr::setCurrentToolset(LLToolset* current)
{
	if (!current) return;

	// switching toolsets?
	if (current != mCurrentToolset)
	{
		// deselect current tool
		if (mSelectedTool)
		{
			mSelectedTool->handleDeselect();
		}
		mCurrentToolset = current;
		// select first tool of new toolset only if toolset changed
		mCurrentToolset->selectFirstTool();
	}
	// update current tool based on new toolset
	setCurrentTool( mCurrentToolset->getSelectedTool() );
}

LLToolset* LLToolMgr::getCurrentToolset()
{
	return mCurrentToolset;
}

void LLToolMgr::setCurrentTool( LLTool* tool )
{
	if (mTransientTool)
	{
		mTransientTool = NULL;
	}

	mBaseTool = tool;
	updateToolStatus();

	mSavedTool = NULL;
}

LLTool* LLToolMgr::getCurrentTool()
{
	MASK override_mask = gKeyboard ? gKeyboard->currentMask(TRUE) : 0;

	LLTool* cur_tool = NULL;
	// always use transient tools if available
	if (mTransientTool)
	{
		mOverrideTool = NULL;
		cur_tool = mTransientTool;
	}
	// tools currently grabbing mouse input will stay active
	else if (mSelectedTool && mSelectedTool->hasMouseCapture())
	{
		cur_tool = mSelectedTool;
	}
	else
	{
		mOverrideTool = mBaseTool ? mBaseTool->getOverrideTool(override_mask) : NULL;

		// use override tool if available otherwise drop back to base tool
		cur_tool = mOverrideTool ? mOverrideTool : mBaseTool;
	}

	LLTool* prev_tool = mSelectedTool;
	// Set the selected tool to avoid infinite recursion
	mSelectedTool = cur_tool;
	
	//update tool selection status
	if (prev_tool != cur_tool)
	{
		if (prev_tool)
		{
			prev_tool->handleDeselect();
		}
		if (cur_tool)
		{
			if (	LLToolCompInspect::getInstance()->isToolCameraActive()
				&&	prev_tool == LLToolCamera::getInstance()
				&&	cur_tool == LLToolPie::getInstance() )
			{
				LLFloaterInspect * inspect_instance = LLFloaterReg::getTypedInstance<LLFloaterInspect>("inspect");
				if(inspect_instance && inspect_instance->getVisible())
				{
					setTransientTool(LLToolCompInspect::getInstance());
				}
			}
			else
			{
				cur_tool->handleSelect();
			}
		}
	}

	return mSelectedTool;
}

LLTool* LLToolMgr::getBaseTool()
{
	return mBaseTool;
}

void LLToolMgr::updateToolStatus()
{
	// call getcurrenttool() to calculate active tool and call handleSelect() and handleDeselect() immediately
	// when active tool changes
	getCurrentTool();
}

bool LLToolMgr::inEdit()
{
	return mBaseTool != LLToolPie::getInstance() && mBaseTool != gToolNull;
}

bool LLToolMgr::canEdit()
{
	return LLViewerParcelMgr::getInstance()->allowAgentBuild();
}

bool LLToolMgr::buildEnabledOrActive()
{
	return LLFloaterReg::instanceVisible("build") || canEdit();
}

void LLToolMgr::toggleBuildMode(const LLSD& sdname)
{
	const std::string& param = sdname.asString();

	LLFloaterReg::toggleInstanceOrBringToFront("build");
	if (param == "build" && !canEdit())
	{
		return;
	}

	bool build_visible = LLFloaterReg::instanceVisible("build");
	if (build_visible)
	{
		ECameraMode camMode = gAgentCamera.getCameraMode();
		if (CAMERA_MODE_MOUSELOOK == camMode ||	CAMERA_MODE_CUSTOMIZE_AVATAR == camMode)
		{
			// pull the user out of mouselook or appearance mode when entering build mode
			handle_reset_view();
		}

		if (gSavedSettings.getBOOL("EditCameraMovement"))
		{
			// camera should be set
			if (LLViewerJoystick::getInstance()->getOverrideCamera())
			{
				handle_toggle_flycam();
			}

			if (gAgentCamera.getFocusOnAvatar())
			{
				// zoom in if we're looking at the avatar
				gAgentCamera.setFocusOnAvatar(FALSE, ANIMATE);
				gAgentCamera.setFocusGlobal(gAgent.getPositionGlobal() + 2.0 * LLVector3d(gAgent.getAtAxis()));
				gAgentCamera.cameraZoomIn(0.666f);
				gAgentCamera.cameraOrbitOver( 30.f * DEG_TO_RAD );
			}
		}


		setCurrentToolset(gBasicToolset);
		getCurrentToolset()->selectTool( LLToolCompCreate::getInstance() );

		// Could be first use
		//LLFirstUse::useBuild();

		gAgentCamera.resetView(false);

		// avoid spurious avatar movements
		LLViewerJoystick::getInstance()->setNeedsReset();

	}
	else
	{
		if (gSavedSettings.getBOOL("EditCameraMovement"))
		{
			// just reset the view, will pull us out of edit mode
			handle_reset_view();
		}
		else
		{
			// manually disable edit mode, but do not affect the camera
			gAgentCamera.resetView(false);
			LLFloaterReg::hideInstance("build");
			gViewerWindow->showCursor();			
		}
		// avoid spurious avatar movements pulling out of edit mode
		LLViewerJoystick::getInstance()->setNeedsReset();
	}

}

bool LLToolMgr::inBuildMode()
{
	// when entering mouselook inEdit() immediately returns true before 
	// cameraMouselook() actually starts returning true.  Also, appearance edit
	// sets build mode to true, so let's exclude that.
	bool b=(inEdit() 
			&& !gAgentCamera.cameraMouselook()
			&& mCurrentToolset != gFaceEditToolset);
	
	return b;
}

bool LLToolMgr::canAccessMarketplace()
{
	return (LLMarketplaceData::instance().getSLMStatus() != MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT);
}

void LLToolMgr::toggleMarketplace(const LLSD& sdname)
{
	const std::string& param = sdname.asString();
    
	if ((param != "marketplace") || !canAccessMarketplace())
	{
		return;
	}
    
	LLFloaterReg::toggleInstanceOrBringToFront("marketplace_listings");
}

void LLToolMgr::setTransientTool(LLTool* tool)
{
	if (!tool)
	{
		clearTransientTool();
	}
	else
	{
		if (mTransientTool)
		{
			mTransientTool = NULL;
		}

		mTransientTool = tool;
	}

	updateToolStatus();
}

void LLToolMgr::clearTransientTool()
{
	if (mTransientTool)
	{
		mTransientTool = NULL;
		if (!mBaseTool)
		{
			LL_WARNS() << "mBaseTool is NULL" << LL_ENDL;
		}
	}
	updateToolStatus();
}


void LLToolMgr::onAppFocusLost()
{
	if (LLApp::isExiting())
		return;

	if (mSelectedTool)
	{
		mSelectedTool->handleDeselect();
	}
	updateToolStatus();
}

void LLToolMgr::onAppFocusGained()
{
	if (mSelectedTool)
	{
		mSelectedTool->handleSelect();
	}
	updateToolStatus();
}

void LLToolMgr::clearSavedTool()
{
	mSavedTool = NULL;
}

/////////////////////////////////////////////////////
// LLToolset

void LLToolset::addTool(LLTool* tool)
{
	mToolList.push_back( tool );
	if( !mSelectedTool )
	{
		mSelectedTool = tool;
	}
}


void LLToolset::selectTool(LLTool* tool)
{
	mSelectedTool = tool;
	LLToolMgr::getInstance()->setCurrentTool( mSelectedTool );
}


void LLToolset::selectToolByIndex( S32 index )
{
	LLTool *tool = (index >= 0 && index < (S32)mToolList.size()) ? mToolList[index] : NULL;
	if (tool)
	{
		mSelectedTool = tool;
		LLToolMgr::getInstance()->setCurrentTool( tool );
	}
}

BOOL LLToolset::isToolSelected( S32 index )
{
	LLTool *tool = (index >= 0 && index < (S32)mToolList.size()) ? mToolList[index] : NULL;
	return (tool == mSelectedTool);
}


void LLToolset::selectFirstTool()
{
	mSelectedTool = (0 < mToolList.size()) ? mToolList[0] : NULL;
	LLToolMgr::getInstance()->setCurrentTool( mSelectedTool );
}


void LLToolset::selectNextTool()
{
	LLTool* next = NULL;
	for( tool_list_t::iterator iter = mToolList.begin();
		 iter != mToolList.end(); )
	{
		LLTool* cur = *iter++;
		if( cur == mSelectedTool && iter != mToolList.end() )
		{
			next = *iter;
			break;
		}
	}

	if( next )
	{
		mSelectedTool = next;
		LLToolMgr::getInstance()->setCurrentTool( mSelectedTool );
	}
	else
	{
		selectFirstTool();
	}
}

void LLToolset::selectPrevTool()
{
	LLTool* prev = NULL;
	for( tool_list_t::reverse_iterator iter = mToolList.rbegin();
		 iter != mToolList.rend(); )
	{
		LLTool* cur = *iter++;
		if( cur == mSelectedTool && iter != mToolList.rend() )
		{
			prev = *iter;
			break;
		}
	}

	if( prev )
	{
		mSelectedTool = prev;
		LLToolMgr::getInstance()->setCurrentTool( mSelectedTool );
	}
	else if (mToolList.size() > 0)
	{
		selectToolByIndex((S32)mToolList.size()-1);
	}
}

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