/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2010 Pelican Ventures, Inc.
 * 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 SEAMLESS_EULER
#define SEAMLESS_EULER 1

#include <osgEarth/Common>
#include <osgEarth/Profile>
#include <osgEarth/TileKey>
#include <osgEarth/Locators>

namespace seamless
{
/** The Euler cube
 *
 * The faces of a cube are projected onto the spherical globe. Within
 * a cube face, the coordinates are specified as as angles from the
 * fixed axes parallel to the cube face. For example, the x coordinate
 * of equatorial faces is equivalent to longitude. The y coordinate is
 * not latitude, because it is not measured in the longitudinal
 * plane. The major effect of this is that the grid lines on the cube
 * faces are great circles.
 *
 * The cube is layed out like a flattened cut-out. the x,y coordinates
 * range from (0,0) to (4, 3). In the equatorial region, x is 0-4 and
 * y is 1-2. North Pole is (0, 2) ->(1,3); South Pole is (0,0)->(1,1).
 *
 * Face 0 is centered on lat lon (0, 0). Faces 1-3 are the equatorial
 * faces to the East. 4 is the North Pole, 5 is the South.
 *
 * Within each face, the conversion formulas assume that coordinate
 * values range from -1 to 1.
 */

namespace euler
{
extern bool latLonToFaceCoords(
    double lat_deg, double lon_deg, double& out_x, double& out_y,
    int& out_face, int faceHint = -1);

/**
 * Converts face coordinates into lat/long.
 */
extern bool faceCoordsToLatLon(
    double x, double y, int face, double& out_lat_deg, double& out_lon_deg);

/**
 * Converts cube coordinates (0,0=>4,3) to face coordinates (-1,-1=>1,1,F).
 * WARNING. If the cube coordinate lies on a face boundary, this method will
 * always return the lower-numbered face. The "extent" version of this
 * method (below) is better b/c it's unambiguous.
 */
extern bool cubeToFace(
    double& in_out_x, double& in_out_y,int& out_face);

/**
 * Converts cube coordinates (0,0=>4,3) to face coordinates (-1,-1=>1,1,F). This
 * version takes an extent, which is better than the non-extent version since it
 * can resolve face-border ambiguity.
 */
extern bool cubeToFace(
    double& in_out_xmin, double& in_out_ymin,
    double& in_out_xmax, double& in_out_ymax,
    int& out_face);

/**
 * Converts face coordinates (-1,-1=>1,1 +F) to cube coordinates (0,0=>4,3).
 */
extern bool faceToCube(
    double& in_out_x, double& in_out_y, int face);

/**
 * Calculate the direction cosine representation (unit vector from the
 * origin) of a face coordinate's geographic location.
 */
extern osg::Vec3d face2dc(int face, const osg::Vec2d& faceCoord);

/**
 * Length of great circle arc between face coordinates. This will be the
 * length of a grid edge if the points share a coordinate.
 */

extern double arcLength(
    const osg::Vec2d& coord1, const osg::Vec2d& coord2, int face);

/**
 * Distance between a point and an arc on a face.
 * @param p the point, for example the eyepoint.
 * @param coord1 beginning of segment in face coordinates
 * @param coord2 end of segment in face coordinates
 * @param face cube face. coord1 and coord2 lie on the same face.
 * @returns distance
 */
extern double distanceToSegment(
    const osg::Vec3d& p, const osg::Vec2d& coord1, const osg::Vec2d& coord2,
    int face);

/**
 * Distance between a point and an arc on the spherical earth.
 * @param p the point, for example the eyepoint.
 * @param coord1 beginning of the arc as a direction cosine.
 * @param coord2 end of the arc as a direction cosine.
 * @returns distance
 */
extern double distanceToSegment(
    const osg::Vec3d& p, const osg::Vec3d& coord1, const osg::Vec3d& coord2);
}

/**
 * osgTerrain locator for positioning data on the terrain using a cube-face
 * coordinate system.
 */
class EulerFaceLocator : public osgEarth::GeoLocator
{
public:
    EulerFaceLocator(unsigned int face)
        : _face(face)
    {
    }

    // This method will generate geocentric vertex coordinates, given local tile
    // coordinates (0=>1).
    bool convertLocalToModel(const osg::Vec3d& local, osg::Vec3d& world) const;

    // This method will generate the texture coordinates for a given location on
    // the globe.
    bool convertModelToLocal(const osg::Vec3d& world, osg::Vec3d& local) const;

private:
    unsigned int _face;
};

/**
 * The EULER SRS represents a 6-face cube, each face being in unit
 * coordinates (-1,-1=>1,1).
 * The cube lays out the 4 equatorial faces in a row. The North Pole
 * face is above face 0 and the South Pole face is below face 0. This
 * results in a space measuring (0,0=>4,3).
 *
 * Note: The cube faces have coordinates (0,0=>4,3), but the profile
 * is defined as having a range of (0,0=>4,4) and one key at the top
 * level. This key and its subkeys are not valid, but the cube faces
 * will have an LOD of 2. This betters corresponds to the LODs in a
 * geographic profile.
 */
class EulerSpatialReference : public osgEarth::SpatialReference
{
public:
    EulerSpatialReference(void* handle);

    virtual osgEarth::GeoLocator* createLocator(
        double xmin, double ymin, double xmax, double ymax,
        bool plate_carre =false) const;

    // Euler is a projected coordinate system.
    virtual bool isGeographic() const { return false; }
    virtual bool isProjected() const { return true; }

    // This SRS uses a WGS84 lat/long SRS under the hood for reprojection. So we need the
    // pre/post transforms to move from cube to latlong and back.
    virtual bool preTransform(double& x, double& y, void* context) const;
    virtual bool postTransform(double& x, double& y, void* context) const;
    // Optimized versions that avoid going through GDAL.
    virtual bool transform(
        double x, double y,
        const SpatialReference* to_srs,
        double& out_x, double& out_y,
        void* context =0L) const;

    virtual bool transformPoints(
        const SpatialReference* to_srs, 
        double* x, double *y,
        unsigned int numPoints,
        void* context =0L,
        bool ignore_errors =false) const;
    
    virtual bool transformExtent(
        const SpatialReference* to_srs,
        double& in_out_xmin,
        double& in_out_ymin,
        double& in_out_xmax,
        double& in_out_ymax,
        void* context) const;

    virtual bool transformExtentPoints(
        const SpatialReference* to_srs,
        double in_xmin, double in_ymin,
        double in_xmax, double in_ymax,
        double* x, double *y,
        unsigned int numx, unsigned int numy,
        void* context = 0L, bool ignore_errors = false ) const;

protected: // SpatialReference overrides

    void _init();
    friend class CacheInitializer;
};

class EulerProfile : public osgEarth::Profile
{
public:
    EulerProfile();

public: // utilities

    /**
     * Gets the cube face associated with a tile key (in cube srs).
     */
    static int getFace( const osgEarth::TileKey& key );

public: // Profile

    virtual void getIntersectingTiles(
        const osgEarth::GeoExtent& extent,
        std::vector<osgEarth::TileKey>& out_intersectingKeys ) const;

private:

};
}
#endif
