/**
 * @file precipitation_mgr.cxx
 * @author Nicolas VIVIEN
 * @date 2008-02-10
 *
 * @brief Precipitation manager
 *   This manager calculate the intensity of precipitation in function of the altitude,
 *   calculate the wind direction and velocity, then update the drawing of precipitation.
 */
// SPDX-FileCopyrightText: 2008 Nicolas Vivien
// SPDX-License-Identifier: GPL-2.0-or-later

#include <config.h>

#include <osg/MatrixTransform>

#include <simgear/constants.h>
#include <simgear/scene/sky/sky.hxx>
#include <simgear/scene/sky/cloud.hxx>
#include <simgear/scene/util/OsgMath.hxx>

#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Viewer/renderer.hxx>
#include <Scenery/scenery.hxx>

#include "precipitation_mgr.hxx"

/**
 * @brief FGPrecipitation Manager constructor
 *
 * Build a new object to manage the precipitation object
 */
FGPrecipitationMgr::FGPrecipitationMgr()
{
    // Try to set up the scenegraph.
    setupSceneGraph();
}

/**
 * @brief FGPrecipitaiton Manager destructor
 */
FGPrecipitationMgr::~FGPrecipitationMgr()
{

}

/**
 * SGSubsystem initialization
 */
void FGPrecipitationMgr::init()
{
    // Read latitude and longitude position
    SGGeod geod = SGGeod::fromDegM(fgGetDouble("/position/longitude-deg", 0.0),
                                   fgGetDouble("/position/latitude-deg", 0.0),
                                   0.0);
    osg::Matrix position(makeZUpFrame(geod));
    // Move the precipitation object to player position
    transform->setMatrix(position);
    fgGetNode("environment/params/precipitation-level-ft", true);
}

void FGPrecipitationMgr::bind ()
{
  _tiedProperties.setRoot( fgGetNode("/sim/rendering", true ) );
  _tiedProperties.Tie("precipitation-enable", precipitation.get(),
          &SGPrecipitation::getEnabled,
          &SGPrecipitation::setEnabled);
}

void FGPrecipitationMgr::unbind ()
{
  _tiedProperties.Untie();
}

// Set up the precipitation manager scenegraph.
void FGPrecipitationMgr::setupSceneGraph(void)
{
    FGScenery* scenery = globals->get_scenery();
    osg::Group* group = scenery->get_precipitation_branch();
    transform = new osg::MatrixTransform();
    precipitation = new SGPrecipitation();


    // By default, no precipitation
    precipitation->setRainIntensity(0);
    precipitation->setSnowIntensity(0);

    // set the clip distance from the config
    precipitation->setClipDistance(fgGetFloat("/environment/precipitation-control/clip-distance",5.0));
    transform->addChild(precipitation->build());
    group->addChild(transform.get());
}


void FGPrecipitationMgr::setPrecipitationLevel(double a)
{
    fgSetDouble("environment/params/precipitation-level-ft",a);
}

/**
 * @brief Calculate the max alitutude with precipitation
 *
 * @returns Elevation max in meter
 *
 * This function permits you to know what is the altitude max where we can
 * find precipitation. The value is returned in meters.
 */
float FGPrecipitationMgr::getPrecipitationAtAltitudeMax(void)
{
    int i;
    int max;
    float result;
    SGPropertyNode *boundaryNode, *boundaryEntry;

    if (fgGetBool("/environment/params/use-external-precipitation-level", false)) {
        // If we're not modeling the precipitation level based on the cloud
        // layers, take it directly from the property tree.
        return fgGetFloat("/environment/params/external-precipitation-level-m", 0.0);
    }


    // By default (not cloud layer)
    max = SGCloudLayer::SG_MAX_CLOUD_COVERAGES;
    result = 0;

     SGSky* thesky = globals->get_renderer()->getSky();

    // To avoid messing up
    if (thesky == NULL)
        return result;

    // For each cloud layer
    for (i=0; i<thesky->get_cloud_layer_count(); i++) {
        int q;

        // Get coverage
        // Value for q are (meaning / thickness) :
        //   5 : "clear"		/ 0
        //   4 : "cirrus"       / ??
        //   3 : "few" 			/ 65
        //   2 : "scattered"	/ 600
        //   1 : "broken"		/ 750
        //   0 : "overcast"		/ 1000
        q = thesky->get_cloud_layer(i)->getCoverage();

        // Save the coverage max
        if (q < max) {
            max = q;
            result = thesky->get_cloud_layer(i)->getElevation_m();
        }
    }


    // If we haven't found clouds layers, we read the boundary layers table.
    if (result > 0)
        return result;


    // Read boundary layers node
    boundaryNode = fgGetNode("/environment/config/boundary");

    if (boundaryNode != NULL) {
        i = 0;

        // For each boundary layers
        while ( ( boundaryEntry = boundaryNode->getNode( "entry", i ) ) != NULL ) {
            double elev = boundaryEntry->getDoubleValue( "elevation-ft" );

            if (elev > result)
                result = elev;

            ++i;
        }
    }

	// Convert the result in meter
	result = result * SG_FEET_TO_METER;

    return result;
}


/**
 * @brief Update the precipitation drawing
 *
 * To seem real, we stop the precipitation above the cloud or boundary layer.
 * If METAR information doesn't give us this altitude, we will see precipitations
 * in space...
 * Moreover, below 0°C we change rain into snow.
 */
void FGPrecipitationMgr::update(double dt)
{
    double dewtemp;
    double currtemp;
    double rain_intensity;
    double snow_intensity;

    float altitudeAircraft;
    float altitudeCloudLayer;
    float rainDropletSize;
    float snowFlakeSize;
    float illumination;

    altitudeCloudLayer = this->getPrecipitationAtAltitudeMax() * SG_METER_TO_FEET;
    setPrecipitationLevel(altitudeCloudLayer);



    // Does the user enable the precipitation ?
    if (!precipitation->getEnabled() ) {
        // Disable precipitations
        precipitation->setRainIntensity(0);
        precipitation->setSnowIntensity(0);

        // Update the drawing...
        precipitation->update();

        // Exit
        return;
    }

   // See if external droplet size and illumination are used
   if (fgGetBool("/environment/precipitation-control/detailed-precipitation", false)) {
	precipitation->setDropletExternal(true);
	rainDropletSize = fgGetFloat("/environment/precipitation-control/rain-droplet-size", 0.015);
	snowFlakeSize = fgGetFloat("/environment/precipitation-control/snow-flake-size", 0.03);
	illumination = fgGetFloat("/environment/precipitation-control/illumination", 1.0);
	precipitation->setRainDropletSize(rainDropletSize);
	precipitation->setSnowFlakeSize(snowFlakeSize);
	precipitation->setIllumination(illumination);
   }

   // Get the elevation of aircraft and of the cloud layer
   altitudeAircraft = fgGetDouble("/position/altitude-ft", 0.0);

   if ((altitudeCloudLayer > 0) && (altitudeAircraft > altitudeCloudLayer)) {
       // The aircraft is above the cloud layer
       rain_intensity = 0;
       snow_intensity = 0;
   } else {
       // The aircraft is below the cloud layer
       rain_intensity = fgGetDouble("/environment/rain-norm", 0.0);
       snow_intensity = fgGetDouble("/environment/snow-norm", 0.0);
   }

    // Get the current and dew temperature
    dewtemp = fgGetDouble("/environment/dewpoint-degc", 0.0);
    currtemp = fgGetDouble("/environment/temperature-degc", 0.0);

    if (currtemp < dewtemp) {
        // There is fog... and the weather is very steamy
        if (rain_intensity == 0)
            rain_intensity = 0.15;
    }

    // If the current temperature is below 0°C, we turn off the rain to snow...
    if (currtemp < 0)
        precipitation->setFreezing(true);
    else
        precipitation->setFreezing(false);


    // Set the wind property
    precipitation->setWindProperty(
        fgGetDouble("/environment/wind-from-heading-deg", 0.0),
        fgGetDouble("/environment/wind-speed-kt", 0.0));

    // Set the intensity of precipitation
    precipitation->setRainIntensity(rain_intensity);
    precipitation->setSnowIntensity(snow_intensity);

    // Update the drawing...
    precipitation->update();
}


// Register the subsystem.
SGSubsystemMgr::Registrant<FGPrecipitationMgr> registrantFGPrecipitationMgr(
    SGSubsystemMgr::GENERAL,
    {{"FGScenery", SGSubsystemMgr::Dependency::HARD},
     {"SGSky", SGSubsystemMgr::Dependency::NONSUBSYSTEM_HARD}});
