/*
 *  TnMapTileId.h
 *  OpenGL Map Engine
 *
 *  Copyright 2011 TeleNav. All rights reserved.
 *
 *  Note the old TeleNav-style level system goes from 0-17, where
 *  level 0 represents the smallest tile and level 17 represents the
 *  largest tile covering the whole earth. (The engine originally
 *  limited itself to levels 0-15, skipping the last two levels, for
 *  reasons that aren't clear.)
 *
 *  In contrast, the QuadCoord level increases as you go down in tile
 *  size. Level 0 corresponds to the root node.
 */

#ifndef TN_MAP_TILE_ID_H
#define TN_MAP_TILE_ID_H

#include <boost/optional.hpp>
#include <foundation/TnTypes.h>
#include <foundation/Math/QuadCoord.hpp>
#include <foundation/Math/BoundingBox.hpp>

#include <iostream>

#include <set>

class TnMapTileId
{
public:

    TnMapTileId(Tn::math::QuadCoord const &, uint32_t baseTileSize);

    /*! Construct with a global X, global Y, zoom level, and base tile
     *  size. TODO: Remove this, and fix Tn::math::QuadCoord. */

    TnMapTileId(uint32_t globalX, uint32_t globalY, uint32_t zoom, uint32_t baseTileSize);

    Tn::math::QuadCoord const & GetQuadCoord() const;

    /* This takes a DM5 (global) coordinate, an old-style TeleNav zoom
     * level, and a base tile size, and returns the corresponding quad
     * tree coordinate.
     */

    static Tn::math::QuadCoord globalToQuadCoord(uint32_t globalX, uint32_t globalY,
                                                 uint32_t level, uint32_t baseTileSize);


    /* This takes latitude and longitude and a tile level and a base
     * tile size, and returns returns the corresponding quadtree
     * coordinate. Were it not for the requirement on the base tile
     * size, this would more properly belong in the Projection math
     * library.
     */
    static Tn::math::QuadCoord latLonToQuadCoord(double latitude, double longitude,
                                                 uint32_t level, uint32_t baseTileSize);


    /*! Returns the old TeleNav-style integer zoom level associated
     *  with the specified tile identifier. If there is no legal zoom
     *  level, a null option is returned. */
    boost::optional<uint32_t> GetZoom() const;

    /*! Returns the quad-tree-style level (with zero at the top-level
     *  root tile). */

    uint32_t GetLevel() const;
    
    /*! Returns the real-world Y or X coordinate of the origin of the
     *  specified tile. (These only work for TeleNav levels 0-17. For
     *  higher-res tiles, use QuadCoord.) */
    uint32_t GlobalY() const;
    uint32_t GlobalX() const;

    /*! Returns the X or Y tile index. */
    uint32_t IndexX() const;
    uint32_t IndexY() const;


    // TODO: Remove Neighbor and Difference once TnMapScene is removed.
    // (These are still used by TnPreloaderStrategies.cpp)

    /*! Returns the ID of the specified neighboring tile. Negative x
     *  to west, positive x to east, negative y to north, positive y
     *  to south. */
    TnMapTileId Neighbor(int32_t x, int32_t y) const;

    /*! Returns the difference (in number of tiles, X & Y directions)
     *  between two tiles b-a */
    static int32_t DifferenceX(TnMapTileId const & a, TnMapTileId const & b);
    static int32_t DifferenceY(TnMapTileId const & a, TnMapTileId const & b);

    /*! Returns bounding box in DM5 global. */
    Tn::math::BoundingBoxd GetBounds() const;
    
    Tn::math::BoundingBoxd GetVolumetricBounds(float minElev, float maxElev) const;

    /*! Returns the size of an edge of this tile. */
    uint32_t TileSize() const;

    /*! Returns the size of an edge of a tile at the specified zoom level and base tile size*/
    static uint32_t TileSize(uint32_t zoom, uint32_t baseTileSize);

    
    /*! Returns the base tile size represented by this ID */
    uint32_t BaseTileSize() const;

    /*! Some useful comparators */
    friend bool operator==(TnMapTileId const & a, TnMapTileId const & b);
    friend bool operator!=(TnMapTileId const & a, TnMapTileId const & b);
    friend bool operator<(TnMapTileId const & a, TnMapTileId const & b);

    /*  Old Telenav-style zoom new level.
     */

    static boost::optional<uint32_t> LevelToZoom(uint32_t level, uint32_t baseTileSize);

    /*  Takes tile size in globals and returns corresponding level.
     */

    static uint32_t GetMaxZoom(uint32_t baseTileSize);
    
    /*! Returns the real-world coordinate of the origin of the specified
     *  tile. */
    
    static Tn::math::Point3d tileIdToPoint(TnMapTileId tileId);
    
    
    /*! Returns the tile identifier containing the specified point for a
     *  given zoom level. */
    
    static TnMapTileId pointToTileID(Tn::math::Point3d point, uint32_t zoomLevel, uint32_t baseTileSize);

private:

    /*! Returns the size of an edge of the tile at the specified zoom level */
    uint32_t TileSize(uint32_t zoom) const;
    

    static uint32_t ignoredBits(uint32_t baseTileSize);

    uint32_t m_base;    // base tile size
    uint32_t m_ignored; // number of ignored bits compared to DM5
    Tn::math::QuadCoord m_qc;

    // These are DM5
    uint32_t m_globalX;
    uint32_t m_globalY;
};

/*! Stream insertion operator for debug output */
std::ostream& operator<<(std::ostream& stream, TnMapTileId const & tile);

#endif
