/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
* Copyright 2008-2014 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_DRIVERS_MP_TERRAIN_ENGINE_TILE_MODEL_FACTORY
#define OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_MODEL_FACTORY 1

#include "Common"
#include "TileNode"
#include "TileNodeRegistry"
#include "MPTerrainEngineOptions"
#include <osgEarth/Map>
#include <osgEarth/Progress>
#include <osgEarth/ThreadingUtils>
#include <osgEarth/Containers>
#include <osgEarth/HeightFieldUtils>
#include <osgEarth/MapFrame>
#include <osgEarth/MapInfo>
#include <osg/Group>

namespace osgEarth { namespace Drivers { namespace MPTerrainEngine
{
    using namespace osgEarth;

    /** Key into the height field cache */
    struct HFKey {
        TileKey               _key;
        Revision              _revision;
        ElevationSamplePolicy _samplePolicy;

        bool operator < (const HFKey& rhs) const {
            if ( _key < rhs._key ) return true;
            if ( rhs._key < _key ) return false;
            if ( _revision < rhs._revision ) return true;
            if ( _revision > rhs._revision ) return false;
            return _samplePolicy < rhs._samplePolicy;
        }
    };

    /** value in the height field cache */
    struct HFValue {
        osg::ref_ptr<osg::HeightField> _hf;
        bool                           _isFallback;
    };        

    class HeightFieldCache : public osg::Referenced, public Revisioned
    {
    public:
        HeightFieldCache(TileNodeRegistry* tiles, const MPTerrainEngineOptions& options) :
          _tiles  ( tiles ),
          _cache  ( true, 128 )
        {
            _firstLOD = options.firstLOD().get();
        }

        bool getOrCreateHeightField( 
                const MapFrame&                 frame,
                const TileKey&                  key,
                bool                            cummulative,  // whether to start with the parent as a template
                osg::ref_ptr<osg::HeightField>& out_hf,
                bool&                           out_isFallback,
                ElevationSamplePolicy           samplePolicy,
                ElevationInterpolation          interp,
                ProgressCallback*               progress )
        {                
            // default
            out_isFallback = false;

            // check the quick cache.
            HFKey cachekey;
            cachekey._key          = key;
            cachekey._revision     = frame.getRevision();
            cachekey._samplePolicy = samplePolicy;

            if (progress)
                progress->stats()["hfcache_try_count"] += 1;

            bool hit = false;
            LRUCache<HFKey,HFValue>::Record rec;
            if ( _cache.get(cachekey, rec) )
            {
                out_hf = rec.value()._hf.get();
                out_isFallback = rec.value()._isFallback;

                if (progress)
                {
                    progress->stats()["hfcache_hit_count"] += 1;
                    progress->stats()["hfcache_hit_rate"] = progress->stats()["hfcache_hit_count"]/progress->stats()["hfcache_try_count"];
                }

                return true;
            }

            // Find the parent tile and start with its heightfield.
            if ( cummulative )
            {
                osg::ref_ptr<TileNode> parentNode;
                TileKey parentKey = key.createParentKey();
                if ( _tiles->get(parentKey, parentNode) )
                {
                    const TileModel* parentModel = parentNode->getTileModel();
                    if ( parentModel )
                    {
                        osg::HeightField* parentHF = parentModel->_elevationData.getHeightField();
                        if ( parentHF )
                        {
                            out_hf = HeightFieldUtils::createSubSample(
                                parentHF,
                                parentKey.getExtent(),
                                key.getExtent(),
                                interp );
                        }
                    }
                }

                if ( !out_hf.valid() && ((int)key.getLOD())-1 > _firstLOD )
                {
                    // This most likely means that a parent tile expired while we were building the child.
                    // No harm done in that case as this tile will soo be discarded as well.
                    OE_DEBUG << "MP HFC: Unable to find tile " << key.str() << " in the live tile registry"
                        << std::endl;
                    return false;
                }
            }

            bool populated = frame.populateHeightField(
                out_hf,
                key,
                true, // convertToHAE
                samplePolicy,
                progress );
     
            // Treat Plate Carre specially by scaling the height values. (There is no need
            // to do this with an empty heightfield)
            const MapInfo& mapInfo = frame.getMapInfo();
            if ( mapInfo.isPlateCarre() )
            {
                HeightFieldUtils::scaleHeightFieldToDegrees( out_hf.get() );
            }

            // cache it.
            HFValue cacheval;
            cacheval._hf = out_hf.get();
            cacheval._isFallback = !populated;
            _cache.insert( cachekey, cacheval );

            out_isFallback = !populated;
            return true;
        }

        void clear()
        {
            _cache.clear();
        }

    private:
        mutable LRUCache<HFKey,HFValue> _cache;
        TileNodeRegistry*               _tiles;
        int                             _firstLOD;
    };


    /**
     * For a given TileKey, this class builds a a corresponding TileNode from
     * the map's data model.
     *
     * TODO: This should probably change to TileModelFactory or something since
     * the creation of the TileNode itself is trivial and can be done outside of
     * this class.
     */
    class TileModelFactory : public osg::Referenced
    {
    public:
        TileModelFactory(
            TileNodeRegistry*             liveTiles,
            const MPTerrainEngineOptions& terrainOptions );

        HeightFieldCache* getHeightFieldCache() const;

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

        void createTileModel(
            const TileKey&           key,           // key for which to create model
            const MapFrame&          frame,         // map frame from which to get data
            bool                     accumulate,    // whether to accumulate values from parent tile(s)
            osg::ref_ptr<TileModel>& out_model,     // output or NULL upon failure
            ProgressCallback*        progress);     // progess tracking

    private:        

        osg::ref_ptr<TileNodeRegistry>   _liveTiles;
        const MPTerrainEngineOptions&    _terrainOptions;
        osg::ref_ptr< HeightFieldCache > _hfCache;
        
        void buildElevation(
            const TileKey&    key,
            const MapFrame&   frame,
            bool              accumulate,
            TileModel*        model,
            ProgressCallback* progress);
    };

} } } // namespace osgEarth::Drivers::MPTerrainEngine

#endif // OSGEARTH_DRIVERS_MP_TERRAIN_ENGINE_TILE_MODEL_FACTORY
