//- ****************************************************************************
//-
//- Copyright 2009 National Technology & Engineering Solutions of Sandia, LLC
//- (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
//- Government retains certain rights in this software.
//-
//- BSD Open Source License
//- All rights reserved.
//-
//- Redistribution and use in source and binary forms, with or without
//- modification, are permitted provided that the following conditions are met:
//-
//-   1. Redistributions of source code must retain the above copyright notice,
//-      this list of conditions and the following disclaimer.
//-
//-   2. Redistributions in binary form must reproduce the above copyright
//-      notice, this list of conditions and the following disclaimer in the
//-      documentation and/or other materials provided with the distribution.
//-
//-   3. Neither the name of the copyright holder nor the names of its
//-      contributors may be used to endorse or promote products derived from
//-      this software without specific prior written permission.
//-
//- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
//- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
//- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
//- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
//- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
//- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//- POSSIBILITY OF SUCH DAMAGE.
//-
//- ****************************************************************************

#ifndef POINTMAP_OBJECT_H
#define POINTMAP_OBJECT_H

// **** _SYSTEM INCLUDES_ ******************************************************

#include <cstdio>

// use standard library objects
using namespace std;

// **** _LOCAL INCLUDES_ *******************************************************

#include "CPPUtils.h"
#include "GeoTessGrid.h"
#include "GeoTessProfile.h"
#include "GeoTessMetaData.h"
#include "GeoTessPolygon.h"
#include "GeoTessPolygon3D.h"
#include "GeoTessPolygonFactory.h"

// **** _BEGIN GEOTESS NAMESPACE_ **********************************************

namespace geotess
{

// **** _FORWARD REFERENCES_ ***************************************************

class GeoTessModel;

// **** _CLASS DEFINITION_ *****************************************************

/**
 * \brief Relationships between vertices (2D positions in a tessellation), nodes
 * (1D positions along a radial Profile) and points (3D positions in a model).
 *
 * Class that manages relationships between vertices (2D positions in a tessellation),
 * nodes (1D positions along a radial Profile) and points (3D positions in a model).
 */
class GEOTESS_EXP_IMP GeoTessPointMap
{
private:

    /**
     * The GeoTessGrid object that supports the 2D components of the model grid.
     */
    GeoTessGrid& grid;

    /**
     * The data stored in the model. An nVertices x nLayers array of Profile objects. Each Profile
     * consists of an array of radii and the associated Data.
     */
    GeoTessProfile *** profiles;

    /**
     * metaData stores basic information about a GeoTessModel including:
     * <ul>
     * <li>textual description of the model
     * <li>the names of all the layers in the model, e.g., ["core", "mantle", "crust"]. Layer names
     * are specified in order of increasing radius.
     * <li>dataType; all the data values stored in the model are of this type. Must be one of
     * DOUBLE, FLOAT, LONG, INT, SHORTINT, BYTE.
     * <li>number of data attributes
     * <li>the names of all the data attributes ["P Velocity", "S Velocity", "Density", etc]
     * <li>the units of the data attributes ["km/sec", "km/sec", "g/cc", etc]
     * <li>layerTessIds: an integer map from layer index to tessellation index.
     * </ul>
     * Each GeoTessModel has a single instance of MetaData that it passes around to wherever the
     * information is needed.
     */
    GeoTessMetaData& metaData;

    GeoTessPolygon* polygon;

    /**
     * A nPoints by 3 array of indexes. For each point in the 3D grid, pointMap stores 3 indexes:
     * the index of the 2D vertex, the layer index, and the node index within the layer. There is an
     * entry for each data object, not each radius.
     */
    vector<vector<int> >  pointMap;

    bool populated;

public:

    /**
     * Constructor.  PointMap is initialized but not populated by
     * this method.
     */
    GeoTessPointMap(GeoTessModel& m);

    /**
     * Copy constructor.
     */
    GeoTessPointMap(GeoTessPointMap& other);

    /**
     * Overloaded assignment operator
     */
    GeoTessPointMap& operator=(const GeoTessPointMap& other);

    /**
     * Overloaded equality operator.
     * Throws and exception if other is not populated.
     */
    bool operator==(const GeoTessPointMap& other);

    /**
     * Overloaded inequality operator
     */
    bool operator!=(const GeoTessPointMap& other) { return !(*this == other); }

    /**
     * Destructor.
     */
    ~GeoTessPointMap();

    LONG_INT getMemory()
    {
        return (LONG_INT) sizeof(GeoTessPointMap)
                + (LONG_INT)(pointMap.capacity() * sizeof(vector<int>)
                + pointMap.size() * 3 * (LONG_INT)sizeof(int));
    }

    /**
     * Populates the PointMap such that every node in the entire
     * model is within the active region.  The pointIndex value
     * of every node in the model will be set a unique positive
     * value.
     */
    void setActiveRegion();

    /**
     * Populate the PointMap such that all nodes associated with
     * Profiles attached to grid vertices that are within the
     * 2D polygon are active. The pointIndex values of nodes
     * within the polygon will be set to unique positive values.
     * All nodes outside the polygon will have pointIndex
     * values equal to -1.
     * @param polygon a 2D Polygon object
     */
    void setActiveRegion(GeoTessPolygon* polygon);

    /**
     * Populate the PointMap such that nodes located within the
     * specified Polygon are active and all others are inactive.
     * The pointIndex values of nodes within the polygon will
     * be set to unique positive values. All nodes outside the
     * polygon will have pointIndex values equal to -1.
     * @param polygonFileName the name of a file that contains
     * a 2D or 3D polygon.
     */
    void setActiveRegion(const string& polygonFileName)
    {
        GeoTessPolygon* plgn = GeoTessPolygonFactory::getPolygon(polygonFileName);
        setActiveRegion(plgn);
    }

    /**
     * Retrieve the pointer to the Polygon or Polygon3D object
     * that supports this PointMap.  May be NULL.
     * <p>Polygon implements referenceCounting so if you wish
     * to retain a copy of this polygon, be sure to
     * addReference() and delete it when you are done with it.
     */
    GeoTessPolygon* getPolygon() { return polygon; }

    /**
     * Clears all the information in this pointMap and the
     * pointIndex values of all nodes in the model.
     * Following this call, isPopulated() will return false.
     * The pointIndex values in all model nodes and profiles
     * will be set to -1.
     */
    void clear();

    /**
     * Determine whether or not this PointMap is populated.
     * @return true if populated
     */
    bool isPopulated() { return populated; }

    /**
     * Retrieve the number of points supported by this model.
     *
     * @return the number of points supported by this model.
     */
    int size()
    {
        return pointMap.size();
    }

    /**
     * Retrieve the index of the vertex that corresponds to the specified
     * pointIndex.
     *
     * @param pointIndex
     * @return the index of the vertex that corresponds to the specified
     *         pointIndex.
     */
    int getVertexIndex(int pointIndex)
    {
        return pointMap[pointIndex][0];
    }

    /**
     * Retrieve the index of the tessellation that corresponds to the specified
     * pointIndex.
     *
     * @param pointIndex
     * @return the index of the tessellation that corresponds to the specified
     *         pointIndex.
     */
    int getTessId(int pointIndex)
    {
        return metaData.getTessellation(pointMap[pointIndex][1]);
    }

    /**
     * Retrieve the index of the layer that corresponds to the specified
     * pointIndex.
     *
     * @param pointIndex
     * @return the index of the layer that corresponds to the specified
     *         pointIndex.
     */
    int getLayerIndex(int pointIndex)
    {
        return pointMap[pointIndex][1];
    }

    /**
     * Retrieve the index of the node that corresponds to the specified
     * pointIndex.  Nodes refer to Data object, not radii.
     *
     * @param pointIndex
     * @return the index of the node that corresponds to the specified
     *         pointIndex.
     */
    int getNodeIndex(int pointIndex)
    {
        return pointMap[pointIndex][2];
    }

    /**
     * Retrieve a reference to the index map for the specified pointIndex. This
     * is a 3-element array consisting of 0:vertexIndex, 1:layerIndex,
     * 2:nodeIndex.
     *
     * @param pointIndex
     * @return the index map for the specified pointIndex.
     */
    const vector<int>& getPointIndices(int pointIndex)
    {
        return pointMap[pointIndex];
    }

    /**
     * Retrieve the pointIndex of the point that corresponds to the specified
     * vertex, layer and node.
     *
     * @param vertex
     * @param layer
     * @param node
     * @return the pointIndex of the point that corresponds to the specified
     *         vertex, layer and node.
     */
    int getPointIndex(int vertex, int layer, int node)
    {
        return profiles[vertex][layer]->getPointIndex(node);
    }

    /**
     * Retrieve the pointIndex of the point that corresponds to the last node
     * in profile[vertex][layer].  The last node is the one with the largest
     * radius (i.e., the shallowest node).
     *
     * @param vertex
     * @param layer
     * @return the pointIndex of the point that corresponds to the last node
     * in profile[vertex][layer].
     */
    int getPointIndexLast(int vertex, int layer)
    {
        return profiles[vertex][layer]->getPointIndex(profiles[vertex][layer]->getNData()-1);
    }

    /**
     * Retrieve the pointIndex of the point that corresponds to the first node
     * in profile[vertex][layer].  The first node is the one with the smallest
     * radius (i.e., the deepest node).
     *
     * @param vertex
     * @param layer
     * @return the pointIndex of the point that corresponds to the first node
     * in profile[vertex][layer].
     */
    int getPointIndexFirst(int vertex, int layer)
    {
        return profiles[vertex][layer]->getPointIndex(0);
    }

    /**
     * Replace the Data object associated with the specified point.
     */
    void setPointData(int pointIndex, GeoTessData* data)
    {
        vector<int>& map = pointMap[pointIndex];
        profiles[map[0]][map[1]]->setData(map[2], data);
    }

    /**
     * Retrieve a reference to the Data object associated with the specified point.
     * @param pointIndex
     * @return
     */
    GeoTessData* getPointData(int pointIndex)
    {
        vector<int>& map = pointMap[pointIndex];
        return profiles[map[0]][map[1]]->getData(map[2]);
    }

    /**
     * Set the value of the specified attribute at the specified point to the
     * specified value.
     *
     * @param pointIndex
     * @param attributeIndex
     * @param value
     */
    template <typename T>
    void setPointValue(int pointIndex, int attributeIndex, T value)
    {
        vector<int>& map = pointMap[pointIndex];
        profiles[map[0]][map[1]]->getData(map[2])->setValue(attributeIndex,
                value);
    }

    /**
     * Retrieve the value of the specified attribute at the specified point.
     *
     * @param pointIndex
     * @param attributeIndex
     * @return the value of the specified attribute at the specified point.
     */
    double getPointValue(int pointIndex, int attributeIndex)
    {
        vector<int>& map = pointMap[pointIndex];
        return profiles[map[0]][map[1]]->getValue(attributeIndex, map[2]);
    }

    /**
     * Retrieve the value of the specified attribute at the specified point
     * cast to double if necessary.
     *
     * @param pointIndex
     * @param attributeIndex
     * @return the value of the specified attribute at the specified point
     * cast to double if necessary.
     */
    double getPointValueDouble(int pointIndex, int attributeIndex)
    {
        vector<int>& map = pointMap[pointIndex];
        return profiles[map[0]][map[1]]->getData(map[2])->getDouble(attributeIndex);
    }

    /**
     * Retrieve the value of the specified attribute at the specified point
     * cast to float if necessary.
     *
     * @param pointIndex
     * @param attributeIndex
     * @return the value of the specified attribute at the specified point
     * cast to float if necessary.
     */
    float getPointValueFloat(int pointIndex, int attributeIndex)
    {
        vector<int>& map = pointMap[pointIndex];
        return profiles[map[0]][map[1]]->getData(map[2])->getFloat(attributeIndex);
    }

    /**
     * Retrieve the value of the specified attribute at the specified point
     * cast to LONG_INT if necessary.
     *
     * @param pointIndex
     * @param attributeIndex
     * @return the value of the specified attribute at the specified point
     * cast to LONG_INT if necessary.
     */
    LONG_INT getPointValueLong(int pointIndex, int attributeIndex)
    {
        vector<int>& map = pointMap[pointIndex];
        return profiles[map[0]][map[1]]->getData(map[2])->getLong(attributeIndex);
    }

    /**
     * Retrieve the value of the specified attribute at the specified point
     * cast to int if necessary.
     *
     * @param pointIndex
     * @param attributeIndex
     * @return the value of the specified attribute at the specified point
     * cast to int if necessary.
     */
    int getPointValueInt(int pointIndex, int attributeIndex)
    {
        vector<int>& map = pointMap[pointIndex];
        return profiles[map[0]][map[1]]->getData(map[2])->getInt(attributeIndex);
    }

    /**
     * Retrieve the value of the specified attribute at the specified point
     * cast to short if necessary.
     *
     * @param pointIndex
     * @param attributeIndex
     * @return the value of the specified attribute at the specified point
     * cast to short if necessary.
     */
    short getPointValueShort(int pointIndex, int attributeIndex)
    {
        vector<int>& map = pointMap[pointIndex];
        return profiles[map[0]][map[1]]->getData(map[2])->getShort(attributeIndex);
    }

    /**
     * Retrieve the value of the specified attribute at the specified point
     * cast to byte if necessary.
     *
     * @param pointIndex
     * @param attributeIndex
     * @return the value of the specified attribute at the specified point
     * cast to byte if necessary.
     */
    byte getPointValueByte(int pointIndex, int attributeIndex)
    {
        vector<int>& map = pointMap[pointIndex];
        return profiles[map[0]][map[1]]->getData(map[2])->getByte(attributeIndex);
    }

    /**
     * Return true if the value of the specified attribute at the specified
     * point is NaN.
     *
     * @param pointIndex
     * @param attributeIndex
     * @return true if the value of the specified attribute at the specified
     *         point is NaN.
     */
    bool isNaN(int pointIndex, int attributeIndex)
    {
        vector<int>& map = pointMap[pointIndex];
        return profiles[map[0]][map[1]]->isNaN(map[2], attributeIndex);
    }

    /**
     * Retrieve a vector representation of the specified point (not a unit
     * vector). The length of the vector is in km. This is a new double[], not a
     * reference to an existing variable.
     *
     * @param pointIndex
     * @param v (output) a vector representation of the specified point
     */
    void getPointVector(int pointIndex, double* v)
    {
        vector<int>& map = pointMap[pointIndex];
        const double* vv = grid.getVertex(map[0]);
        double r = profiles[map[0]][map[1]]->getRadius(map[2]);
        v[0] = vv[0] * r;
        v[1] = vv[1] * r;
        v[2] = vv[2] * r;
    }

    /**
     * Retrieve a reference to the unit vector for the specified point.
     *
     * @param pointIndex
     * @return a reference to the unit vector for the specified point.
     */
    const double* getPointUnitVector(int pointIndex) const
    {
        return grid.getVertex(pointMap[pointIndex][0]);
    }

    /**
     * Retrieve the radius of the specified point.
     *
     * @param pointIndex
     * @return radius of specified point, in km.
     */
    double getPointRadius(int pointIndex)
    {
        vector<int>& map = pointMap[pointIndex];
        return profiles[map[0]][map[1]]->getRadius(map[2]);
    }

    /**
     * Retrieve the radius of the specified point.
     *
     * @param pointIndex
     * @return radius of specified point, in km.
     */
    double getPointDepth(int pointIndex)
    {
        vector<int>& map = pointMap[pointIndex];
        return GeoTessUtils::getEarthRadius(
                grid.getVertex(pointMap[pointIndex][0]))
                - profiles[map[0]][map[1]]->getRadius(map[2]);
    }

    /**
     * Retrieve the straight-line distance between two points in km.
     *
     * @param pointIndex1
     * @param pointIndex2
     * @return the straight-line distance between two points in km.
     */
    double getDistance3D(int pointIndex1, int pointIndex2)
    {
        vector<int>& m1 = pointMap[pointIndex1];
        vector<int>& m2 = pointMap[pointIndex2];
        return GeoTessUtils::getDistance3D(grid.getVertex(m1[0]),
                profiles[m1[0]][m1[1]]->getRadius(m1[2]), grid.getVertex(m2[0]),
                profiles[m2[0]][m2[1]]->getRadius(m2[2]));
    }

//	/**
//	 * Append the input new values array to the profile at pointIndex
//	 */
//	template<typename T>
//	void appendData(int pointIndex, T* newValues, int n)
//	{
//		vector<int>& map = pointMap[pointIndex];
//		profiles[map[0]][map[1]]->appendData<T>(map[2], newValues, n);
//	}


    /**
     * Find all the points that are first-order neighbors of the specified
     * point. First, find all the vertices that are first order neighbors of the
     * vertex of the supplied point (vertices connected by a single triangle
     * edge). For each of those vertices, find the Profile that occupies the
     * same layer and find the index of the radius in that Profile that is
     * closest to the radius of the supplied point. Build the set of all such
     * node index values. Finally, convert the node indexes to point indexes.
     * There will generally be 6 such points, but that number is not guaranteed.
     *
     * <p>
     * Here is an example of using a HashSetInteger object:
     * <p>
     * import gov.sandia.gmp.util.containers.hash.sets.HashSetInteger; <br>
     * import gov.sandia.gmp.util.containers.hash.sets.HashSetInteger.Iterator;
     * <p>
     * Iterator it = getPointNeighbors(pointIndex).iterator(); <br>
     * while (it.hasNext()) <br>
     * { <br>
     * int pointIndex = it.next(); <br>
     * // do stuff with pointIndex. <br>
     * }
     */
    void getPointNeighbors(set<int>& pointNeighbors, int pointIndex);

    /**
     * Retrieve nicely formated string with lat, lon of the point in degrees.
     * @param pointIndex
     * @return string with lat, lon in degrees.
     */
    string getPointLatLonString(int pointIndex)
    {
        return GeoTessUtils::getLatLonString(getPointUnitVector(pointIndex));
    }

    /**
     * Retrieve a nicely formated string with lat, lon, depth of the point in degrees and km.
     * @param pointIndex
     * @return string with lat, lon, depth
     */
    string toString(int pointIndex)
    {
        char s[100];
        string frmt = "%8.3f";
        sprintf(s, frmt.c_str(), getPointDepth(pointIndex));
        return GeoTessUtils::getLatLonString(getPointUnitVector(pointIndex))
                + " " + s;
    }

};
// end class PointMap

}// end namespace geotess

#endif  // POINTMAP_OBJECT_H
