/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2016 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 OSGEARTHUTIL_GEODETIC_GRATICULE_H
#define OSGEARTHUTIL_GEODETIC_GRATICULE_H

#include <osgEarthUtil/Common>
#include <osgEarthUtil/LatLongFormatter>
#include <osgEarth/VisibleLayer>
#include <osgEarth/TerrainEffect>
#include <osgEarth/MapNode>
#include <osgEarthSymbology/Style>
#include <osgEarthAnnotation/LabelNode>
#include <osg/ClipPlane>

namespace osgEarth { namespace Util
{
    using namespace osgEarth;
    using namespace osgEarth::Annotation;
    using namespace osgEarth::Symbology;


    class GeodeticGraticuleOptions : public VisibleLayerOptions
    {
    public:
        //! Grid line color
        optional<Color>& color() { return _color; }
        const optional<Color>& color() const { return _color; }

        //! Label color
        optional<Color>& labelColor() { return _labelColor; }
        const optional<Color>& labelColor() const { return _labelColor; }

        //! Grid line width in pixels
        optional<float>& lineWidth() { return _lineWidth; }
        const optional<float>& lineWidth() const { return _lineWidth; }

        //! A target number of grid lines to view on screen at once.
        optional<int>& gridLines() { return _gridLines; }
        const optional<int>& gridLines() const { return _gridLines; }

        /** Resolutions for the graticule separated by spaces
         *  Resolutions are in degrees listed from lowest to highest resolution
         *  For example:  10 5 2.5 1.25 */
        optional<std::string>& resolutions() { return _resolutions; }
        const optional<std::string>& resolutions() const { return _resolutions; }

    public:
        GeodeticGraticuleOptions(const ConfigOptions& opt =ConfigOptions()) : VisibleLayerOptions( opt )
        {
            _lineWidth.init(2.0f);
            _color.init(Color(Color::Yellow, 0.5f));
            _labelColor.init(Color::White);
            _gridLines.init(10);
            fromConfig( _conf );
        }

    public:
        virtual Config getConfig() const {
            Config conf = VisibleLayerOptions::getConfig();
            conf.key() = "geodetic_graticule";
            conf.addIfSet("line_width", _lineWidth);
            conf.addIfSet("color",      _color);
            conf.addIfSet("label_color", _labelColor );
            conf.addIfSet("grid_lines", _gridLines);
            conf.addIfSet("resolutions", _resolutions);
            return conf;
        }

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

        void fromConfig( const Config& conf ) {
            conf.getIfSet("line_width", _lineWidth);
            conf.getIfSet("color",      _color);
            conf.getIfSet("label_color", _labelColor);
            conf.getIfSet("grid_lines", _gridLines);
            conf.getIfSet("resolutions", _resolutions);
        }

        optional<float>       _lineWidth;
        optional<Color>       _color;
        optional<Color>       _labelColor;
        optional<int>         _gridLines;
        optional<std::string> _resolutions;
    };


    /**
     * Graticule that shows lat/long lines and automatically places labels.
     */
    class OSGEARTHUTIL_EXPORT GeodeticGraticule : public VisibleLayer
    {
    public:
        META_Layer(osgEarthUtil, GeodeticGraticule, GeodeticGraticuleOptions);

        //! Construct a graticule with default settings.
        GeodeticGraticule();

        //! Construct a graticule with custom settings.
        GeodeticGraticule(const GeodeticGraticuleOptions& options);

        //! Rebuild the graticule after changing options.
        void dirty();

    public: // Layer

        virtual void addedToMap(const Map* map);

        virtual void removedFromMap(const Map* map);
        
        virtual osg::Node* getOrCreateNode();

        virtual void init();

    public: // VisibleLayer

        virtual void setVisible(bool value);

    protected:

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

    private:

        void setUpDefaultStyles();

        void rebuild();

        UID _uid;

        osg::ref_ptr<const Profile> _profile;

        osg::ref_ptr<osg::ClipPlane> _clipPlane;

        osg::ref_ptr<osg::Group> _root;

        osg::observer_ptr<const Map> _map;
        osg::ref_ptr<TerrainEffect> _effect;
        osg::ref_ptr<osg::NodeCallback > _callback;
        osg::ref_ptr<LatLongFormatter> _formatter;
        float _defaultResolution;
        
        osg::Vec2f _centerOffset;

        bool _visible;
        

        std::vector< double > _resolutions;


        struct CameraData
        {
            osg::ref_ptr<osg::StateSet> _stateset;
            osg::ref_ptr<osg::Uniform> _resolutionUniform;
            osg::ref_ptr<osg::StateSet> _labelStateset;
            std::vector< osg::ref_ptr<LabelNode> > _labelPool;
            float _resolution;
            osg::Matrixd _lastViewMatrix;     
            GeoExtent _viewExtent;
            double _lon;
            double _lat;
            double _metersPerPixel;
        };
        typedef std::map<osg::Camera*, CameraData> CameraDataMap;
        mutable CameraDataMap _cameraDataMap;
        mutable Threading::Mutex _cameraDataMapMutex;
        
        CameraData& getCameraData(osg::Camera*) const;

        double getResolution() const;
        void setResolution(double resolution);

        void initLabelPool(CameraData&);

        std::string getText(const GeoPoint& location, bool lat);

        osg::ref_ptr<MapNode> _mapNode;

        void installEffect();
        void removeEffect();

        GeoExtent getViewExtent(osgUtil::CullVisitor*) const;

    public:
        
        void updateLabels();
        void cull(osgUtil::CullVisitor*);
        osg::StateSet* getStateSet(osgUtil::CullVisitor* cv);
    };  
} } // namespace osgEarth::Util

#endif // OSGEARTHUTIL_GEODETIC_GRATICULE_H
