/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2008-2013 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
#ifndef OSGEARTH_DRIVER_OCEAN_SURFACE
#define OSGEARTH_DRIVER_OCEAN_SURFACE 1

#include <osgEarth/MapNode>
#include <osgEarth/ImageLayer>
#include <osgEarth/URI>
#include <osgEarth/Registry>
#include <osgEarthSymbology/Color>
#include <osg/Node>
#include <osg/Image>
#include <osgDB/ReadFile>
#include <osgDB/Options>

namespace osgEarth { namespace Drivers
{
    using namespace osgEarth;
    using namespace osgEarth::Symbology;

    /**
     * Options for controlling the ocean surface node.
     */
    class /*header-only*/ OceanSurfaceOptions : public ConfigOptions
    {
    public:
        /** Nominal sea level in meters (relative to ellipsoid/geoid); default is zero. */
        optional<float>& seaLevel() { return _seaLevel; }
        const optional<float>& seaLevel() const { return _seaLevel; }

        /** Elevation offset (relative to seaLevel) at which ocean surface goes to min transparency.
         *  Not available when using a mask layer. */
        optional<float>& lowFeatherOffset() { return _lowFeatherOffset; }
        const optional<float>& lowFeatherOffset() const { return _lowFeatherOffset; }

        /** Elevation offset (relative to seaLevel) at which ocean surface goes to full transparency.
         *  Not available when using a mask layer. */
        optional<float>& highFeatherOffset() { return _highFeatherOffset; }
        const optional<float>& highFeatherOffset() const { return _highFeatherOffset; }

        /** Maximum LOD to subdivide ocean surface */
        optional<unsigned>& maxLOD() { return _maxLOD; }
        const optional<unsigned>& maxLOD() const { return _maxLOD; }

        /** Color of the ocean surface (before texturing) */
        optional<Color>& baseColor() { return _baseColor; }
        const optional<Color>& baseColor() const { return _baseColor; }

        /** Maximum visibile range of the ocean (at which is starts fading in) */
        optional<float>& maxRange() { return _maxRange; }
        const optional<float>& maxRange() const { return _maxRange; }

        /** Range over which ocean fades into view (starting at maxRange) */
        optional<float>& fadeRange() { return _fadeRange; }
        const optional<float>& fadeRange() const { return _fadeRange; }

        /** URI of the texture to blend and animate into the ocean surface. */
        optional<URI>& textureURI() { return _textureURI; }
        const optional<URI>& textureURI() const { return _textureURI; }

        /** Image layer configuration for an optional "ocean mask" layer.
         *  This is an image layer that encodes areas of land versus ocean in the
         *  alpha channel of the image. The mapping is: [0...1] => [land...ocean] */
        optional<ImageLayerOptions>& maskLayer() { return _maskLayerOptions; }
        const optional<ImageLayerOptions>& maskLayer() const { return _maskLayerOptions; }

    public:
        OceanSurfaceOptions( const Config& conf =Config() )
            : ConfigOptions     ( conf ),
              _seaLevel         ( 0.0f ),
              _lowFeatherOffset ( -100.0f ),
              _highFeatherOffset( -10.0f ),
              _maxRange         ( 1000000.0f ),
              _fadeRange        ( 225000.0f ),
              _maxLOD           ( 11 ),
              _baseColor        ( osg::Vec4(0.2, 0.3, 0.5, 0.8) )
        {
            mergeConfig( _conf );
        }

        /** dtor */
        virtual ~OceanSurfaceOptions() { }

    public:
        Config getConfig() const {
            Config conf = ConfigOptions::newConfig();
            conf.updateIfSet("sea_level",           _seaLevel );
            conf.updateIfSet("high_feather_offset", _highFeatherOffset );
            conf.updateIfSet("low_feather_offset",  _lowFeatherOffset );
            conf.updateIfSet("max_range",           _maxRange );
            conf.updateIfSet("fade_range",          _fadeRange );
            conf.updateIfSet("max_lod",             _maxLOD );
            conf.updateIfSet("base_color",          _baseColor );
            conf.updateIfSet("texture_url",         _textureURI );
            conf.updateObjIfSet("mask_layer",       _maskLayerOptions );
            return conf;
        }

    protected:
        void mergeConfig( const Config& conf ) {
            ConfigOptions::mergeConfig( conf );
            fromConfig( conf );
        }

    private:
        void fromConfig( const Config& conf ) {
            conf.getIfSet("sea_level",           _seaLevel );
            conf.getIfSet("high_feather_offset", _highFeatherOffset );
            conf.getIfSet("low_feather_offset",  _lowFeatherOffset );
            conf.getIfSet("max_range",           _maxRange );
            conf.getIfSet("fade_range",          _fadeRange );
            conf.getIfSet("max_lod",             _maxLOD );
            conf.getIfSet("base_color",          _baseColor );
            conf.getIfSet("texture_url",         _textureURI );
            conf.getObjIfSet("mask_layer",       _maskLayerOptions );
        }

    private:
        optional<float>             _seaLevel;
        optional<float>             _lowFeatherOffset;
        optional<float>             _highFeatherOffset;
        optional<float>             _maxRange;
        optional<float>             _fadeRange;
        optional<unsigned>          _maxLOD;
        optional<Color>             _baseColor;
        optional<URI>               _textureURI;
        optional<ImageLayerOptions> _maskLayerOptions;
    };


    /**
     * Node tha renders an ocean surface over a MapNode.
     */
    class /*header-only*/ OceanSurfaceNode : public osg::Group
    {
    public:
        /**
         * Constructs a new ocean surface node. Add this to your scene graph somewhere
         * alongside your MapNode.
         */
        OceanSurfaceNode( MapNode* mapNode, const OceanSurfaceOptions& initialOptions =OceanSurfaceOptions() )
            : _mapNode( mapNode ), _options( initialOptions )
        {
            osg::Node* node = load();
            if ( node )
                this->addChild( node );
        }

        /** dtor */
        virtual ~OceanSurfaceNode() { }

        /**
         * Options controlling the look and behavior of the ocean
         */         
        OceanSurfaceOptions& options() { return _options; }
        const OceanSurfaceOptions& options() const { return _options; }

        /**
         * Call this whenever you change options and want to apply the changes.
         */
        void dirty() { load(); }

    private:
        osg::observer_ptr<MapNode> _mapNode;
        OceanSurfaceOptions        _options;

        osg::Node* load()
        {
            osg::Node* result = 0L;
            osg::ref_ptr<MapNode> safeMapNode = _mapNode.get();
            if ( safeMapNode.valid() )
            {
                osg::ref_ptr<osgDB::Options> o = Registry::instance()->cloneOrCreateOptions();
                o->setPluginData( "mapNode", (void*)_mapNode.get() );
                o->setPluginData( "options", (void*)&_options );
                osgDB::ReaderWriter::ReadResult r = osgDB::readNodeFile( ".osgearth_ocean_surface", o.get() );
                result = r.takeNode();
            }
            return result;
        }
    };

} } // namespace osgEarth::Drivers

#endif // OSGEARTH_DRIVER_OCEAN_SURFACE
