/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/

#include "stdafx.h"
#include "simulationenvironment.h"

#include "Globals.h"

namespace simulation {

world_environment Environment;

} // simulation

void
world_environment::toggle_daylight() {

    Global.FakeLight = !Global.FakeLight;

    if( Global.FakeLight ) {
        // for fake daylight enter fixed hour
        time( 10, 30, 0 );
    }
    else {
        // local clock based calculation
        time();
    }
}

// calculates current season of the year based on set simulation date
void
world_environment::compute_season( int const Yearday ) const {

    using dayseasonpair = std::pair<int, std::string>;

    std::vector<dayseasonpair> seasonsequence {
        {  65, "winter:" },
        { 158, "spring:" },
        { 252, "summer:" },
        { 341, "autumn:" },
        { 366, "winter:" } };
    auto const lookup =
        std::lower_bound(
            std::begin( seasonsequence ), std::end( seasonsequence ),
            clamp( Yearday, 1, seasonsequence.back().first ),
            []( dayseasonpair const &Left, const int Right ) {
                return Left.first < Right; } );
    
    Global.Season = lookup->second;
    // season can affect the weather so if it changes, re-calculate weather as well
    compute_weather();
}

// calculates current weather
void
world_environment::compute_weather() const {

    Global.Weather = (
        Global.Overcast <= 0.25 ? "clear:" :
        Global.Overcast <= 1.0 ? "cloudy:" :
        ( Global.Season != "winter:" ?
            "rain:" :
            "snow:" ) );
}

void
world_environment::init() {

    m_sun.init();
    m_moon.init();
    m_stars.init();
    m_clouds.Init();
    m_precipitation.init();

    m_precipitationsound.deserialize( "rain-sound-loop", sound_type::single );
}

void
world_environment::update() {
    // move celestial bodies...
    m_sun.update();
    m_moon.update();
    // ...determine source of key light and adjust global state accordingly...
    // diffuse (sun) intensity goes down after twilight, and reaches minimum 18 degrees below horizon
    float twilightfactor = clamp( -m_sun.getAngle(), 0.0f, 18.0f ) / 18.0f;
    // NOTE: sun light receives extra padding to prevent moon from kicking in too soon
    auto const sunlightlevel = m_sun.getIntensity() + 0.05f * ( 1.f - twilightfactor );
    auto const moonlightlevel = m_moon.getIntensity() * 0.65f; // scaled down by arbitrary factor, it's pretty bright otherwise

    // ...update skydome to match the current sun position as well...
    // twilight factor can be reset later down, so we do it here while it's still reflecting state of the sun
    // turbidity varies from 2-3 during the day based on overcast, 3-4 after sunset to deal with sunlight bleeding too much into the sky from below horizon
    m_skydome.SetTurbidity(
        2.f
        + clamp( Global.Overcast, 0.f, 1.f )
        + interpolate( 0.f, 1.f, clamp( twilightfactor * 1.5f, 0.f, 1.f ) ) );
    m_skydome.SetOvercastFactor( Global.Overcast );
    m_skydome.Update( m_sun.getDirection() );

    float keylightintensity;
    glm::vec3 keylightcolor;
    if( moonlightlevel > sunlightlevel ) {
        // rare situations when the moon is brighter than the sun, typically at night
        Global.SunAngle = m_moon.getAngle();
        Global.DayLight.position = m_moon.getDirection();
        Global.DayLight.direction = -1.0f * m_moon.getDirection();
        keylightintensity = moonlightlevel;
        // if the moon is up, it overrides the twilight
        twilightfactor = 0.0f;
        keylightcolor = glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 202.0f / 255.0f );
    }
    else {
        // regular situation with sun as the key light
        Global.SunAngle = m_sun.getAngle();
        Global.DayLight.position = m_sun.getDirection();
        Global.DayLight.direction = -1.0f * m_sun.getDirection();
        keylightintensity = sunlightlevel;
        // include 'golden hour' effect in twilight lighting
        float const duskfactor = 1.0f - clamp( Global.SunAngle, 0.0f, 18.0f ) / 18.0f;
        keylightcolor = interpolate(
            glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ),
            glm::vec3( 235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f ),
            duskfactor );
    }
    // ...retrieve current sky colour and brightness...
    auto const skydomecolour = m_skydome.GetAverageColor();
    auto const skydomehsv = colors::RGBtoHSV( skydomecolour );
    // sun strength is reduced by overcast level
    keylightintensity *= ( 1.0f - std::min( 1.f, Global.Overcast ) * 0.65f );

    // intensity combines intensity of the sun and the light reflected by the sky dome
    // it'd be more technically correct to have just the intensity of the sun here,
    // but whether it'd _look_ better is something to be tested
    auto const intensity = std::min( 1.15f * ( 0.05f + keylightintensity + skydomehsv.z ), 1.25f );
    // the impact of sun component is reduced proportionally to overcast level, as overcast increases role of ambient light
    auto const diffuselevel = interpolate( keylightintensity, intensity * ( 1.0f - twilightfactor ), 1.0f - std::min( 1.f, Global.Overcast ) * 0.75f );
    // ...update light colours and intensity.
    keylightcolor = keylightcolor * diffuselevel;
    Global.DayLight.diffuse = glm::vec4( keylightcolor, Global.DayLight.diffuse.a );
    Global.DayLight.specular = glm::vec4( keylightcolor * 0.85f, diffuselevel );

    // tonal impact of skydome color is inversely proportional to how high the sun is above the horizon
    // (this is pure conjecture, aimed more to 'look right' than be accurate)
    float const ambienttone = clamp( 1.0f - ( Global.SunAngle / 90.0f ), 0.0f, 1.0f );
    Global.DayLight.ambient[ 0 ] = interpolate( skydomehsv.z, skydomecolour.r, ambienttone );
    Global.DayLight.ambient[ 1 ] = interpolate( skydomehsv.z, skydomecolour.g, ambienttone );
    Global.DayLight.ambient[ 2 ] = interpolate( skydomehsv.z, skydomecolour.b, ambienttone );

    Global.fLuminance = intensity;

    // update the fog. setting it to match the average colour of the sky dome is cheap
    // but quite effective way to make the distant items blend with background better
    // NOTE: base brightness calculation provides scaled up value, so we bring it back to 'real' one here
    Global.FogColor = m_skydome.GetAverageHorizonColor();

    // weather-related simulation factors
    // TODO: dynamic change of air temperature and overcast levels
    if( Global.Weather == "rain:" ) {
        // reduce friction in rain
        Global.FrictionWeatherFactor = 0.85f;
        m_precipitationsound.play( sound_flags::exclusive | sound_flags::looping );
    }
    else if( Global.Weather == "snow:" ) {
        // reduce friction due to snow
        Global.FrictionWeatherFactor = 0.75f;
    }
}

void
world_environment::update_precipitation() {

    m_precipitation.update();
}

void
world_environment::time( int const Hour, int const Minute, int const Second ) {

    m_sun.setTime( Hour, Minute, Second );
}

//---------------------------------------------------------------------------
