/**
 *
 * Copyright (c) 2009-2010, Kitty Barnett
 *
 * The source code in this file is provided to you under the terms of the
 * GNU General Public License, version 2.0, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. Terms of the GPL can be found in doc/GPL-license.txt
 * in this distribution, or online at http://www.gnu.org/licenses/gpl-2.0.txt
 *
 * 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.
 *
 */

#include "llviewerprecompiledheaders.h"
#include "llagent.h"
#include "llfloaterwindlight.h"
#include "llviewercontrol.h"
#include "llviewerwindow.h"
#include "llvoavatar.h"
#include "llwlparammanager.h"

#include "rlvextensions.h"
#include "rlvhandler.h"

// ============================================================================

std::map<std::string, S16> RlvExtGetSet::m_DbgAllowed;
std::map<std::string, std::string> RlvExtGetSet::m_PseudoDebug;

// Checked: 2009-06-03 (RLVa-0.2.0h) | Modified: RLVa-0.2.0h
RlvExtGetSet::RlvExtGetSet()
{
	if (!m_DbgAllowed.size())	// m_DbgAllowed is static and should only be initialized once
	{
		m_DbgAllowed.insert(std::pair<std::string, S16>("AvatarSex", DBG_READ | DBG_WRITE | DBG_PSEUDO));
		m_DbgAllowed.insert(std::pair<std::string, S16>("RenderResolutionDivisor", DBG_READ | DBG_WRITE));
		#ifdef RLV_EXTENSION_CMD_GETSETDEBUG_EX
			m_DbgAllowed.insert(std::pair<std::string, S16>(RLV_SETTING_FORBIDGIVETORLV, DBG_READ));
			m_DbgAllowed.insert(std::pair<std::string, S16>(RLV_SETTING_NOSETENV, DBG_READ));
			m_DbgAllowed.insert(std::pair<std::string, S16>("WindLightUseAtmosShaders", DBG_READ));
		#endif // RLV_EXTENSION_CMD_GETSETDEBUG_EX

		// Cache persistance of every setting
		LLControlVariable* pSetting;
		for (std::map<std::string, S16>::iterator itDbg = m_DbgAllowed.begin(); itDbg != m_DbgAllowed.end(); ++itDbg)
		{
			if ( ((pSetting = gSavedSettings.getControl(itDbg->first)) != NULL) && (pSetting->isPersisted()) )
				itDbg->second |= DBG_PERSIST;
		}
	}
}

// Checked: 2009-05-17 (RLVa-0.2.0a)
bool RlvExtGetSet::onForceCommand(const LLUUID& idObj, const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet)
{
	return processCommand(idObj, rlvCmd, cmdRet);
}

// Checked: 2009-05-17 (RLVa-0.2.0a)
bool RlvExtGetSet::onReplyCommand(const LLUUID& idObj, const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet)
{
	return processCommand(idObj, rlvCmd, cmdRet);
}

// Checked: 2009-12-23 (RLVa-1.1.0k) | Modified: RLVa-1.1.0k
bool RlvExtGetSet::processCommand(const LLUUID& idObj, const RlvCommand& rlvCmd, ERlvCmdRet& eRet)
{
	std::string strBehaviour = rlvCmd.getBehaviour(), strGetSet, strSetting;
	int idxSetting = strBehaviour.find('_');
	if ( (strBehaviour.length() >= 6) && (-1 != idxSetting) && ((int)strBehaviour.length() > idxSetting + 1) )
	{
		strSetting = strBehaviour.substr(idxSetting + 1);
		strBehaviour.erase(idxSetting);	// Get rid of "_<setting>"

		strGetSet = strBehaviour.substr(0, 3);
		strBehaviour.erase(0, 3);		// Get rid of get/set

		if ("debug" == strBehaviour)
		{
			if ( ("get" == strGetSet) && (RLV_TYPE_REPLY == rlvCmd.getParamType()) )
			{
				rlvSendChatReply(rlvCmd.getParam(), onGetDebug(strSetting));
				eRet = RLV_RET_SUCCESS;
				return true;
			}
			else if ( ("set" == strGetSet) && (RLV_TYPE_FORCE == rlvCmd.getParamType()) )
			{
				if (!gRlvHandler.hasBehaviourExcept(RLV_BHVR_SETDEBUG, idObj))
					eRet = onSetDebug(strSetting, rlvCmd.getOption());
				return true;
			}
		}
		else if ("env" == strBehaviour)
		{
			if ( ("get" == strGetSet) && (RLV_TYPE_REPLY == rlvCmd.getParamType()) )
			{
				rlvSendChatReply(rlvCmd.getParam(), onGetEnv(strSetting));
				eRet = RLV_RET_SUCCESS;
				return true;
			}
			else if ( ("set" == strGetSet) && (RLV_TYPE_FORCE == rlvCmd.getParamType()) )
			{
				if (!gRlvHandler.hasBehaviourExcept(RLV_BHVR_SETENV, idObj))
					eRet = onSetEnv(strSetting, rlvCmd.getOption());
				return true;
			}
		}
	}
	else if ("setrot" == rlvCmd.getBehaviour())
	{
		// NOTE: if <option> is invalid (or missing) altogether then RLV-1.17 will rotate to 0.0 (which is actually PI / 4)
		F32 nAngle = 0.0f;
		if (LLStringUtil::convertToF32(rlvCmd.getOption(), nAngle))
		{
			nAngle = RLV_SETROT_OFFSET - nAngle;

			gAgent.startCameraAnimation();

			LLVector3 at(LLVector3::x_axis);
			at.rotVec(nAngle, LLVector3::z_axis);
			at.normalize();
			gAgent.resetAxes(at);

			eRet = RLV_RET_SUCCESS;
		}
		else
			eRet = RLV_RET_FAILED_OPTION;
		return true;
	}
	return false;
}

// Checked: 2009-06-03 (RLVa-0.2.0h) | Modified: RLVa-0.2.0h
bool RlvExtGetSet::findDebugSetting(std::string& strSetting, S16& flags)
{
	LLStringUtil::toLower(strSetting);	// Convenience for non-RLV calls

	// HACK-RLVa: bad code but it's just a temporary measure to provide a smooth changeover from the old to the new rebranded settings
	if ( (strSetting.length() >= 14) && (0 == strSetting.find("restrainedlife")) )
		strSetting = "restrainedlove" + strSetting.substr(14);

	std::string strTemp;
	for (std::map<std::string, S16>::const_iterator itSetting = m_DbgAllowed.begin(); itSetting != m_DbgAllowed.end(); ++itSetting)
	{
		strTemp = itSetting->first;
		LLStringUtil::toLower(strTemp);
		
		if (strSetting == strTemp)
		{
			strSetting = itSetting->first;
			flags = itSetting->second;
			return true;
		}
	}
	return false;
}

// Checked: 2009-06-03 (RLVa-0.2.0h) | Added: RLVa-0.2.0h
S16 RlvExtGetSet::getDebugSettingFlags(const std::string& strSetting)
{
	std::map<std::string, S16>::const_iterator itSetting = m_DbgAllowed.find(strSetting);
	return (itSetting != m_DbgAllowed.end()) ? itSetting->second : 0;
}

// Checked: 2009-06-03 (RLVa-0.2.0h) | Modified: RLVa-0.2.0h
std::string RlvExtGetSet::onGetDebug(std::string strSetting)
{
	S16 dbgFlags;
	if ( (findDebugSetting(strSetting, dbgFlags)) && ((dbgFlags & DBG_READ) == DBG_READ) )
	{
		if ((dbgFlags & DBG_PSEUDO) == 0)
		{
			LLControlVariable* pSetting = gSavedSettings.getControl(strSetting);
			if (pSetting)
			{
				switch (pSetting->type())
				{
					case TYPE_U32:
						return llformat("%u", gSavedSettings.getU32(strSetting));
					case TYPE_S32:
						return llformat("%d", gSavedSettings.getS32(strSetting));
					case TYPE_BOOLEAN:
						return llformat("%d", gSavedSettings.getBOOL(strSetting));
					default:
						RLV_ERRS << "Unexpected debug setting type" << LL_ENDL;
						break;
				}
			}
		}
		else
		{
			return onGetPseudoDebug(strSetting);
		}
	}
	return std::string();
}

// Checked: 2009-10-03 (RLVa-1.0.4e) | Added: RLVa-1.0.4e
std::string RlvExtGetSet::onGetPseudoDebug(const std::string& strSetting)
{
	// Skip sanity checking because it's all done in RlvExtGetSet::onGetDebug() already
	if ("AvatarSex" == strSetting)
	{
		std::map<std::string, std::string>::const_iterator itPseudo = m_PseudoDebug.find(strSetting);
		if (itPseudo != m_PseudoDebug.end())
		{
			return itPseudo->second;
		}
		else
		{
			if (gAgent.getAvatarObject())
				return llformat("%d", (gAgent.getAvatarObject()->getSex() == SEX_MALE)); // [See LLFloaterCustomize::LLFloaterCustomize()]
		}
	}
	return std::string();
}

// Checked: 2009-10-10 (RLVa-1.0.4e) | Modified: RLVa-1.0.4e
ERlvCmdRet RlvExtGetSet::onSetDebug(std::string strSetting, const std::string& strValue)
{
	S16 dbgFlags; ERlvCmdRet eRet = RLV_RET_FAILED_UNKNOWN;
	if ( (findDebugSetting(strSetting, dbgFlags)) && ((dbgFlags & DBG_WRITE) == DBG_WRITE) )
	{
		eRet = RLV_RET_FAILED_OPTION;
		if ((dbgFlags & DBG_PSEUDO) == 0)
		{
			LLControlVariable* pSetting = gSavedSettings.getControl(strSetting);
			if (pSetting)
			{
				U32 u32Value; S32 s32Value; BOOL fValue;
				switch (pSetting->type())
				{
					case TYPE_U32:
						if (LLStringUtil::convertToU32(strValue, u32Value))
						{
							gSavedSettings.setU32(strSetting, u32Value);
							eRet = RLV_RET_SUCCESS;
						}
						break;
					case TYPE_S32:
						if (LLStringUtil::convertToS32(strValue, s32Value))
						{
							gSavedSettings.setS32(strSetting, s32Value);
							eRet = RLV_RET_SUCCESS;
						}
						break;
					case TYPE_BOOLEAN:
						if (LLStringUtil::convertToBOOL(strValue, fValue))
						{
							gSavedSettings.setBOOL(strSetting, fValue);
							eRet = RLV_RET_SUCCESS;
						}
						break;
					default:
						RLV_ERRS << "Unexpected debug setting type" << LL_ENDL;
						eRet = RLV_RET_FAILED;
						break;
				}

				// Default settings should persist if they were marked that way, but non-default settings should never persist
				pSetting->setPersist( (pSetting->isDefault()) ? ((dbgFlags & DBG_PERSIST) == DBG_PERSIST) : false );
			}
		}
		else
		{
			eRet = onSetPseudoDebug(strSetting, strValue);
		}
	}
	return eRet;
}

// Checked: 2009-10-10 (RLVa-1.0.4e) | Modified: RLVa-1.0.4e
ERlvCmdRet RlvExtGetSet::onSetPseudoDebug(const std::string& strSetting, const std::string& strValue)
{
	ERlvCmdRet eRet = RLV_RET_FAILED_OPTION;
	if ("AvatarSex" == strSetting)
	{
		BOOL fValue;
		if (LLStringUtil::convertToBOOL(strValue, fValue))
		{
			m_PseudoDebug[strSetting] = strValue;
			eRet = RLV_RET_SUCCESS;
		}
	}
	return eRet;
}

// Checked: 2009-09-16 (RLVa-1.0.3c) | Modified: RLVa-1.0.3c
std::string RlvExtGetSet::onGetEnv(std::string strSetting)
{
	// HACK: - create a LLFloaterWindLight instance if there isn't one already
	//       - isOpen() is actually instanceExists()
	//       - creating an instance results in showing the floater which is why we need to ->close() it
	if (!LLFloaterWindLight::isOpen())
	{
		LLFloaterWindLight::instance()->close();
		LLFloaterWindLight::instance()->syncMenu();
	}

	LLWLParamManager* pWLParams = LLWLParamManager::instance();

	F32 nValue = 0.0f;
	if ("daytime" == strSetting)
	{
		nValue = (pWLParams->mAnimator.mIsRunning && pWLParams->mAnimator.mUseLindenTime) ? -1.0f : pWLParams->mAnimator.getDayTime();
	}
	else if ("preset" == strSetting)
	{
		return (pWLParams->mAnimator.mIsRunning && pWLParams->mAnimator.mUseLindenTime) ? std::string() : pWLParams->mCurParams.mName;
	}
	else if ("cloudcoverage" == strSetting)			nValue = pWLParams->mCloudCoverage;
	else if ("cloudscale" == strSetting)			nValue = pWLParams->mCloudScale;
	else if ("cloudscrollx" == strSetting)			nValue = pWLParams->mCurParams.getCloudScrollX() - 10.0f;
	else if ("cloudscrolly" == strSetting)			nValue = pWLParams->mCurParams.getCloudScrollY() - 10.0f;
	else if ("densitymultiplier" == strSetting)		nValue = pWLParams->mDensityMult.x * pWLParams->mDensityMult.mult;
	else if ("distancemultiplier" == strSetting)	nValue = pWLParams->mDistanceMult;
	else if ("eastangle" == strSetting)				nValue = pWLParams->mCurParams.getEastAngle() / F_TWO_PI;
	else if ("hazedensity" == strSetting)			nValue = pWLParams->mHazeDensity.r;
	else if ("hazehorizon" == strSetting)			nValue = pWLParams->mHazeHorizon.r;
	else if ("maxaltitude" == strSetting)			nValue = pWLParams->mMaxAlt;
	else if ("scenegamma" == strSetting)			nValue = pWLParams->mWLGamma;
	else if ("starbrightness" == strSetting)		nValue = pWLParams->mCurParams.getStarBrightness();
	else if ("sunglowfocus" == strSetting)			nValue = -pWLParams->mGlow.b / 5.0f;
	else if ("sunglowsize" == strSetting)			nValue = 2 - pWLParams->mGlow.r / 20.0f;
	else if ("sunmoonposition" == strSetting)		nValue = pWLParams->mCurParams.getSunAngle() / F_TWO_PI;
	else
	{
		char ch = strSetting[strSetting.length() - 1];
		// HACK-RLVa: not entirely proper (creates new synonyms)
		if ('x' == ch)		ch = 'r';
		else if ('y' == ch)	ch = 'g';
		else if ('d' == ch)	ch = 'b';

		if ( ('r' == ch) || ('g' == ch) || ('b' == ch) || ('i' == ch) )
		{
			WLColorControl* pColour = NULL;
			strSetting.erase(strSetting.length() - 1, 1);
			
			if ("ambient" == strSetting)			pColour = &pWLParams->mAmbient;
			else if ("bluedensity" == strSetting)	pColour = &pWLParams->mBlueDensity;
			else if ("bluehorizon" == strSetting)	pColour = &pWLParams->mBlueHorizon;
			else if ("sunmooncolor" == strSetting)	pColour = &pWLParams->mSunlight;
			else if ("cloudcolor" == strSetting)	pColour = &pWLParams->mCloudColor;
			else if ("cloud" == strSetting)			pColour = &pWLParams->mCloudMain;
			else if ("clouddetail" == strSetting)	pColour = &pWLParams->mCloudDetail;

			if (pColour)
			{
				if ('r' == ch)		nValue = pColour->r;
				else if ('g' == ch)	nValue = pColour->g;
				else if ('b' == ch)	nValue = pColour->b;
				else if (('i' == ch) && (pColour->hasSliderName)) nValue = llmax(pColour->r, pColour->g, pColour->b);

				if (pColour->isBlueHorizonOrDensity)	nValue /= 2.0f;
				else if (pColour->isSunOrAmbientColor)	nValue /= 3.0f;
			}
		}
	}

	return llformat("%f", nValue);
}

// Checked: 2009-09-16 (RLVa-1.0.3c) | Modified: RLVa-1.0.3c
ERlvCmdRet RlvExtGetSet::onSetEnv(std::string strSetting, const std::string& strValue)
{
	// HACK: see RlvExtGetSet::onGetEnv
	if (!LLFloaterWindLight::isOpen())
	{
		LLFloaterWindLight::instance()->close();
		LLFloaterWindLight::instance()->syncMenu();
	}

	LLWLParamManager* pWLParams = LLWLParamManager::instance();
	WLFloatControl* pFloat = NULL;
	WLColorControl* pColour = NULL;

	F32 nValue = 0.0f;
	// Sanity check - make sure strValue specifies a number for all settings except "preset"
	if ( (RlvSettings::getNoSetEnv()) || ( (!LLStringUtil::convertToF32(strValue, nValue)) && ("preset" != strSetting) ))
		return RLV_RET_FAILED_OPTION;

	// Not quite correct, but RLV-1.16.0 will halt the default daytime cycle on invalid commands so we need to as well
	pWLParams->mAnimator.mIsRunning = false;
	pWLParams->mAnimator.mUseLindenTime = false;

	// See LLWorldEnvSettings::handleEvent()
	if ("daytime" == strSetting)
	{
		if (0.0f <= nValue)
		{
			pWLParams->mAnimator.setDayTime(llmin(nValue, 1.0f));
			pWLParams->mAnimator.update(pWLParams->mCurParams);
		}
		else
		{
			pWLParams->mAnimator.mIsRunning = true;
			pWLParams->mAnimator.mUseLindenTime = true;	
		}
		return RLV_RET_SUCCESS;
	}
	// See LLFloaterWindLight::onChangePresetName()
	else if ("preset" == strSetting)
	{
		pWLParams->loadPreset(strValue, true);
		return RLV_RET_SUCCESS;
	}
	// See LLFloaterWindLight::onStarAlphaMoved
	else if ("starbrightness" == strSetting)
	{
		pWLParams->mCurParams.setStarBrightness(nValue);
		return RLV_RET_SUCCESS;
	}
	// See LLFloaterWindLight::onGlowRMoved() / LLFloaterWindLight::onGlowBMoved()
	else if ( ("sunglowfocus" == strSetting) || ("sunglowsize" == strSetting) )
	{
		WLColorControl *pColour = &pWLParams->mGlow;
		if ("sunglowfocus" == strSetting) 
			pColour->b = -nValue * 5;
		else
			pColour->r = (2 - nValue) * 20;

		pColour->update(pWLParams->mCurParams);
		pWLParams->propagateParameters();
		return RLV_RET_SUCCESS;
	}
	// See LLFloaterWindLight::onSunMoved()
	else if ( ("eastangle" == strSetting) || ("sunmoonposition" == strSetting) )	
	{
		if ("eastangle" == strSetting)	
			pWLParams->mCurParams.setEastAngle(F_TWO_PI * nValue);
		else
			pWLParams->mCurParams.setSunAngle(F_TWO_PI * nValue);

		// TODO-RLVa: it looks like propagateParameters() will actually take care of this for us, making this redundant?
		WLColorControl* pColour = &pWLParams->mLightnorm;
		pColour->r = -sin(pWLParams->mCurParams.getEastAngle()) * cos(pWLParams->mCurParams.getSunAngle());
		pColour->g = sin(pWLParams->mCurParams.getSunAngle());
		pColour->b = cos(pWLParams->mCurParams.getEastAngle()) * cos(pWLParams->mCurParams.getSunAngle());
		pColour->i = 1.f;

		pColour->update(pWLParams->mCurParams);
		pWLParams->propagateParameters();
		return RLV_RET_SUCCESS;
	}
	// See LLFloaterWindLight::onCloudScrollXMoved() / LLFloaterWindLight::onCloudScrollYMoved() 
	else if ("cloudscrollx" == strSetting)
	{
		pWLParams->mCurParams.setCloudScrollX(nValue + 10.0f);
		return RLV_RET_SUCCESS;
	}
	else if ("cloudscrolly" == strSetting)
	{
		pWLParams->mCurParams.setCloudScrollY(nValue + 10.0f);
		return RLV_RET_SUCCESS;
	}
	// See LLFloaterWindLight::onFloatControlMoved()
	else if ("cloudcoverage" == strSetting)			pFloat = &pWLParams->mCloudCoverage;
	else if ("cloudscale" == strSetting)			pFloat = &pWLParams->mCloudScale;
	else if ("densitymultiplier" == strSetting)		pFloat = &pWLParams->mDensityMult;
	else if ("distancemultiplier" == strSetting)	pFloat = &pWLParams->mDistanceMult;
	else if ("maxaltitude" == strSetting)			pFloat = &pWLParams->mMaxAlt;
	else if ("scenegamma" == strSetting)			pFloat = &pWLParams->mWLGamma;
	// See LLFloaterWindLight::onColorControlRMoved()
	else if ("hazedensity" == strSetting)	pColour = &pWLParams->mHazeDensity;
	else if ("hazehorizon" == strSetting)	pColour = &pWLParams->mHazeHorizon;

	if (pFloat)
	{
		pFloat->x = nValue / pFloat->mult;
		pFloat->update(pWLParams->mCurParams);
		pWLParams->propagateParameters();
		return RLV_RET_SUCCESS;
	} 
	else if (pColour)
	{
		pColour->r = nValue;
		pColour->update(pWLParams->mCurParams);
		pWLParams->propagateParameters();
		return RLV_RET_SUCCESS;
	}

	// RGBI settings
	char ch = strSetting[strSetting.length() - 1];
	if ('x' == ch)		ch = 'r';
	else if ('y' == ch)	ch = 'g';
	else if ('d' == ch)	ch = 'b';

	if ( ('r' == ch) || ('g' == ch) || ('b' == ch) || ('i' == ch) )
	{
		strSetting.erase(strSetting.length() - 1, 1);
		
		if ("ambient" == strSetting)			pColour = &pWLParams->mAmbient;
		else if ("bluedensity" == strSetting)	pColour = &pWLParams->mBlueDensity;
		else if ("bluehorizon" == strSetting)	pColour = &pWLParams->mBlueHorizon;
		else if ("sunmooncolor" == strSetting)	pColour = &pWLParams->mSunlight;
		else if ("cloudcolor" == strSetting)	pColour = &pWLParams->mCloudColor;
		else if ("cloud" == strSetting)			pColour = &pWLParams->mCloudMain;
		else if ("clouddetail" == strSetting)	pColour = &pWLParams->mCloudDetail;

		if (pColour)
		{
			if (pColour->isBlueHorizonOrDensity)   nValue *= 2.0f;
			else if (pColour->isSunOrAmbientColor) nValue *= 3.0f;

			if ('i' == ch)									// (See: LLFloaterWindLight::onColorControlIMoved)
			{
				if (!pColour->hasSliderName)
					return RLV_RET_FAILED_UNKNOWN;

				F32 curMax = llmax(pColour->r, pColour->g, pColour->b);
				if ( (0.0f == nValue) || (0.0f == curMax) )
					pColour->r = pColour->g = pColour->b = pColour->i = nValue;
				else
				{
					F32 nDelta = (nValue - curMax) / curMax;
					pColour->r *= (1.0f + nDelta);
					pColour->g *= (1.0f + nDelta);
					pColour->b *= (1.0f + nDelta);
					pColour->i = nValue;
				}
			}
			else											// (See: LLFloaterWindLight::onColorControlRMoved)
			{
				F32* pnValue = ('r' == ch) ? &pColour->r : ('g' == ch) ? &pColour->g : ('b' == ch) ? &pColour->b : NULL;
				if (pnValue)
					*pnValue = nValue;
				pColour->i = llmax(pColour->r, pColour->g, pColour->b);
			}

			pColour->update(pWLParams->mCurParams);
			pWLParams->propagateParameters();

			return RLV_RET_SUCCESS;
		}
	}
	return RLV_RET_FAILED_UNKNOWN;
}

// ============================================================================