/**
 *
 * Copyright (c) 2009-2020, Kitty Barnett
 *
 * The source code in this file is provided to you under the terms of the
 * GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt
 * in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.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 "llinventoryfunctions.h"
#include "llsettingsvo.h"
#include <boost/algorithm/string.hpp>

#include "rlvactions.h"
#include "rlvenvironment.h"
#include "rlvhelper.h"

// ================================================================================================
// Constants and helper functions
//

namespace
{
	const F32 SLIDER_SCALE_BLUE_HORIZON_DENSITY(2.0f);
	const F32 SLIDER_SCALE_DENSITY_MULTIPLIER(0.001f);
	const F32 SLIDER_SCALE_GLOW_R(20.0f);
	const F32 SLIDER_SCALE_GLOW_B(-5.0f);
	const F32 SLIDER_SCALE_SUN_AMBIENT(3.0f);

	const std::string RLV_GETENV_PREFIX = "getenv_";
	const std::string RLV_SETENV_PREFIX = "setenv_";

	U32 rlvGetColorComponentFromCharacter(char ch)
	{
		if ( ('r' == ch) || ('x' == ch) )      return VRED;
		else if ( ('g' == ch) || ('y' == ch )) return VGREEN;
		else if ( ('b' == ch) || ('d' == ch) ) return VBLUE;
		else if ('i' == ch)                    return VALPHA;
		return U32_MAX;
	}

	const LLUUID& rlvGetLibraryEnvironmentsFolder()
	{
		LLInventoryModel::cat_array_t cats;
		LLInventoryModel::item_array_t items;
		LLNameCategoryCollector f("Environments");
		gInventory.collectDescendentsIf(gInventory.getLibraryRootFolderID(), cats, items, LLInventoryModel::EXCLUDE_TRASH, f);
		return (!cats.empty()) ? cats.front()->getUUID() : LLUUID::null;
	}

	// Legacy WindLight values we need tend to be expressed as a fraction of the [0, 2PI[ domain
	F32 normalize_angle_domain(F32 angle)
	{
		while (angle < 0)
			angle += F_TWO_PI;
		while (angle > F_TWO_PI)
			angle -= F_TWO_PI;
		return angle;
	}
}

/*
 * Reasoning (Reference - https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Azimuth-Altitude_schematic.svg/1024px-Azimuth-Altitude_schematic.svg.png)
 *
 * Given a(zimuth angle) and e(levation angle) - in the SL axis - we know that it calculates the quaternion as follows:
 *
 * | cos a	sin a	0 |   | cos e |   | cos a x cos e | = | x |
 * | sin a	cos a	0 | x |     0 | = | sin a x cos e | = | y | (normalized direction vector identifying the sun position on a unit sphere)
 * |     0	    0	1 |   | sin e |   |         sin e | = | z |
 *
 * As a result we can reverse the above by: quaternion -> rotate it around X-axis
 *   x = cos a x cos e <=> cos a = x / cos e   \
 *                                              | (if we divide them we can get rid of cos e)
 *                                              | <=> sin a / cos a = y / x <=> tan a = y / x <=> a = atan2(y, x)
 *   y = sin a x cos e <=> sin a = y / cos e   /
 *   z = sin e         <=> e = asin z
 *
 * If we look at the resulting domain azimuth lies in ]-PI, PI] and elevation lies in [-PI/2, PI/2] which I actually prefer most. Going forward people should get the sun in a wind
 * direction by manipulating the azimuth and then deal with the elevation (which ends up mimicking how a camera or an observer behave in real life).
 *
 * Special cases:
 *   x = 0 => (1) cos e = 0 -> sin e = 1 so y = 0     and z = 1                                    => in (0, 0, 1) we loose all information about the azimuth since cos e = 0
 *         OR (2) cos a = 0 -> sin a = 1 so y = cos e and z = sin e => tan e = z/y (with y != 0)   => in (0, Y, Z) azimuth is PI/2 (or 3PI/2) and elevation can have an extended domain of ]-PI, PI]
 *         => When x = 0 (and y != 0) return PI/2 for azimuth and atan2(z, y) for elevation
 *   y = 0 => (1) sin a = 0 -> cos a = 1 so x = cos e and z = sin e => tan e = z/x (with x != 0)   => in (X, 0, Z) azimuth is    0 (or PI)    and elevation can have an extended domain of ]-PI, PI]
 *         OR (2) cos e = 0 -> see above
		   => When y = 0 (and x != 0) return 0 for azimuth and atan2(z, x) for elevation
 *   z = 0 =>     sin e = 0 -> cos e = 1 so x = cos a and y = sin a => tan a = y / x               => in (X, Y, 0) elevation is  0 (or PI)    and azimuth has its normal domain of ]-PI, PI]
 *         => When z = 0 return 0 for elevation and a = atan2(y, x) for azimuth
 *
 * We still need to convert all that back/forth between legacy WindLight's odd choices:
 *   east angle   = SL's azimuth rotates from E (1, 0, 0) to N (0, 1, 0) to W (-1, 0, 0) to S (0, -1, O) but the legacy east angle rotates the opposite way from E to S to W to N so invert the angle
 *                  (the resulting number then needs to be positive and reported as a fraction of 2PI)
 *   sunposition  = sun elevation reported as a fraction of 2PI
 *   moonposition = the moon always has sun's azimuth but its negative elevation
 *
 * Pre-EEP both azimuth and elevation have a 2PI range which means that two different a and e value combinations could yield the same sun direction which causes us problems now since we
 * can't differentiate between the two. Pre-EEP likely favoured elevation over azimuth since people might naturally get the time of day they're thinking of and then try to adjust the
 * azimuth to get the sun in the correct wind direction; however I've already decided that we'll favour azimuth going forward (see above).
 *
 * Comparison of pre-EEP and post-EEP legacy values:
 *   east angle = 0 (aka azimuth = 0)   -> y = 0 so e = atan2(z, x) -> elevation has a range of 2PI so we correctly report pre-EEP values
 *   sunmoonpos = 0 (aka elevation = 0) -> z = 0 so a = atan2(y, x) -> azimuth has a range of 2PI so we correctly report pre-EEP values
 *   -PI/2 < sunmoonpos < PI/2          -> general case             -> post-EEP ranges match pre-EEP ranges so we correctly report pre-EEP values
 *   sunmoonpos > PI/2                  -> elevation went beyond our new maxium so the post-EEP sunmoonpos will actually be off by PI/2 (or 0.25)
 *                                         (and the resulting east angle is off by PI or 0.5 - for example smp 0.375 and ea 0.875 are equivalent with smp 0.125 and ea 0.375)
 *
 * In reverse this means that when setting values through RLVa:
 *   sunmoonpos without eastangle (=0) => always correct
 *   eastangle without sunmoonpos (=0) => always correct
 *   eastangle before sunmoonpos       => always correct
 *   sunmoonpos before eastangle       => correct   for -0.25 <= sunmoonpos <= 0.25
 *                                        incorrect for  0.75  > sunmoonpos  > 0.25
 */
F32 rlvGetAzimuthFromDirectionVector(const LLVector3& vecDir)
{
	if (is_zero(vecDir.mV[VY]))
		return 0.f;
	else if (is_zero(vecDir.mV[VX]))
		return F_PI_BY_TWO;

	F32 radAzimuth = atan2f(vecDir.mV[VY], vecDir.mV[VX]);
	return (radAzimuth >= 0.f) ? radAzimuth : radAzimuth + F_TWO_PI;
}

F32 rlvGetElevationFromDirectionVector(const LLVector3& vecDir)
{
	if (is_zero(vecDir.mV[VZ]))
		return 0.f;

	F32 radElevation;
	if ( (is_zero(vecDir.mV[VX])) && (!is_zero(vecDir.mV[VY])) )
		radElevation = atan2f(vecDir.mV[VZ], vecDir.mV[VY]);
	else if ( (!is_zero(vecDir.mV[VX])) && (is_zero(vecDir.mV[VY])) )
		radElevation = atan2f(vecDir.mV[VZ], vecDir.mV[VX]);
	else
		radElevation = asinf(vecDir.mV[VZ]);
	return (radElevation >= 0.f) ? radElevation : radElevation + F_TWO_PI;
}

// Defined in llsettingssky.cpp
LLQuaternion convert_azimuth_and_altitude_to_quat(F32 azimuth, F32 altitude);

// ================================================================================================
// RlvIsOfSettingsType - Inventory collector for settings of a specific subtype
//

class RlvIsOfSettingsType : public LLInventoryCollectFunctor
{
public:
	RlvIsOfSettingsType(LLSettingsType::type_e eSettingsType, const std::string& strNameMatch = LLStringUtil::null)
		: m_eSettingsType(eSettingsType)
		, m_strNameMatch(strNameMatch)
	{
	}

	~RlvIsOfSettingsType() override
	{
	}

	bool operator()(LLInventoryCategory*, LLInventoryItem* pItem) override
	{
		if ( (pItem) && (LLAssetType::AT_SETTINGS == pItem->getActualType()) )
		{
			return
				(m_eSettingsType == LLSettingsType::fromInventoryFlags(pItem->getFlags())) &&
				( (m_strNameMatch.empty()) || (boost::iequals(pItem->getName(), m_strNameMatch)) );
		}
		return false;
	}

protected:
	LLSettingsType::type_e m_eSettingsType;
	std::string            m_strNameMatch;
};

// ================================================================================================
// RlvEnvironment
//

RlvEnvironment::RlvEnvironment()
{
	//
	// Presets
	//
	registerSetEnvFn<LLUUID>("asset", [](LLEnvironment::EnvSelection_t env, const LLUUID& idAsset)
		{
			if (idAsset.isNull())
				return RLV_RET_FAILED_OPTION;

			LLEnvironment::instance().setEnvironment(env, idAsset);
			return RLV_RET_SUCCESS;
		});
	// Deprecated
	auto fnApplyLibraryPreset = [](LLEnvironment::EnvSelection_t env, const std::string& strPreset, LLSettingsType::type_e eSettingsType)
		{
			LLUUID idAsset(strPreset);
			if (idAsset.isNull())
			{
				const LLUUID& idLibraryEnv = rlvGetLibraryEnvironmentsFolder();
				LLInventoryModel::cat_array_t cats;
				LLInventoryModel::item_array_t items;
				RlvIsOfSettingsType f(eSettingsType, strPreset);
				gInventory.collectDescendentsIf(idLibraryEnv, cats, items, LLInventoryModel::EXCLUDE_TRASH, f);
				if (!items.empty())
					idAsset = items.front()->getAssetUUID();
			}

			if (idAsset.isNull())
				return RLV_RET_FAILED_OPTION;

			LLEnvironment::instance().setEnvironment(env, idAsset);
			return RLV_RET_SUCCESS;
		};
	registerSetEnvFn<std::string>("preset",   [&fnApplyLibraryPreset](LLEnvironment::EnvSelection_t env, const std::string& strPreset) { return fnApplyLibraryPreset(env, strPreset, LLSettingsType::ST_SKY); });
	registerSetEnvFn<std::string>("daycycle", [&fnApplyLibraryPreset](LLEnvironment::EnvSelection_t env, const std::string& strPreset) { return fnApplyLibraryPreset(env, strPreset, LLSettingsType::ST_DAYCYCLE); });

	//
	// Atmosphere & Lighting tab
	//

	// SETTING_AMBIENT
	registerSkyFn<LLColor3>("ambient",		[](LLSettingsSky::ptr_t pSky) { return pSky->getAmbientColor() * (1.f / SLIDER_SCALE_SUN_AMBIENT); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setAmbientColor(clrValue * SLIDER_SCALE_SUN_AMBIENT); });
	registerLegacySkyFn<LLColor3>("ambient",[](LLSettingsSky::ptr_t pSky) { return pSky->getAmbientColor() * (1.f / SLIDER_SCALE_SUN_AMBIENT); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setAmbientColor(clrValue * SLIDER_SCALE_SUN_AMBIENT); });

	// SETTING_BLUE_DENSITY
	registerSkyFn<LLColor3>("bluedensity",	[](LLSettingsSky::ptr_t pSky) { return pSky->getBlueDensity() * (1.f / SLIDER_SCALE_BLUE_HORIZON_DENSITY); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setBlueDensity(clrValue * SLIDER_SCALE_BLUE_HORIZON_DENSITY); });
	registerLegacySkyFn<LLColor3>("bluedensity",[](LLSettingsSky::ptr_t pSky) { return pSky->getBlueDensity() * (1.f / SLIDER_SCALE_BLUE_HORIZON_DENSITY); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setBlueDensity(clrValue * SLIDER_SCALE_BLUE_HORIZON_DENSITY); });

	// SETTING_BLUE_HORIZON
	registerSkyFn<LLColor3>("bluehorizon",	[](LLSettingsSky::ptr_t pSky) { return pSky->getBlueHorizon() * (1.f / SLIDER_SCALE_BLUE_HORIZON_DENSITY); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setBlueHorizon(clrValue * SLIDER_SCALE_BLUE_HORIZON_DENSITY); });
	registerLegacySkyFn<LLColor3>("bluehorizon",[](LLSettingsSky::ptr_t pSky) { return pSky->getBlueHorizon() * (1.f / SLIDER_SCALE_BLUE_HORIZON_DENSITY); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setBlueHorizon(clrValue * SLIDER_SCALE_BLUE_HORIZON_DENSITY); });

	// SETTING_DENSITY_MULTIPLIER
	registerSkyFn<F32>("densitymultiplier",	[](LLSettingsSky::ptr_t pSky) { return pSky->getDensityMultiplier() / SLIDER_SCALE_DENSITY_MULTIPLIER; },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setDensityMultiplier(nValue * SLIDER_SCALE_DENSITY_MULTIPLIER); });

	// SETTING_DISTANCE_MULTIPLIER
	registerSkyFn<F32>("distancemultiplier",[](LLSettingsSky::ptr_t pSky) { return pSky->getDistanceMultiplier(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setDistanceMultiplier(nValue); });


	// SETTING_SKY_DROPLET_RADIUS
	registerSkyFn<F32>("dropletradius",		[](LLSettingsSky::ptr_t pSky) { return pSky->getSkyDropletRadius(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setSkyDropletRadius(nValue); });

	// SETTING_HAZE_DENSITY
	registerSkyFn<F32>("hazedensity",		[](LLSettingsSky::ptr_t pSky) { return pSky->getHazeDensity(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setHazeDensity(nValue); });

	// SETTING_HAZE_HORIZON
	registerSkyFn<F32>("hazehorizon",		[](LLSettingsSky::ptr_t pSky) { return pSky->getHazeHorizon(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setHazeHorizon(nValue); });

	// SETTING_SKY_ICE_LEVEL
	registerSkyFn<F32>("icelevel",			[](LLSettingsSky::ptr_t pSky) { return pSky->getSkyIceLevel(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setSkyIceLevel(nValue); });

	// SETTING_MAX_Y
	registerSkyFn<F32>("maxaltitude",		[](LLSettingsSky::ptr_t pSky) { return pSky->getMaxY(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setMaxY(nValue); });

	// SETTING_SKY_MOISTURE_LEVEL
	registerSkyFn<F32>("moisturelevel",		[](LLSettingsSky::ptr_t pSky) { return pSky->getSkyMoistureLevel(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setSkyMoistureLevel(nValue); });

	//	SETTING_GAMMA
	registerSkyFn<F32>("scenegamma",		[](LLSettingsSky::ptr_t pSky) { return pSky->getGamma(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setGamma(nValue); });

	//
	// Clouds tab
	//

	// SETTING_CLOUD_COLOR
	registerSkyFn<LLColor3>("cloudcolor",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudColor(); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudColor(clrValue); });
	registerLegacySkyFn<LLColor3>("cloudcolor",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudColor(); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudColor(clrValue); });

	// SETTING_CLOUD_SHADOW
	registerSkyFn<F32>("cloudcoverage",		[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudShadow(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setCloudShadow(nValue); });

	// SETTING_CLOUD_POS_DENSITY1
	registerSkyFn<LLColor3>("clouddensity",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudPosDensity1(); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudPosDensity1(clrValue); });
	registerLegacySkyFn<LLColor3>("cloud",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudPosDensity1(); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudPosDensity1(clrValue); });

	// SETTING_CLOUD_POS_DENSITY2
	registerSkyFn<LLColor3>("clouddetail",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudPosDensity2(); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudPosDensity2(clrValue); });
	registerLegacySkyFn<LLColor3>("clouddetail",[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudPosDensity2(); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudPosDensity2(clrValue); });

	// SETTING_CLOUD_SCALE
	registerSkyFn<F32>("cloudscale",		[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudScale(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setCloudScale(nValue); });

	// SETTING_CLOUD_SCROLL_RATE
	registerSkyFn<LLVector2>("cloudscroll",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudScrollRate(); },
											[](LLSettingsSky::ptr_t pSky, const LLVector2& vecValue) { pSky->setCloudScrollRate(vecValue); });
	registerLegacySkyFn<LLVector2>("cloudscroll", [](LLSettingsSky::ptr_t pSky) { return pSky->getCloudScrollRate(); },
											[](LLSettingsSky::ptr_t pSky, const LLVector2& vecValue) { pSky->setCloudScrollRate(vecValue); });

	// SETTING_CLOUD_TEXTUREID
	registerSkyFn<LLUUID>("cloudtexture",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudNoiseTextureId();  },
											[](LLSettingsSky::ptr_t pSky, const LLUUID& idTexture) { pSky->setCloudNoiseTextureId(idTexture);  });

	// SETTING_CLOUD_VARIANCE
	registerSkyFn<F32>("cloudvariance",		[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudVariance(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setCloudVariance(nValue); });

	//
	// Sun & Moon
	//

	// SETTING_MOON_BRIGHTNESS
	registerSkyFn<F32>("moonbrightness",	[](LLSettingsSky::ptr_t pSky) { return pSky->getMoonBrightness(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setMoonBrightness(nValue); });

	// SETTING_MOON_SCALE
	registerSkyFn<F32>("moonscale",			[](LLSettingsSky::ptr_t pSky) { return pSky->getMoonScale(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setMoonScale(nValue); });

	// SETTING_MOON_TEXTUREID
	registerSkyFn<LLUUID>("moontexture",	[](LLSettingsSky::ptr_t pSky) { return pSky->getMoonTextureId(); },
											[](LLSettingsSky::ptr_t pSky, const LLUUID& idTexture) { pSky->setMoonTextureId(idTexture); });

	// SETTING_GLOW
	registerSkyFn<float>("sunglowsize",		[](LLSettingsSky::ptr_t pSky) { return 2.0 - (pSky->getGlow().mV[VRED] / SLIDER_SCALE_GLOW_R); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setGlow(LLColor3((2.0f - nValue) * SLIDER_SCALE_GLOW_R, .0f, pSky->getGlow().mV[VBLUE])); });
	registerSkyFn<float>("sunglowfocus",	[](LLSettingsSky::ptr_t pSky) { return pSky->getGlow().mV[VBLUE] / SLIDER_SCALE_GLOW_B; },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setGlow(LLColor3(pSky->getGlow().mV[VRED], .0f, nValue * SLIDER_SCALE_GLOW_B)); });

	// SETTING_SUNLIGHT_COLOR
	registerSkyFn<LLColor3>("sunlightcolor",[](LLSettingsSky::ptr_t pSky) { return pSky->getSunlightColor() * (1.f / SLIDER_SCALE_SUN_AMBIENT); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setSunlightColor(clrValue * SLIDER_SCALE_SUN_AMBIENT); });
	registerLegacySkyFn<LLColor3>("sunmooncolor", [](LLSettingsSky::ptr_t pSky) { return pSky->getSunlightColor() * (1.f / SLIDER_SCALE_SUN_AMBIENT); },
											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setSunlightColor(clrValue * SLIDER_SCALE_SUN_AMBIENT); });

	// SETTING_SUN_SCALE
	registerSkyFn<float>("sunscale",		[](LLSettingsSky::ptr_t pSky) { return pSky->getSunScale(); },
											[](LLSettingsSky::ptr_t pSky, F32 nValue) { pSky->setSunScale(nValue); });

	// SETTING_SUN_TEXTUREID
	registerSkyFn<LLUUID>("suntexture",		[](LLSettingsSky::ptr_t pSky) { return pSky->getSunTextureId(); },
											[](LLSettingsSky::ptr_t pSky, const LLUUID& idTexture) { pSky->setSunTextureId(idTexture); });

	// SETTING_STAR_BRIGHTNESS
	registerSkyFn<F32>("starbrightness",	[](LLSettingsSky::ptr_t pSky) { return pSky->getStarBrightness(); },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setStarBrightness(nValue); });

	// SETTING_SUN_ROTATION
	registerSkyFn<F32>("sunazimuth",		[](LLSettingsSky::ptr_t pSky) { return rlvGetAzimuthFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation()); },
											[](LLSettingsSky::ptr_t pSky, const F32& radAzimuth) {
												pSky->setSunRotation(convert_azimuth_and_altitude_to_quat(radAzimuth, rlvGetElevationFromDirectionVector(LLVector3::x_axis* pSky->getSunRotation())));
											});
	registerSkyFn<F32>("sunelevation",		[](LLSettingsSky::ptr_t pSky) { return rlvGetElevationFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation()); },
											[](LLSettingsSky::ptr_t pSky, F32 radElevation) {
												radElevation = llclamp(radElevation, -F_PI_BY_TWO, F_PI_BY_TWO);
												pSky->setSunRotation(convert_azimuth_and_altitude_to_quat(rlvGetAzimuthFromDirectionVector(LLVector3::x_axis* pSky->getSunRotation()), radElevation));
											});

	// SETTING_MOON_ROTATION
	registerSkyFn<F32>("moonazimuth",		[](LLSettingsSky::ptr_t pSky) { return rlvGetAzimuthFromDirectionVector(LLVector3::x_axis * pSky->getMoonRotation()); },
											[](LLSettingsSky::ptr_t pSky, const F32& radAzimuth) {
												pSky->setMoonRotation(convert_azimuth_and_altitude_to_quat(radAzimuth, rlvGetElevationFromDirectionVector(LLVector3::x_axis* pSky->getMoonRotation())));
											});
	registerSkyFn<F32>("moonelevation",		[](LLSettingsSky::ptr_t pSky) { return rlvGetElevationFromDirectionVector(LLVector3::x_axis * pSky->getMoonRotation()); },
											[](LLSettingsSky::ptr_t pSky, F32 radElevation) {
												radElevation = llclamp(radElevation, -F_PI_BY_TWO, F_PI_BY_TWO);
												pSky->setMoonRotation(convert_azimuth_and_altitude_to_quat(rlvGetAzimuthFromDirectionVector(LLVector3::x_axis* pSky->getMoonRotation()), radElevation));
											});

	// Legacy WindLight support (see remarks at the top of this file)
	registerSkyFn<F32>("eastangle",			[](LLSettingsSky::ptr_t pSky) { return normalize_angle_domain(-rlvGetAzimuthFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation())) / F_TWO_PI; },
											[](LLSettingsSky::ptr_t pSky, const F32& radEastAngle)
											{
												const F32 radAzimuth = -radEastAngle * F_TWO_PI;
												const F32 radElevation = rlvGetElevationFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation());
												pSky->setSunRotation(convert_azimuth_and_altitude_to_quat(radAzimuth, radElevation));
												pSky->setMoonRotation(convert_azimuth_and_altitude_to_quat(radAzimuth + F_PI, -radElevation));
											});

	registerSkyFn<F32>("sunmoonposition",	[](LLSettingsSky::ptr_t pSky) { return rlvGetElevationFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation()) / F_TWO_PI; },
											[](LLSettingsSky::ptr_t pSky, const F32& nValue)
											{
												const F32 radAzimuth = rlvGetAzimuthFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation());
												const F32 radElevation = nValue * F_TWO_PI;
												pSky->setSunRotation(convert_azimuth_and_altitude_to_quat(radAzimuth, radElevation));
												pSky->setMoonRotation(convert_azimuth_and_altitude_to_quat(radAzimuth + F_PI, -radElevation));
											});

	// Create a fixed sky from the nearest daycycle (local > experience > parcel > region)
	registerSetEnvFn<F32>("daytime",		[](LLEnvironment::EnvSelection_t env, const F32& nValue)
											{
												if ((nValue >= 0.f) && (nValue <= 1.0f))
												{
													LLSettingsDay::ptr_t pDay;
													if (LLEnvironment::ENV_EDIT != env)
													{
														LLEnvironment::EnvSelection_t envs[] = { LLEnvironment::ENV_LOCAL, LLEnvironment::ENV_PUSH, LLEnvironment::ENV_PARCEL, LLEnvironment::ENV_REGION };
														for (size_t idxEnv = 0, cntEnv = sizeof(envs) / sizeof(LLEnvironment::EnvSelection_t); idxEnv < cntEnv && !pDay; idxEnv++)
															pDay = LLEnvironment::instance().getEnvironmentDay(envs[idxEnv]);
													}
													else
													{
														pDay = LLEnvironment::instance().getEnvironmentDay(LLEnvironment::ENV_EDIT);
													}

													if (pDay)
													{
														auto pNewSky = LLSettingsVOSky::buildDefaultSky();
														auto pSkyBlender = std::make_shared<LLTrackBlenderLoopingManual>(pNewSky, pDay, 1);
														pSkyBlender->setPosition(nValue);

														LLEnvironment::instance().setEnvironment(env, pNewSky);
														LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
													}
												}
												else if (nValue == -1)
												{
													LLEnvironment::instance().clearEnvironment(env);
													LLEnvironment::instance().setSelectedEnvironment(env);
													LLEnvironment::instance().updateEnvironment();
//													defocusEnvFloaters();
												}
												else
												{
													return RLV_RET_FAILED_OPTION;
												}

												return RLV_RET_SUCCESS;
											});
	registerGetEnvFn("daytime",				[](LLEnvironment::EnvSelection_t env)
											{
												// I forgot how much I hate this command... it literally makes no sense since time of day only has any meaning in an
												// actively animating day cycle (but in that case we have to return -1).
												if (!LLEnvironment::instance().getEnvironmentFixedSky(env)) {
													return std::to_string(-1.f);
												}

												// It's invalid input for @setenv_daytime (see above) so it can be fed in without changing the current environment
												return std::to_string(2.f);
											});
}

RlvEnvironment::~RlvEnvironment()
{
}

// static
LLEnvironment::EnvSelection_t RlvEnvironment::getTargetEnvironment()
{
	return RlvActions::canChangeEnvironment() ? LLEnvironment::ENV_LOCAL : LLEnvironment::ENV_EDIT;
}

// static
LLSettingsSky::ptr_t RlvEnvironment::getTargetSky(bool forSetCmd)
{
	LLEnvironment* pEnv = LLEnvironment::getInstance();

	if (forSetCmd)
	{
		LLEnvironment::EnvSelection_t targetEnv = getTargetEnvironment();
		bool isSharedEnv = !pEnv->getEnvironmentFixedSky(targetEnv),
			 hasLocalDayCycle = !isSharedEnv && pEnv->getEnvironmentDay(targetEnv),
			 isLocalTransition = !hasLocalDayCycle  && pEnv->getCurrentEnvironmentInstance()->isTransition();
		if ( (isSharedEnv) || (hasLocalDayCycle) || (isLocalTransition) )
		{
			LLSettingsSky::ptr_t pSky = (isSharedEnv) ? pEnv->getEnvironmentFixedSky(LLEnvironment::ENV_PARCEL, true)->buildClone()
				                                      : (hasLocalDayCycle) ? pEnv->getEnvironmentFixedSky(targetEnv)->buildClone()
			                                                               : pEnv->getEnvironmentFixedSky(targetEnv);
			pEnv->setEnvironment(targetEnv, pSky);
			pEnv->setSelectedEnvironment(targetEnv, LLEnvironment::TRANSITION_INSTANT);
			pEnv->updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
		}
	}

	return pEnv->getCurrentSky();
}


// static
bool RlvEnvironment::onHandleCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet, const std::string& strCmdPrefix, const handler_map_t& fnLookup, const legacy_handler_map_t& legacyFnLookup)
{
	if ( (rlvCmd.getBehaviour().length() > strCmdPrefix.length() + 2) && (boost::starts_with(rlvCmd.getBehaviour(), strCmdPrefix)) )
	{
		if ( (RLV_TYPE_FORCE == rlvCmd.getParamType()) && (!RlvActions::canChangeEnvironment(rlvCmd.getObjectID())) )
		{
			cmdRet = RLV_RET_FAILED_LOCK;
			return true;
		}

		std::string strEnvCommand = rlvCmd.getBehaviour().substr(strCmdPrefix.length());

		handler_map_t::const_iterator itFnEntry = fnLookup.find(strEnvCommand);
		if (fnLookup.end() != itFnEntry)
		{
			cmdRet = itFnEntry->second((RLV_TYPE_FORCE == rlvCmd.getParamType()) ? rlvCmd.getOption() : rlvCmd.getParam());
			return true;
		}

		// Legacy handling (blargh)
		U32 idxComponent = rlvGetColorComponentFromCharacter(strEnvCommand.back());
		if (idxComponent <= VALPHA)
		{
			strEnvCommand.pop_back();

			legacy_handler_map_t::const_iterator itLegacyFnEntry = legacyFnLookup.find(strEnvCommand);
			if (legacyFnLookup.end() != itLegacyFnEntry)
			{
				cmdRet = itLegacyFnEntry->second((RLV_TYPE_FORCE == rlvCmd.getParamType()) ? rlvCmd.getOption() : rlvCmd.getParam(), idxComponent);
				return true;
			}
		}
	}

	return false;
}

bool RlvEnvironment::onReplyCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet)
{
	return onHandleCommand(rlvCmd, cmdRet, RLV_GETENV_PREFIX, m_GetFnLookup, m_LegacyGetFnLookup);
}

bool RlvEnvironment::onForceCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet)
{
	return onHandleCommand(rlvCmd, cmdRet, RLV_SETENV_PREFIX, m_SetFnLookup, m_LegacySetFnLookup);
}

template<>
std::string RlvEnvironment::handleGetFn<float>(const std::function<float(LLSettingsSky::ptr_t)>& fn)
{
	LLSettingsSky::ptr_t pSky = getTargetSky();
	return std::to_string(fn(pSky));
}

template<>
std::string RlvEnvironment::handleGetFn<LLUUID>(const std::function<LLUUID(LLSettingsSky::ptr_t)>& fn)
{
	LLSettingsSky::ptr_t pSky = getTargetSky();
	return fn(pSky).asString();
}

template<>
std::string RlvEnvironment::handleGetFn<LLVector2>(const std::function<LLVector2(LLSettingsSky::ptr_t)>& fn)
{
	LLSettingsSky::ptr_t pSky = getTargetSky();
	LLVector2 replyVec = fn(pSky);
	return llformat("%f/%f", replyVec.mV[VX], replyVec.mV[VY]);
}

template<>
std::string RlvEnvironment::handleGetFn<LLColor3>(const std::function<LLColor3(LLSettingsSky::ptr_t)>& fn)
{
	LLSettingsSky::ptr_t pSky = getTargetSky();
	LLColor3 replyColor = fn(pSky);
	return llformat("%f/%f/%f", replyColor.mV[VX], replyColor.mV[VY], replyColor.mV[VZ]);
}

template<typename T>
ERlvCmdRet RlvEnvironment::handleSetFn(const std::string& strRlvOption, const std::function<void(LLSettingsSky::ptr_t, const T&)>& fn)
{
	T optionValue;
	if (!RlvCommandOptionHelper::parseOption<T>(strRlvOption, optionValue))
		return RLV_RET_FAILED_PARAM;

	LLSettingsSky::ptr_t pSky = getTargetSky(true);
	fn(pSky, optionValue);
	pSky->update();
	return RLV_RET_SUCCESS;
}

template<>
std::string RlvEnvironment::handleLegacyGetFn<LLVector2>(const std::function<const LLVector2 (LLSettingsSkyPtr_t)>& getFn, U32 idxComponent)
{
	if (idxComponent >= 2)
		return LLStringUtil::null;
	return std::to_string(getFn(getTargetSky()).mV[idxComponent]);
}

template<>
std::string RlvEnvironment::handleLegacyGetFn<LLColor3>(const std::function<const LLColor3 (LLSettingsSkyPtr_t)>& getFn, U32 idxComponent)
{
	if ( (idxComponent >= VRED) && (idxComponent <= VBLUE) )
	{
		return std::to_string(getFn(getTargetSky()).mV[idxComponent]);
	}
	else if (idxComponent == VALPHA)
	{
		const LLColor3& clr = getFn(getTargetSky());
		return std::to_string(llmax(clr.mV[VRED], clr.mV[VGREEN], clr.mV[VBLUE]));
	}
	return LLStringUtil::null;
}

template<>
ERlvCmdRet RlvEnvironment::handleLegacySetFn<LLVector2>(float optionValue, LLVector2 curValue, const std::function<void(LLSettingsSkyPtr_t, const LLVector2&)>& setFn, U32 idxComponent)
{
	if (idxComponent >= 2)
		return RLV_RET_FAILED_UNKNOWN;

	LLSettingsSky::ptr_t pSky = getTargetSky(true);
	curValue.mV[idxComponent] = optionValue;
	setFn(pSky, curValue);
	pSky->update();

	return RLV_RET_SUCCESS;
}

template<>
ERlvCmdRet RlvEnvironment::handleLegacySetFn<LLColor3>(float optionValue, LLColor3 curValue, const std::function<void(LLSettingsSkyPtr_t, const LLColor3&)>& setFn, U32 idxComponent)
{
	LLSettingsSky::ptr_t pSky = getTargetSky(true);
	if ( (idxComponent >= VRED) && (idxComponent <= VBLUE) )
	{
		curValue.mV[idxComponent] = optionValue;
	}
	else if (idxComponent == VALPHA)
	{
		const F32 curMax = llmax(curValue.mV[VRED], curValue.mV[VGREEN], curValue.mV[VBLUE]);
		if ( (0.0f == optionValue) || (0.0f == curMax) )
		{
			curValue.mV[VRED] = curValue.mV[VGREEN] = curValue.mV[VBLUE] = optionValue;
		}
		else
		{
			const F32 nDelta = (optionValue - curMax) / curMax;
			curValue.mV[VRED] *= (1.0f + nDelta);
			curValue.mV[VGREEN] *= (1.0f + nDelta);
			curValue.mV[VBLUE] *= (1.0f + nDelta);
		}
	}
	else
	{
		return RLV_RET_FAILED_UNKNOWN;
	}

	setFn(pSky, curValue);
	pSky->update();

	return RLV_RET_SUCCESS;
}


template<typename T>
void RlvEnvironment::registerSkyFn(const std::string& strFnName, const std::function<T(LLSettingsSkyPtr_t)>& getFn, const std::function<void(LLSettingsSkyPtr_t, const T&)>& setFn)
{
	RLV_ASSERT(m_GetFnLookup.end() == m_GetFnLookup.find(strFnName));
	m_GetFnLookup.insert(std::make_pair(strFnName, [this, getFn](const std::string& strRlvParam)
		{
			if (RlvUtil::sendChatReply(strRlvParam, handleGetFn<T>(getFn)))
				return RLV_RET_SUCCESS;
			return RLV_RET_FAILED_PARAM;
		}));

	RLV_ASSERT(m_SetFnLookup.end() == m_SetFnLookup.find(strFnName));
	m_SetFnLookup.insert(std::make_pair(strFnName, [this, setFn](const std::string& strRlvOption)
		{
			return handleSetFn<T>(strRlvOption, setFn);
		}));
}

void RlvEnvironment::registerGetEnvFn(const std::string& strFnName, const std::function<std::string(LLEnvironment::EnvSelection_t env)>& getFn)
{
	RLV_ASSERT(m_GetFnLookup.end() == m_GetFnLookup.find(strFnName));
	m_GetFnLookup.insert(std::make_pair(strFnName, [getFn](const std::string& strRlvParam)
		{
			if (RlvUtil::sendChatReply(strRlvParam, getFn(getTargetEnvironment())))
				return RLV_RET_SUCCESS;
			return RLV_RET_FAILED_PARAM;
		}));
}

template<typename T>
void RlvEnvironment::registerSetEnvFn(const std::string& strFnName, const std::function<ERlvCmdRet(LLEnvironment::EnvSelection_t env, const T& strRlvOption)>& setFn)
{
	RLV_ASSERT(m_SetFnLookup.end() == m_SetFnLookup.find(strFnName));
	m_SetFnLookup.insert(std::make_pair(strFnName, [setFn](const std::string& strRlvOption)
		{
			T optionValue;
			if (!RlvCommandOptionHelper::parseOption<T>(strRlvOption, optionValue))
				return RLV_RET_FAILED_PARAM;
			return setFn(getTargetEnvironment(), optionValue);
		}));
}

template<typename T>
void RlvEnvironment::registerLegacySkyFn(const std::string& strFnName, const std::function<const T (LLSettingsSkyPtr_t)>& getFn, const std::function<void(LLSettingsSkyPtr_t, const T&)>& setFn)
{
	RLV_ASSERT(m_LegacyGetFnLookup.end() == m_LegacyGetFnLookup.find(strFnName));
	m_LegacyGetFnLookup.insert(std::make_pair(strFnName, [this, getFn](const std::string& strRlvParam, U32 idxComponent)
		{
			const std::string strReply = handleLegacyGetFn<T>(getFn, idxComponent);
			if (strReply.empty())
				return RLV_RET_FAILED_UNKNOWN;
			else if (RlvUtil::sendChatReply(strRlvParam, strReply))
				return RLV_RET_SUCCESS;
			return RLV_RET_FAILED_PARAM;
		}));

	RLV_ASSERT(m_LegacySetFnLookup.end() == m_LegacySetFnLookup.find(strFnName));
	m_LegacySetFnLookup.insert(std::make_pair(strFnName, [this, getFn, setFn](const std::string& strRlvOption, U32 idxComponent)
		{
			float optionValue;
			if (!RlvCommandOptionHelper::parseOption(strRlvOption, optionValue))
				return RLV_RET_FAILED_PARAM;
			return handleLegacySetFn<T>(optionValue, getFn(getTargetSky(true)), setFn, idxComponent);;
		}));
}

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