/* -*-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_ENGINE_MP_TILE_MODEL
#define OSGEARTH_ENGINE_MP_TILE_MODEL 1

#include "Common"
#include <osgEarth/Common>
#include <osgEarth/Map>
#include <osgEarth/ImageLayer>
#include <osgEarth/TileKey>
#include <osgTerrain/Locator>
#include <osgTerrain/Layer>
#include <osg/Image>
#include <osg/StateSet>
#include <map>

namespace osgEarth_engine_mp
{
    using namespace osgEarth;

    class TileModel : public osg::Referenced
    {
    public:
        // do not change the order of these.
        enum Neighbor
        {
            NORTHWEST = 0,
            NORTH     = 1,
            NORTHEAST = 2,
            WEST      = 3,
            EAST      = 4,
            SOUTHWEST = 5,
            SOUTH     = 6,
            SOUTHEAST = 7
        };

        class ElevationData
        {
        public:
            ElevationData() { }
            virtual ~ElevationData() { }
            ElevationData( osgTerrain::HeightFieldLayer* hfLayer, bool fallbackData =false )
                : _hfLayer(hfLayer), _fallbackData(fallbackData) { }

            osgTerrain::HeightFieldLayer* getHFLayer() const { return _hfLayer.get(); }
            bool isFallbackData() const { return _fallbackData; }
            
            osg::HeightField* getNeighbor(int xoffset, int yoffset) const
            {
                int index = getNeighborIndex(xoffset, yoffset);
                return _neighbors[index];
            }

            void setNeighbor(int xoffset, int yoffset, osg::HeightField* hf )
            {
                int index = getNeighborIndex(xoffset, yoffset);
                _neighbors[index] = hf;
            }

        private:
            static int getNeighborIndex(int xoffset, int yoffset)
            {
                int index = 3*(yoffset+1)+(xoffset+1);
                if (index > 4) index--;
                return index;
            }
            osg::ref_ptr<osgTerrain::HeightFieldLayer> _hfLayer;
            bool _fallbackData;
            osg::ref_ptr<osg::HeightField> _neighbors[8];
        };


        class ColorData
        {
        public:
            ColorData() { }

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

            ColorData(
                const osgEarth::ImageLayer* imageLayer,
                unsigned                    order,
                osg::Image*                 image,
                const osgTerrain::Locator*  locator,
                int                         lod,
                const osgEarth::TileKey&    tileKey,
                bool                        fallbackData =false )
                  : _layer(imageLayer), _order(order), _locator(locator), _image(image),  _tileKey(tileKey), _lod(lod), _fallbackData(fallbackData) { }

            ColorData( const ColorData& rhs ) :
                _layer( rhs._layer.get() ),
                _order( rhs._order ),
                _locator( rhs._locator.get() ),
                _image( rhs._image.get() ),
                _tileKey( rhs._tileKey ),
                _lod( rhs._lod ),
                _fallbackData( rhs._fallbackData ) { }

            osgEarth::UID getUID() const {
                return _layer->getUID();
            }

            unsigned getOrder() const {
                return _order;
            }

            const osgTerrain::Locator* getLocator() const {
                return _locator.get();
            }

            osg::Image* getImage() const { 
                return _image.get(); }

            const osgEarth::TileKey& getTileKey() const {
                return _tileKey; }

            const osgEarth::ImageLayer* getMapLayer() const {
                return _layer.get(); }

            int getLevelOfDetail() const {
                return _lod; }

            bool isFallbackData() const {
                return _fallbackData; }

            osg::BoundingSphere computeBound() const {
                osg::BoundingSphere bs;
                osg::Vec3d v;
                if (getLocator()->convertLocalToModel(osg::Vec3d(0.5,0.5,0.0), v)) {
                    bs.center() = v;
                }
                if (getLocator()->convertLocalToModel(osg::Vec3d(0.0,0.0,0.0), v)) {
                    bs.radius() = (bs.center() - v).length();
                }
                return bs;
            }


        private:
            osg::ref_ptr<const osgEarth::ImageLayer> _layer;
            osg::ref_ptr<const osgTerrain::Locator>  _locator;
            osg::ref_ptr<osg::Image>                 _image;
            osgEarth::TileKey                        _tileKey;
            int                                      _lod;
            bool                                     _fallbackData;
            unsigned                                 _order;
        };

        class ColorDataRef : public osg::Referenced
        {
        public:
            ColorDataRef( const ColorData& layer ) : _layer(layer) { }
            ColorData _layer;
        };


        typedef std::map<UID, ColorData> ColorDataByUID;


    public:
        TileModel() { }
        virtual ~TileModel() { }

        osg::ref_ptr<const Map>     _map; // observer_ptr?
        TileKey                     _tileKey;
        osg::ref_ptr<GeoLocator>    _tileLocator;
        ColorDataByUID              _colorData;
        ElevationData               _elevationData;
        float                       _sampleRatio;
        osg::ref_ptr<osg::StateSet> _parentStateSet;

        // convenience funciton to pull out a layer by its UID.
        bool getColorData( UID layerUID, ColorData& out ) const {
            ColorDataByUID::const_iterator i = _colorData.find( layerUID );
            if ( i != _colorData.end() ) {
                out = i->second;
                return true;
            }
            return false;
        }
    };

} // namespace osgEarth_engine_mp

#endif // OSGEARTH_ENGINE_MP_TILE_MODEL
