/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2008-2012 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 */
        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 */
        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; }

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

        /** URI of the intensity texture to use for the ocean surface alpha. */
        optional<URI>& textureURI() { return _textureURI; }
        const optional<URI>& textureURI() const { return _textureURI; }

    public:
        OceanSurfaceOptions( const Config& conf =Config() )
            : ConfigOptions     ( conf ),
              _seaLevel         ( 0.0f ),
              _lowFeatherOffset ( -100.0f ),
              _highFeatherOffset( -10.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_lod",             _maxLOD );
            conf.updateIfSet("base_color",          _baseColor );
            conf.updateIfSet("texture_url",         _textureURI );
            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_lod",             _maxLOD );
            conf.getIfSet("base_color",          _baseColor );
            conf.getIfSet("texture_url",         _textureURI );
        }

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


    /**
     * 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(); //new osgDB::Options();
                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
