/* -*-c++-*- VirtualPlanetBuilder - Copyright (C) 1998-2007 Robert Osfield
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * This library 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
 * OpenSceneGraph Public License for more details.
*/

#ifndef SOURCE_H
#define SOURCE_H 1

#include <vpb/SpatialProperties>
#include <vpb/GeospatialDataset>
#include <vpb/BuildLog>
#include <vpb/SourceData>

#include <osg/Shape>
#include <osgTerrain/Layer>

namespace vpb
{

// forward declare
class BuildOptions;

class VPB_EXPORT Source : public osg::Referenced, public SpatialProperties
{
public:

    enum Type
    {
        IMAGE = 1,
        HEIGHT_FIELD = 2,
        SHAPEFILE = 4,
        MODEL = 8
    };

    enum PatchStatus
    {
        UNASSIGNED,
        UNCHANGED,
        MODIFIED,
        ADDED,
        REMOVED
    };

    enum ParameterPolicy
    {
        PREFER_CONFIG_SETTINGS,
        PREFER_CONFIG_SETTINGS_BUT_SCALE_BY_FILE_RESOLUTION,
        PREFER_FILE_SETTINGS
    };

    Source():
        _type(IMAGE),
        _patchStatus(UNASSIGNED),
        _revisionNumber(0),
        _sortValue(0.0),
        _temporaryFile(false),
        _coordinateSystemPolicy(PREFER_FILE_SETTINGS),
        _geoTransformPolicy(PREFER_FILE_SETTINGS),
        _minLevel(0),
        _maxLevel(MAXIMUM_NUMBER_OF_LEVELS),
        _layer(0),
        _gdalDataset(0),
        _hfDataset(0)
        {}

    Source(Type type, const std::string& filename):
        _type(type),
        _patchStatus(UNASSIGNED),
        _revisionNumber(0),
        _sortValue(0.0),
        _filename(filename),
        _temporaryFile(false),
        _coordinateSystemPolicy(PREFER_FILE_SETTINGS),
        _geoTransformPolicy(PREFER_FILE_SETTINGS),
        _minLevel(0),
        _maxLevel(MAXIMUM_NUMBER_OF_LEVELS),
        _layer(0),
        _gdalDataset(0),
        _hfDataset(0)
        {}

    Source(Type type, osg::Node* model);

    void setSortValue(double s) { _sortValue = s; }
    double getSortValue() const { return _sortValue; }

    void setSortValueFromSourceDataResolution(const osg::CoordinateSystemNode* cs);

    void setSortValueFromSourceDataResolution();

    void setType(Type type) { _type = type; }
    Type getType() const { return _type; }

    void setPatchStatus(PatchStatus ps) { _patchStatus = ps; }
    PatchStatus getPatchStatus() const { return _patchStatus; }

    void setRevisionNumber(unsigned int num) { _revisionNumber = num; }
    unsigned int getRevisionNumber() const { return _revisionNumber; }

    void setSetName(const std::string& setname, BuildOptions* bo);
    
    const std::string& getSetName() const { return _setname; }

    void setSwitchSetName(const std::string& setname) { _switchSetName = setname; }
    const std::string& getSwitchSetName() const { return _switchSetName; }

    void setFileName(const std::string& filename) { _filename = filename; }
    const std::string& getFileName() const { return _filename; }

    void setTemporaryFile(bool temporaryFile) { _temporaryFile = temporaryFile; }
    bool getTemporaryFile() const { return _temporaryFile; }

    GeospatialDataset* getOptimumGeospatialDataset(const SpatialProperties& sp, AccessMode accessMode) const;

    GeospatialDataset* getGeospatialDataset(AccessMode accessMode) const;

    void setGdalDataset(GDALDataset* gdalDataset);
    GDALDataset* getGdalDataset();
    const GDALDataset* getGdalDataset() const;

    void setHFDataset(osg::HeightField* hfDataset);
    osg::HeightField* getHFDataset();
    const osg::HeightField* getHFDataset() const;

    void setCoordinateSystemPolicy(ParameterPolicy policy) { _coordinateSystemPolicy = policy; }
    ParameterPolicy getCoordinateSystemPolicy() const { return _coordinateSystemPolicy; }

    void setCoordinateSystem(const std::string& wellKnownText) { _cs = new osg::CoordinateSystemNode("WKT",wellKnownText); }
    void setCoordinateSystem(osg::CoordinateSystemNode* cs) { _cs = cs; }
    osg::CoordinateSystemNode* getCoordinateSystem() { return  _cs.get(); }


    void setGeoTransformPolicy(ParameterPolicy policy)  { _geoTransformPolicy = policy; }
    ParameterPolicy getGeoTransformPolicy() const { return _geoTransformPolicy; }

    void setGeoTransform(const osg::Matrixd& transform) { _geoTransform = transform; }
    osg::Matrixd& getGeoTransform() { return _geoTransform; }

    void setGeoTransformFromRange(double xMin, double xMax, double yMin, double yMax)
    {
        _geoTransform.makeIdentity();
        _geoTransform(0,0) = xMax-xMin;
        _geoTransform(3,0) = xMin;

        _geoTransform(1,1) = yMax-yMin;
        _geoTransform(3,1) = yMin;
     }


    void assignCoordinateSystemAndGeoTransformAccordingToParameterPolicy();


    void setMinLevel(unsigned int minLevel) { _minLevel = minLevel; }
    void setMaxLevel(unsigned int maxLevel) { _maxLevel = maxLevel; }
    void setMinMaxLevel(unsigned int minLevel, unsigned int maxLevel) { _minLevel = minLevel; _maxLevel = maxLevel; }

    unsigned int getMinLevel() const { return _minLevel; }
    unsigned int getMaxLevel() const { return _maxLevel; }

    void setLayer(unsigned int layer) { _layer = layer; }
    unsigned int getLayer() const { return _layer; }


    void setSourceData(SourceData* data) { _sourceData = data; if (_sourceData.valid()) _sourceData->_source = this; }
    SourceData* getSourceData() { return _sourceData.get(); }

    bool intersects(const SpatialProperties& sp) const
    {
        return  _sourceData.valid()?_sourceData->intersects(sp):false;
    }

    void loadSourceData();


    bool needReproject(const osg::CoordinateSystemNode* cs) const;

    bool needReproject(const osg::CoordinateSystemNode* cs, double minResolution, double maxResolution) const;


    bool isRaster() const { return (_type==IMAGE || _type==HEIGHT_FIELD); }

    bool is3DObject() const { return (_type==SHAPEFILE || _type==MODEL); }

    /** Do reprojection of source image/DEM's. */
    Source* doRasterReprojection(const std::string& filename, osg::CoordinateSystemNode* cs, double targetResolution=0.0) const;
    
    /** Do reprojection by selecting one from the cache that is already in the appropriate projection. */
    Source* doRasterReprojectionUsingFileCache(osg::CoordinateSystemNode* cs);
    
    /** Do reprojection by 3D Object in-situ -- i.e change this Source directly. */
    bool do3DObjectReprojection(osg::CoordinateSystemNode* cs);

    void buildOverviews();


    struct ResolutionPair
    {
        ResolutionPair():
            _resX(0.0),_resY(0.0) {}

        ResolutionPair(double x,double y):
            _resX(x),_resY(y) {}

        bool operator < (const ResolutionPair& rhs) const
        {
            double minLHS = osg::minimum(_resX,_resY);
            double minRHS = osg::minimum(rhs._resX,rhs._resY);
            return minLHS<minRHS;
        }

        double _resX;
        double _resY;
    };

    typedef std::vector<ResolutionPair> ResolutionList;

    void addRequiredResolution(double resX, double resY) { _requiredResolutions.push_back(ResolutionPair(resX,resY)); }

    void setRequiredResolutions(ResolutionList& resolutions) { _requiredResolutions = resolutions; }

    ResolutionList& getRequiredResolutions() { return _requiredResolutions; }

    const ResolutionList& getRequiredResolutions() const { return _requiredResolutions; }

    void consolodateRequiredResolutions();

protected:


    Type                                        _type;
    PatchStatus                                 _patchStatus;
    unsigned int                                _revisionNumber;

    double                                      _sortValue;

    std::string                                 _setname;
    std::string                                 _switchSetName;
    std::string                                 _filename;
    bool                                        _temporaryFile;

    ParameterPolicy                             _coordinateSystemPolicy;
    ParameterPolicy                             _geoTransformPolicy;

    unsigned int                                _minLevel;
    unsigned int                                _maxLevel;
    unsigned int                                _layer;

    osg::ref_ptr<SourceData>                    _sourceData;

    ResolutionList                              _requiredResolutions;

    GDALDataset*                                _gdalDataset;
    osg::ref_ptr<osg::HeightField>              _hfDataset;
};

enum CompositeType
{
    GROUP,
    LOD,
    PAGED_LOD
};

class VPB_EXPORT CompositeSource : public osg::Referenced, public SpatialProperties
{
public:

    CompositeSource(CompositeType type=GROUP):_type(type) {};

    typedef std::vector< osg::ref_ptr<Source> > SourceList;
    typedef std::vector< osg::ref_ptr< CompositeSource> > ChildList;

    void setType(CompositeType type) { _type = type; }
    CompositeType getType() const { return _type; }

    void setSortValueFromSourceDataResolution();

    void setSortValueFromSourceDataResolution(const osg::CoordinateSystemNode* cs);

    /** Sort by Source::getSortValue.**/
    void sortBySourceSortValue();

    /** Sort by Source::Type, filename, levels etc.*/
    void sortBySourceDetails();

    /** assign the Source::PatchStatus according to the versions.*/
    void assignSourcePatchStatus();

    /** count the number Source's that don't have UNCHANGED status.*/
    unsigned int getNumberAlteredSources();

    class iterator
    {
    public:

        enum IteratorMode
        {
            ACTIVE,
            ALL
        };


        iterator(CompositeSource* composite=0,IteratorMode mode=ALL):
            _iteratorMode(mode)
        {
            if (composite)
            {
                _positionStack.push_back(IteratorPosition(composite));
            }
        }

        iterator(const iterator& rhs):
            _positionStack(rhs._positionStack) {}

        iterator& operator = (const iterator& rhs)
        {
            if (&rhs==this) return *this;
            _positionStack = rhs._positionStack;
            return *this;
        }

        bool operator == (const iterator& rhs) const
        {
            return _positionStack == rhs._positionStack;
        }

        bool operator != (const iterator& rhs) const
        {
            return _positionStack != rhs._positionStack;
        }

        bool valid() const
        {
            return !_positionStack.empty() && _positionStack.back().valid();
        }

        CompositeSource& operator *()
        {
            return *(valid()?_positionStack.back().current():0);
        }

        CompositeSource* operator ->()
        {
            return valid()?_positionStack.back().current():0;
        }

        const CompositeSource& operator *() const
        {
            return *(valid()?_positionStack.back().current():0);
        }

        const CompositeSource* operator ->() const
        {
            return valid()?_positionStack.back().current():0;
        }

        iterator& operator++()
        {
            advance();
            return *this;
        }

        iterator operator++(int)
        {
            iterator tmp=*this;
            advance();
            return tmp;
        }

        bool advance()
        {
            if (_positionStack.empty()) return false;

            // simple advance to the next source
            if (_positionStack.back().advance())
            {
                if (_positionStack.back().current())
                {
                    _positionStack.push_back(IteratorPosition(_positionStack.back().current()));
                    return advance();
                }
            }

            _positionStack.pop_back();
            return advance();
        }

    protected:

        struct IteratorPosition
        {

            IteratorPosition(CompositeSource* composite):
                _composite(composite),
                _index(-1) {}

            IteratorPosition(const IteratorPosition& rhs):
                _composite(rhs._composite),
                _index(rhs._index) {}

            IteratorPosition& operator = (const IteratorPosition& rhs)
            {
                _composite = rhs._composite;
                _index = rhs._index;
                return *this;
            }

            bool operator == (const IteratorPosition& rhs) const
            {
                return _composite == rhs._composite && _index == rhs._index;
            }

            bool operator != (const IteratorPosition& rhs) const
            {
                return _composite != rhs._composite || _index != rhs._index;
            }

            CompositeSource* current()
            {
                if (_index==-1) return _composite;
                else return  (_index>=0 && _index < (int)_composite->_children.size())?_composite->_children[_index].get():0;
            }

            const CompositeSource* current() const
            {
                if (_index==-1) return _composite;
                else return  (_index>=0 && _index < (int)_composite->_children.size())?_composite->_children[_index].get():0;
            }

            bool valid() const
            {
                return _composite && 
                       _index < (int)_composite->_children.size();
            }

            inline bool advance()
            {
                return advanceToNextChild(*_composite,_index);
            }

            inline bool isActive(const CompositeSource& /*composite*/,int /*index*/)
            {
                return true;
            }

            inline bool advanceToNextChild(CompositeSource& composite, int& index)
            {
                ++index;
                while (index<(int)composite._children.size())
                {
                    if (isActive(composite,index)) return true;
                    ++index;
                }
                return false;
            }

            CompositeSource*                _composite;
            int                             _index;
        };

        typedef std::vector<IteratorPosition> PositionStack;
        IteratorMode                        _iteratorMode;
        PositionStack                       _positionStack;
    };


    template<class T>
    class base_source_iterator
    {
    public:


        base_source_iterator(CompositeSource* composite=0, T advancer=T()):
            _advancer(advancer),
            _compositeIterator(composite),
            _sourceIndex(-1)
        {
            advance();
        }

        base_source_iterator(const base_source_iterator& rhs):
            _advancer(rhs._advancer),
            _compositeIterator(rhs._compositeIterator),
            _sourceIndex(rhs._sourceIndex) {}

        base_source_iterator& operator = (const base_source_iterator& rhs)
        {
            if (&rhs==this) return *this;
            _advancer = rhs._advancer;
            _compositeIterator = rhs._compositeIterator;
            _sourceIndex = rhs._sourceIndex;
        }

        bool operator == (const base_source_iterator& rhs) const
        {
            return _compositeIterator == rhs._compositeIterator &&
                   _sourceIndex == rhs._sourceIndex;
        }

        bool operator != (const base_source_iterator& rhs) const
        {
            return _compositeIterator != rhs._compositeIterator ||
                   _sourceIndex != rhs._sourceIndex;
        }

        bool valid() const
        {
            return _compositeIterator.valid() && _sourceIndex < (int)_compositeIterator->_sourceList.size();
        }

        osg::ref_ptr<Source>& operator *()
        {
            return valid()?_compositeIterator->_sourceList[_sourceIndex]:_nullSource;
        }

        osg::ref_ptr<Source>* operator ->()
        {
            return &(valid()?_compositeIterator->_sourceList[_sourceIndex]:_nullSource);
        }

        base_source_iterator& operator++()
        {
            advance();
            return *this;
        }

        base_source_iterator operator++(int)
        {
            base_source_iterator tmp=*this;
            advance();
            return tmp;
        }

        bool advance()
        {
            if (!_compositeIterator.valid()) return false;

            if (_advancer.advanceToNextSource(*_compositeIterator,_sourceIndex)) return true;

            // at end of current CompositeSource, so need to advance to new one.
            _sourceIndex = -1;
            ++_compositeIterator;
            return advance();
        }

    protected:

        T                       _advancer;
        iterator                _compositeIterator;
        int                     _sourceIndex;
        osg::ref_ptr<Source>    _nullSource;

    };

    struct DefaultSourceAdvancer
    {
        DefaultSourceAdvancer() {}

        bool isActive(const CompositeSource& /*composite*/,int /*index*/)
        {
            return true;
        }

        inline bool advanceToNextSource(const CompositeSource& composite, int& index)
        {
            return ++index<(int)composite._sourceList.size();
        }
    };

    struct LODSourceAdvancer
    {
        LODSourceAdvancer(float targetResolution=0.0f):
            _targetResolution(targetResolution) {}

        inline bool advanceToNextSource(const CompositeSource& composite, int& index)
        {
            if (composite.getType()==GROUP)
            {
                return (++index<(int)composite._sourceList.size());
            }
            else
            {
                if (composite._sourceList.empty()) return false;
                if (index!=-1) return false; // we've already traversed this composite, only ever one valid LOD.

                // find source with resolution closest to target
                int foundIndex = 0;
                float closestResolution = fabsf(composite._sourceList[0]->getSortValue()-_targetResolution);
                for(int i=1;i<(int)composite._sourceList.size();++i)
                {
                    float delta = fabsf(composite._sourceList[i]->getSortValue()-_targetResolution);
                    if (delta<closestResolution)
                    {
                        foundIndex = i;
                        closestResolution = delta;
                    }
                }
                if (foundIndex==index) return false;
                index = foundIndex;
                return true;
            }
        }

        float _targetResolution;
    };

    typedef base_source_iterator<DefaultSourceAdvancer> source_iterator;
    typedef base_source_iterator<LODSourceAdvancer>     source_lod_iterator;

    CompositeType   _type;
    SourceList      _sourceList;
    ChildList       _children;
};

}

#endif
