// a layer of 3d clouds
//
// Written by Harald JOHNSEN, started April 2005.
//
// Copyright (C) 2005  Harald JOHNSEN - hjohnsen@evc.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
//

#ifdef HAVE_CONFIG_H
#  include <simgear_config.h>
#endif

#include <osg/Fog>
#include <osg/Texture2D>
#include <osg/PositionAttitudeTransform>
#include <osg/Vec4f>

#include <simgear/compiler.h>

#include <simgear/math/sg_random.h>
#include <simgear/math/sg_geodesy.hxx>

#include <algorithm>
#include <vector>

using std::vector;

#include <simgear/environment/visual_enviro.hxx>
#include <simgear/scene/util/RenderConstants.hxx>
#include <simgear/scene/util/SGUpdateVisitor.hxx>
#include "sky.hxx"
#include "newcloud.hxx"
#include "cloudfield.hxx"

#if defined(__MINGW32__)
#define isnan(x) _isnan(x)
#endif

#if defined (__FreeBSD__)
#  if __FreeBSD_version < 500000
     extern "C" {
       inline int isnan(double r) { return !(r <= 0 || r >= 0); }
     }
#  endif
#endif

using namespace simgear;


float SGCloudField::fieldSize = 50000.0f;
double SGCloudField::timer_dt = 0.0;
float SGCloudField::view_distance = 20000.0f;
SGVec3f SGCloudField::view_vec, SGCloudField::view_X, SGCloudField::view_Y;

// reposition the cloud layer at the specified origin and orientation
bool SGCloudField::reposition( const SGVec3f& p, const SGVec3f& up, double lon, double lat,
        		       double dt, int asl )
{
    osg::Matrix T, LON, LAT;
    
    // Always update the altitude transform, as this allows
    // the clouds to rise and fall smoothly depending on environment updates.
    altitude_transform->setPosition(osg::Vec3d(0.0, 0.0, (double) asl));
    
    // Calculating the reposition information is expensive. 
    // Only perform the reposition every 60 frames.
    reposition_count = (reposition_count + 1) % 60;
    if ((reposition_count != 0) || !defined3D) return false;
    
    SGGeoc pos = SGGeoc::fromGeod(SGGeod::fromRad(lon, lat));
    
    double dist = SGGeodesy::distanceM(cld_pos, pos);
    
    if (dist > (fieldSize * 2)) {
        // First time or very large distance
        SGVec3<double> cart;
        SGGeodesy::SGGeodToCart(SGGeod::fromRad(lon, lat), cart);
        T.makeTranslate(toOsg(cart));
        
        LON.makeRotate(lon, osg::Vec3(0, 0, 1));
        LAT.makeRotate(90.0 * SGD_DEGREES_TO_RADIANS - lat, osg::Vec3(0, 1, 0));

        field_transform->setMatrix( LAT*LON*T );
        cld_pos = SGGeoc::fromGeod(SGGeod::fromRad(lon, lat));
    } else if (dist > fieldSize) {
        // Distance requires repositioning of cloud field.
        // We can easily work out the direction to reposition
        // from the course between the cloud position and the
        // camera position.
        SGGeoc pos = SGGeoc::fromGeod(SGGeod::fromRad(lon, lat));
        
        float crs = SGGeoc::courseDeg(cld_pos, pos);
        if ((crs < 45.0) || (crs > 315.0)) {
            SGGeodesy::advanceRadM(cld_pos, 0.0, fieldSize, cld_pos);
        }
        
        if ((crs > 45.0) && (crs < 135.0)) {
            SGGeodesy::advanceRadM(cld_pos, SGD_PI_2, fieldSize, cld_pos);
        }

        if ((crs > 135.0) && (crs < 225.0)) {
            SGGeodesy::advanceRadM(cld_pos, SGD_PI, fieldSize, cld_pos);
        }
        
        if ((crs > 225.0) && (crs < 315.0)) {
            SGGeodesy::advanceRadM(cld_pos, SGD_PI + SGD_PI_2, fieldSize, cld_pos);
        }
        
        SGVec3<double> cart;
        SGGeodesy::SGGeodToCart(SGGeod::fromRad(cld_pos.getLongitudeRad(), cld_pos.getLatitudeRad()), cart);
        T.makeTranslate(toOsg(cart));
        
        LON.makeRotate(cld_pos.getLongitudeRad(), osg::Vec3(0, 0, 1));
        LAT.makeRotate(90.0 * SGD_DEGREES_TO_RADIANS - cld_pos.getLatitudeRad(), osg::Vec3(0, 1, 0));

        field_transform->setMatrix( LAT*LON*T );
    }
    
    // Render the clouds in order from farthest away layer to nearest one.
    field_root->getStateSet()->setRenderBinDetails(CLOUDS_BIN, "DepthSortedBin");

    return true;
}

SGCloudField::SGCloudField() :
        field_root(new osg::Group),
        field_transform(new osg::MatrixTransform),
        altitude_transform(new osg::PositionAttitudeTransform),
	deltax(0.0),
	deltay(0.0),
	last_course(0.0),
	last_coverage(0.0),
        coverage(0.0),
        reposition_count(0),
        defined3D(false)
{
    cld_pos = SGGeoc();
    field_root->addChild(field_transform.get());
    field_root->setName("3D Cloud field root");
    osg::StateSet *rootSet = field_root->getOrCreateStateSet();
    rootSet->setRenderBinDetails(CLOUDS_BIN, "DepthSortedBin");
    rootSet->setAttributeAndModes(getFog());
    
    osg::ref_ptr<osg::Group> quad_root = new osg::Group();
    
    for (int i = 0; i < BRANCH_SIZE; i++) {
        for (int j = 0; j < BRANCH_SIZE; j++) {
            quad[i][j] = new osg::LOD();
            quad[i][j]->setName("Quad");
            quad_root->addChild(quad[i][j].get());
        }
    }
    
    int leafs = QUADTREE_SIZE / BRANCH_SIZE;

    for (int x = 0; x < QUADTREE_SIZE; x++) {
        for (int y = 0; y < QUADTREE_SIZE; y++) {
            field_group[x][y]= new osg::Switch;
            field_group[x][y]->setName("3D cloud group");
            
            // Work out where to put this node in the quad tree
            int i = x / leafs;
            int j = y / leafs;
            quad[i][j]->addChild(field_group[x][y].get(), 0.0f, view_distance);
        }
    }
    
    field_transform->addChild(altitude_transform.get());
            
    // We duplicate the defined field group in a 3x3 array. This way,
    // we can simply shift entire groups around.
    // TODO: "Bend" the edge groups so when shifted they line up.
    // Currently the clouds "jump down" when we reposition them.
    for(int x = -1 ; x <= 1 ; x++) {
        for(int y = -1 ; y <= 1 ; y++ ) {
            osg::ref_ptr<osg::PositionAttitudeTransform> transform =
                    new osg::PositionAttitudeTransform;
            transform->addChild(quad_root.get());
            transform->setPosition(osg::Vec3(x*fieldSize, y * fieldSize, 0.0));
            
            altitude_transform->addChild(transform.get());
        }
    }
}

SGCloudField::~SGCloudField() {
}


void SGCloudField::clear(void) {
    for (int x = 0; x < QUADTREE_SIZE; x++) {
        for (int y = 0; y < QUADTREE_SIZE; y++) {
            int num_children = field_group[x][y]->getNumChildren();
            field_group[x][y]->removeChildren(0, num_children);
        }
    }
    
    SGCloudField::defined3D = false;
}

// use a table or else we see poping when moving the slider...
static int densTable[][10] = {
	{0,0,0,0,0,0,0,0,0,0},
	{1,0,0,0,0,0,0,0,0,0},
	{1,0,0,0,1,0,0,0,0,0},
	{1,0,0,0,1,0,0,1,0,0}, // 30%
	{1,0,1,0,1,0,0,1,0,0},
	{1,0,1,0,1,0,1,1,0,0}, // 50%
	{1,0,1,0,1,0,1,1,0,1},
	{1,0,1,1,1,0,1,1,0,1}, // 70%
	{1,1,1,1,1,0,1,1,0,1},
	{1,1,1,1,1,0,1,1,1,1}, // 90%
	{1,1,1,1,1,1,1,1,1,1}
};

void SGCloudField::applyCoverage(void) {

        int row = (int) (coverage * 10.0);
        if (row > 9) row = 9;
        int col = 0;

        if (coverage != last_coverage) {
            for (int x = 0; x < QUADTREE_SIZE; x++) {
                for (int y = 0; y < QUADTREE_SIZE; y++) {
                // Switch on/off the children depending on the required coverage.
                    int num_children = field_group[x][y]->getNumChildren();
                    for (int i = 0; i < num_children; i++) {
                        if (++col > 9) col = 0;
                        if ( densTable[row][col] ) {
                            field_group[x][y]->setValue(i, true);
                        } else {
                            field_group[x][y]->setValue(i, false);
                        }
                    }
                }
            }
        }

        last_coverage = coverage;
}

void SGCloudField::addCloud( SGVec3f& pos, SGNewCloud *cloud) {
        defined3D = true;
        osg::ref_ptr<osg::Geode> geode = cloud->genCloud();
        
        // Determine which quadtree to put it in.
        int x = (int) floor((pos.x() + fieldSize/2.0) * QUADTREE_SIZE / fieldSize);
        if (x >= QUADTREE_SIZE) x = (QUADTREE_SIZE - 1);
        if (x < 0) x = 0;
        
        int y = (int) floor((pos.y() + fieldSize/2.0) * QUADTREE_SIZE / fieldSize);
        if (y >= QUADTREE_SIZE) y = (QUADTREE_SIZE - 1);
        if (y < 0) y = 0;
        
        osg::ref_ptr<osg::PositionAttitudeTransform> transform = new osg::PositionAttitudeTransform;

        transform->setPosition(toOsg(pos));
        transform->addChild(geode.get());
        
        field_group[x][y]->addChild(transform.get(), true);
}

void SGCloudField::applyVisRange(void) { 
    
    for (int x = 0; x < BRANCH_SIZE; x++) {
        for (int y = 0; y < BRANCH_SIZE; y++) {
            int num_children = quad[x][y]->getNumChildren(); 
            for (int i = 0; i < num_children; i++) { 
                quad[x][y]->setRange(i, 0.0f, view_distance);
            }
        }
    }
}

SGCloudField::CloudFog::CloudFog()
{
    fog = new osg::Fog;
    fog->setMode(osg::Fog::EXP2);
    fog->setDataVariance(osg::Object::DYNAMIC);
}

void SGCloudField::updateFog(double visibility, const osg::Vec4f& color)
{
    const double sqrt_m_log01 = sqrt(-log(0.01));
    osg::Fog* fog = CloudFog::instance()->fog.get();
    fog->setColor(color);
    fog->setDensity(sqrt_m_log01 / visibility);
}
