#include "weather.hpp"

#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"

#include "../mwrender/renderingmanager.hpp"

#include "player.hpp"
#include "esmstore.hpp"
#include "fallback.hpp"

using namespace Ogre;
using namespace MWWorld;
using namespace MWSound;

namespace
{
    float lerp (float x, float y, float factor)
    {
        return x * (1-factor) + y * factor;
    }

    Ogre::ColourValue lerp (const Ogre::ColourValue& x, const Ogre::ColourValue& y, float factor)
    {
        return x * (1-factor) + y * factor;
    }
}

void WeatherManager::setFallbackWeather(Weather& weather,const std::string& name)
{
    std::string upper=name;
    upper[0]=toupper(name[0]);
    weather.mCloudsMaximumPercent = mFallback->getFallbackFloat("Weather_"+upper+"_Clouds_Maximum_Percent");
    weather.mTransitionDelta = mFallback->getFallbackFloat("Weather_"+upper+"_Transition_Delta");
    weather.mSkySunriseColor=mFallback->getFallbackColour("Weather_"+upper+"_Sky_Sunrise_Color");
    weather.mSkyDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Day_Color");
    weather.mSkySunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Sunset_Color");
    weather.mSkyNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Night_Color");
    weather.mFogSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Sunrise_Color");
    weather.mFogDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Day_Color");
    weather.mFogSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Sunset_Color");
    weather.mFogNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Night_Color");
    weather.mAmbientSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Sunrise_Color");
    weather.mAmbientDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Day_Color");
    weather.mAmbientSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Sunset_Color");
    weather.mAmbientNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Night_Color");
    weather.mSunSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Sunrise_Color");
    weather.mSunDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Day_Color");
    weather.mSunSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Sunset_Color");
    weather.mSunNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Night_Color");
    weather.mSunDiscSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Disc_Sunset_Color");
    weather.mLandFogDayDepth = mFallback->getFallbackFloat("Weather_"+upper+"_Land_Fog_Day_Depth");
    weather.mLandFogNightDepth = mFallback->getFallbackFloat("Weather_"+upper+"_Land_Fog_Night_Depth");
    weather.mWindSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Wind_Speed");
    weather.mCloudSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Cloud_Speed");
    weather.mGlareView = mFallback->getFallbackFloat("Weather_"+upper+"_Glare_View");
    mWeatherSettings[name] = weather;
}


float WeatherManager::calculateHourFade (const std::string& moonName) const
{
    float fadeOutStart=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Out_Start");
    float fadeOutFinish=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Out_Finish");
    float fadeInStart=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_In_Start");
    float fadeInFinish=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_In_Finish");

    if (mHour >= fadeOutStart && mHour <= fadeOutFinish)
        return (1 - ((mHour - fadeOutStart) / (fadeOutFinish - fadeOutStart)));
    if (mHour >= fadeInStart && mHour <= fadeInFinish)
        return (1 - ((mHour - fadeInStart) / (fadeInFinish - fadeInStart)));
    else
        return 1;
}

float WeatherManager::calculateAngleFade (const std::string& moonName, float angle) const
{
    float endAngle=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_End_Angle");
    float startAngle=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Start_Angle");
    if (angle <= startAngle && angle >= endAngle)
        return (1 - ((angle - endAngle)/(startAngle-endAngle)));
    else if (angle > startAngle)
        return 0.f;
    else
        return 1.f;
}

WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fallback* fallback) :
     mHour(14), mCurrentWeather("clear"), mNextWeather(""), mFirstUpdate(true),
     mWeatherUpdateTime(0), mThunderFlash(0), mThunderChance(0),
     mThunderChanceNeeded(50), mThunderSoundDelay(0), mRemainingTransitionTime(0),
     mMonth(0), mDay(0), mTimePassed(0), mFallback(fallback), mWindSpeed(0.f),
     mRendering(rendering)
{
    //Globals
    mThunderSoundID0 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_0");
    mThunderSoundID1 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_1");
    mThunderSoundID2 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_2");
    mThunderSoundID3 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_3");
    mSunriseTime = mFallback->getFallbackFloat("Weather_Sunrise_Time");
    mSunsetTime = mFallback->getFallbackFloat("Weather_Sunset_Time");
    mSunriseDuration = mFallback->getFallbackFloat("Weather_Sunrise_Duration");
    mSunsetDuration = mFallback->getFallbackFloat("Weather_Sunset_Duration");
    mHoursBetweenWeatherChanges = mFallback->getFallbackFloat("Weather_Hours_Between_Weather_Changes");
    mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600;
    mThunderFrequency = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Frequency");
    mThunderThreshold = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Threshold");
    mThunderSoundDelay = 0.25;

    //Some useful values
    /* TODO: Use pre-sunrise_time, pre-sunset_time,
     * post-sunrise_time, and post-sunset_time to better
     * describe sunrise/sunset time.
     * These values are fallbacks attached to weather.
     */
    mNightStart = mSunsetTime + mSunsetDuration;
    mNightEnd = mSunriseTime - 0.5;
    mDayStart = mSunriseTime + mSunriseDuration;
    mDayEnd = mSunsetTime;

    //Weather
    Weather clear;
    clear.mCloudTexture = "tx_sky_clear.dds";
    setFallbackWeather(clear,"clear");

    Weather cloudy;
    cloudy.mCloudTexture = "tx_sky_cloudy.dds";
    setFallbackWeather(cloudy,"cloudy");

    Weather foggy;
    foggy.mCloudTexture = "tx_sky_foggy.dds";
    setFallbackWeather(foggy,"foggy");

    Weather thunderstorm;
    thunderstorm.mCloudTexture = "tx_sky_thunder.dds";
    thunderstorm.mRainLoopSoundID = "rain heavy";
    setFallbackWeather(thunderstorm,"thunderstorm");

    Weather rain;
    rain.mCloudTexture = "tx_sky_rainy.dds";
    rain.mRainLoopSoundID = "rain";
    setFallbackWeather(rain,"rain");

    Weather overcast;
    overcast.mCloudTexture = "tx_sky_overcast.dds";
    setFallbackWeather(overcast,"overcast");

    Weather ashstorm;
    ashstorm.mCloudTexture = "tx_sky_ashstorm.dds";
    ashstorm.mAmbientLoopSoundID = "ashstorm";
    setFallbackWeather(ashstorm,"ashstorm");

    Weather blight;
    blight.mCloudTexture = "tx_sky_blight.dds";
    blight.mAmbientLoopSoundID = "blight";
    setFallbackWeather(blight,"blight");

    Weather snow;
    snow.mCloudTexture = "tx_bm_sky_snow.dds";
    setFallbackWeather(snow, "snow");

    Weather blizzard;
    blizzard.mCloudTexture = "tx_bm_sky_blizzard.dds";
    blizzard.mAmbientLoopSoundID = "BM Blizzard";
    setFallbackWeather(blizzard,"blizzard");
}

WeatherManager::~WeatherManager()
{
    stopSounds(true);
}

void WeatherManager::setWeather(const String& weather, bool instant)
{
    if (weather == mCurrentWeather && mNextWeather == "")
    {
        mFirstUpdate = false;
        return;
    }

    if (instant || mFirstUpdate)
    {
        mNextWeather = "";
        mCurrentWeather = weather;
    }
    else
    {
        if (mNextWeather != "")
        {
            // transition more than 50% finished?
            if (mRemainingTransitionTime/(mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600) <= 0.5)
                mCurrentWeather = mNextWeather;
        }

        mNextWeather = weather;
        mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600.f;
    }
    mFirstUpdate = false;
}

void WeatherManager::setResult(const String& weatherType)
{
    const Weather& current = mWeatherSettings[weatherType];

    mResult.mCloudTexture = current.mCloudTexture;
    mResult.mCloudBlendFactor = 0;
    mResult.mCloudOpacity = current.mCloudsMaximumPercent;
    mResult.mWindSpeed = current.mWindSpeed;
    mResult.mCloudSpeed = current.mCloudSpeed;
    mResult.mGlareView = current.mGlareView;
    mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID;
    mResult.mSunColor = current.mSunDiscSunsetColor;

    mResult.mNight = (mHour < mSunriseTime || mHour > mNightStart - 1);

    mResult.mFogDepth = mResult.mNight ? current.mLandFogNightDepth : current.mLandFogDayDepth;

    // night
    if (mHour <= mNightEnd || mHour >= mNightStart + 1)
    {
        mResult.mFogColor = current.mFogNightColor;
        mResult.mAmbientColor = current.mAmbientNightColor;
        mResult.mSunColor = current.mSunNightColor;
        mResult.mSkyColor = current.mSkyNightColor;
        mResult.mNightFade = 1.f;
    }

    // sunrise
    else if (mHour >= mNightEnd && mHour <= mDayStart + 1)
    {
        if (mHour <= mSunriseTime)
        {
            // fade in
            float advance = mSunriseTime - mHour;
            float factor = advance / 0.5f;
            mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogNightColor, factor);
            mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientNightColor, factor);
            mResult.mSunColor = lerp(current.mSunSunriseColor, current.mSunNightColor, factor);
            mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyNightColor, factor);
            mResult.mNightFade = factor;
        }
        else //if (mHour >= 6)
        {
            // fade out
            float advance = mHour - mSunriseTime;
            float factor = advance / 3.f;
            mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogDayColor, factor);
            mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientDayColor, factor);
            mResult.mSunColor = lerp(current.mSunSunriseColor, current.mSunDayColor, factor);
            mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyDayColor, factor);
        }
    }

    // day
    else if (mHour >= mDayStart + 1 && mHour <= mDayEnd - 1)
    {
        mResult.mFogColor = current.mFogDayColor;
        mResult.mAmbientColor = current.mAmbientDayColor;
        mResult.mSunColor = current.mSunDayColor;
        mResult.mSkyColor = current.mSkyDayColor;
    }

    // sunset
    else if (mHour >= mDayEnd - 1 && mHour <= mNightStart + 1)
    {
        if (mHour <= mDayEnd + 1)
        {
            // fade in
            float advance = (mDayEnd + 1) - mHour;
            float factor = (advance / 2);
            mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogDayColor, factor);
            mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientDayColor, factor);
            mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunDayColor, factor);
            mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyDayColor, factor);
        }
        else //if (mHour >= 19)
        {
            // fade out
            float advance = mHour - (mDayEnd + 1);
            float factor = advance / 2.f;
            mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogNightColor, factor);
            mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientNightColor, factor);
            mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunNightColor, factor);
            mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyNightColor, factor);
            mResult.mNightFade = factor;
        }
    }
}

void WeatherManager::transition(float factor)
{
    setResult(mCurrentWeather);
    const WeatherResult current = mResult;
    setResult(mNextWeather);
    const WeatherResult other = mResult;

    mResult.mCloudTexture = current.mCloudTexture;
    mResult.mNextCloudTexture = other.mCloudTexture;
    mResult.mCloudBlendFactor = factor;

    mResult.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity, factor);
    mResult.mFogColor = lerp(current.mFogColor, other.mFogColor, factor);
    mResult.mSunColor = lerp(current.mSunColor, other.mSunColor, factor);
    mResult.mSkyColor = lerp(current.mSkyColor, other.mSkyColor, factor);

    mResult.mAmbientColor = lerp(current.mAmbientColor, other.mAmbientColor, factor);
    mResult.mSunDiscColor = lerp(current.mSunDiscColor, other.mSunDiscColor, factor);
    mResult.mFogDepth = lerp(current.mFogDepth, other.mFogDepth, factor);
    mResult.mWindSpeed = lerp(current.mWindSpeed, other.mWindSpeed, factor);
    mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor);
    mResult.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity, factor);
    mResult.mGlareView = lerp(current.mGlareView, other.mGlareView, factor);
    mResult.mNightFade = lerp(current.mNightFade, other.mNightFade, factor);

    mResult.mNight = current.mNight;
}

void WeatherManager::update(float duration)
{
    float timePassed = mTimePassed;
    mTimePassed = 0;

    mWeatherUpdateTime -= timePassed;

    MWBase::World* world = MWBase::Environment::get().getWorld();
    const bool exterior = (world->isCellExterior() || world->isCellQuasiExterior());
    if (!exterior)
    {
        mRendering->sunDisable(false);
        mRendering->skyDisable();
        mRendering->getSkyManager()->setLightningStrength(0.f);
        stopSounds(true);
        return;
    }

    switchToNextWeather(false);

    if (mNextWeather != "")
    {
        mRemainingTransitionTime -= timePassed;
        if (mRemainingTransitionTime < 0)
        {
            mCurrentWeather = mNextWeather;
            mNextWeather = "";
        }
    }

    if (mNextWeather != "")
        transition(1 - (mRemainingTransitionTime / (mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600)));
    else
        setResult(mCurrentWeather);

    mWindSpeed = mResult.mWindSpeed;

    mRendering->configureFog(mResult.mFogDepth, mResult.mFogColor);

    // disable sun during night
    if (mHour >= mNightStart || mHour <= mSunriseTime)
        mRendering->getSkyManager()->sunDisable();
    else
        mRendering->getSkyManager()->sunEnable();

    // sun angle
    float height;

    //Day duration
    float dayDuration = (mNightStart - 1) - mSunriseTime;

    // rise at 6, set at 20
    if (mHour >= mSunriseTime && mHour <= mNightStart)
        height = 1 - std::abs(((mHour - dayDuration) / 7.f));
    else if (mHour > mNightStart)
        height = (mHour - mNightStart) / 4.f;
    else //if (mHour > 0 && mHour < 6)
        height = 1 - (mHour / mSunriseTime);

    int facing = (mHour > 13.f) ? 1 : -1;

    Vector3 final(
            (height - 1) * facing,
            (height - 1) * facing,
            height);
    mRendering->setSunDirection(final);

    /*
     * TODO: import separated fadeInStart/Finish, fadeOutStart/Finish
     * for masser and secunda
     */

    float fadeOutFinish=mFallback->getFallbackFloat("Moons_Masser_Fade_Out_Finish");
    float fadeInStart=mFallback->getFallbackFloat("Moons_Masser_Fade_In_Start");

    //moon calculations
    float moonHeight;
    if (mHour >= fadeInStart)
        moonHeight = mHour - fadeInStart;
    else if (mHour <= fadeOutFinish)
        moonHeight = mHour + fadeOutFinish;
    else
        moonHeight = 0;

    moonHeight /= (24.f - (fadeInStart - fadeOutFinish));

    if (moonHeight != 0)
    {
        int facing = (moonHeight <= 1) ? 1 : -1;
        Vector3 masser(
                (moonHeight - 1) * facing,
                (1 - moonHeight) * facing,
                moonHeight);

        Vector3 secunda(
                (moonHeight - 1) * facing * 1.25,
                (1 - moonHeight) * facing * 0.8,
                moonHeight);

        mRendering->getSkyManager()->setMasserDirection(masser);
        mRendering->getSkyManager()->setSecundaDirection(secunda);
        mRendering->getSkyManager()->masserEnable();
        mRendering->getSkyManager()->secundaEnable();

        float angle = (1-moonHeight) * 90.f * facing;
        float masserHourFade = calculateHourFade("Masser");
        float secundaHourFade = calculateHourFade("Secunda");
        float masserAngleFade = calculateAngleFade("Masser", angle);
        float secundaAngleFade = calculateAngleFade("Secunda", angle);

        masserAngleFade *= masserHourFade;
        secundaAngleFade *= secundaHourFade;

        mRendering->getSkyManager()->setMasserFade(masserAngleFade);
        mRendering->getSkyManager()->setSecundaFade(secundaAngleFade);
    }
    else
    {
        mRendering->getSkyManager()->masserDisable();
        mRendering->getSkyManager()->secundaDisable();
    }

    if (mCurrentWeather == "thunderstorm" && mNextWeather == "")
    {
        if (mThunderFlash > 0)
        {
            // play the sound after a delay
            mThunderSoundDelay -= duration;
            if (mThunderSoundDelay <= 0)
            {
                // pick a random sound
                int sound = rand() % 4;
                std::string* soundName = NULL;
                if (sound == 0) soundName = &mThunderSoundID0;
                else if (sound == 1) soundName = &mThunderSoundID1;
                else if (sound == 2) soundName = &mThunderSoundID2;
                else if (sound == 3) soundName = &mThunderSoundID3;
                MWBase::Environment::get().getSoundManager()->playSound(*soundName, 1.0, 1.0);
                mThunderSoundDelay = 1000;
            }

            mThunderFlash -= duration;
            if (mThunderFlash > 0)
                mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold );
            else
            {
                mThunderChanceNeeded = rand() % 100;
                mThunderChance = 0;
                mRendering->getSkyManager()->setLightningStrength( 0.f );
            }
        }
        else
        {
            // no thunder active
            mThunderChance += duration*4; // chance increases by 4 percent every second
            if (mThunderChance >= mThunderChanceNeeded)
            {
                mThunderFlash = mThunderThreshold;

                mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold );

                mThunderSoundDelay = 0.25;
            }
        }
    }
    else
        mRendering->getSkyManager()->setLightningStrength(0.f);

    mRendering->setAmbientColour(mResult.mAmbientColor);
    mRendering->sunEnable(false);
    mRendering->setSunColour(mResult.mSunColor);

    mRendering->getSkyManager()->setWeather(mResult);


    // Play sounds
    if (mNextWeather == "")
    {
        std::string ambientSnd = mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID;
        if (!ambientSnd.empty() && std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), ambientSnd) == mSoundsPlaying.end())
        {
            mSoundsPlaying.push_back(ambientSnd);
            MWBase::Environment::get().getSoundManager()->playSound(ambientSnd, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
        }

        std::string rainSnd = mWeatherSettings[mCurrentWeather].mRainLoopSoundID;
        if (!rainSnd.empty() && std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), rainSnd) == mSoundsPlaying.end())
        {
            mSoundsPlaying.push_back(rainSnd);
            MWBase::Environment::get().getSoundManager()->playSound(rainSnd, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
        }
    }

    stopSounds(false);
}

void WeatherManager::stopSounds(bool stopAll)
{
    std::vector<std::string>::iterator it = mSoundsPlaying.begin();
    while (it!=mSoundsPlaying.end())
    {
        if (stopAll ||
                !((*it == mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID) ||
                (*it == mWeatherSettings[mCurrentWeather].mRainLoopSoundID)))
        {
            MWBase::Environment::get().getSoundManager()->stopSound(*it);
            it = mSoundsPlaying.erase(it);
        }
        else
            ++it;
    }
}

Ogre::String WeatherManager::nextWeather(const ESM::Region* region) const
{
    std::vector<char> probability;

    RegionModMap::const_iterator iter = mRegionMods.find(Misc::StringUtils::lowerCase(region->mId));
    if(iter != mRegionMods.end())
        probability = iter->second;
    else
    {
        probability.reserve(10);
        probability.push_back(region->mData.mClear);
        probability.push_back(region->mData.mCloudy);
        probability.push_back(region->mData.mFoggy);
        probability.push_back(region->mData.mOvercast);
        probability.push_back(region->mData.mRain);
        probability.push_back(region->mData.mThunder);
        probability.push_back(region->mData.mAsh);
        probability.push_back(region->mData.mBlight);
        probability.push_back(region->mData.mA);
        probability.push_back(region->mData.mB);
    }

    /*
     * All probabilities must add to 100 (responsibility of the user).
     * If chances A and B has values 30 and 70 then by generating
     * 100 numbers 1..100, 30% will be lesser or equal 30 and
     * 70% will be greater than 30 (in theory).
     */

    int chance = (rand() % 100) + 1; // 1..100
    int sum = 0;
    unsigned int i = 0;
    for (; i < probability.size(); ++i)
    {
        sum += probability[i];
        if (chance < sum)
            break;
    }

    switch (i)
    {
        case 1:
            return "cloudy";
        case 2:
            return "foggy";
        case 3:
            return "overcast";
        case 4:
            return "rain";
        case 5:
            return "thunderstorm";
        case 6:
            return "ashstorm";
        case 7:
            return "blight";
        case 8:
            return "snow";
        case 9:
            return "blizzard";
        default: // case 0
            return "clear";
    }
}

void WeatherManager::setHour(const float hour)
{
    mHour = hour;
}

void WeatherManager::setDate(const int day, const int month)
{
    mDay = day;
    mMonth = month;
}

unsigned int WeatherManager::getWeatherID() const
{
    // Source: http://www.uesp.net/wiki/Tes3Mod:GetCurrentWeather

    if (mCurrentWeather == "clear")
        return 0;
    else if (mCurrentWeather == "cloudy")
        return 1;
    else if (mCurrentWeather == "foggy")
        return 2;
    else if (mCurrentWeather == "overcast")
        return 3;
    else if (mCurrentWeather == "rain")
        return 4;
    else if (mCurrentWeather == "thunderstorm")
        return 5;
    else if (mCurrentWeather == "ashstorm")
        return 6;
    else if (mCurrentWeather == "blight")
        return 7;
    else if (mCurrentWeather == "snow")
        return 8;
    else if (mCurrentWeather == "blizzard")
        return 9;

    else
        return 0;
}

void WeatherManager::changeWeather(const std::string& region, const unsigned int id)
{
    // make sure this region exists
    MWBase::Environment::get().getWorld()->getStore().get<ESM::Region>().find(region);

    std::string weather;
    if (id==0)
        weather = "clear";
    else if (id==1)
        weather = "cloudy";
    else if (id==2)
        weather = "foggy";
    else if (id==3)
        weather = "overcast";
    else if (id==4)
        weather = "rain";
    else if (id==5)
        weather = "thunderstorm";
    else if (id==6)
        weather = "ashstorm";
    else if (id==7)
        weather = "blight";
    else if (id==8)
        weather = "snow";
    else if (id==9)
        weather = "blizzard";
    else
        weather = "clear";

    mRegionOverrides[Misc::StringUtils::lowerCase(region)] = weather;

    std::string playerRegion = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->mCell->mRegion;
    if (Misc::StringUtils::ciEqual(region, playerRegion))
        setWeather(weather);
}

void WeatherManager::modRegion(const std::string &regionid, const std::vector<char> &chances)
{
    mRegionMods[Misc::StringUtils::lowerCase(regionid)] = chances;
    // Start transitioning right away if the region no longer supports the current weather type
    unsigned int current = getWeatherID();
    if(current >= chances.size() || chances[current] == 0)
        mWeatherUpdateTime = 0.0f;
}

float WeatherManager::getWindSpeed() const
{
    return mWindSpeed;
}

bool WeatherManager::isDark() const
{
    bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior()
                     || MWBase::Environment::get().getWorld()->isCellQuasiExterior());
    return exterior && (mHour < mSunriseTime || mHour > mNightStart - 1);
}

void WeatherManager::switchToNextWeather(bool instantly)
{
    MWBase::World* world = MWBase::Environment::get().getWorld();
    if (world->isCellExterior() || world->isCellQuasiExterior())
    {
        std::string regionstr = Misc::StringUtils::lowerCase(world->getPlayerPtr().getCell()->mCell->mRegion);

        if (mWeatherUpdateTime <= 0 || regionstr != mCurrentRegion)
        {
            mCurrentRegion = regionstr;
            mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600;

            std::string weatherType = "clear";

            if (mRegionOverrides.find(regionstr) != mRegionOverrides.end())
            {
                weatherType = mRegionOverrides[regionstr];
            }
            else
            {
                // get weather probabilities for the current region
                const ESM::Region *region = world->getStore().get<ESM::Region>().search (regionstr);

                if (region != 0)
                {
                    weatherType = nextWeather(region);
                }
            }

            setWeather(weatherType, instantly);
        }
    }
}
