/** \file
 *
 *  Contains the Location class definition.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 *
 */

#ifndef LOCATION_H_
#define LOCATION_H_

#include "I1D.h"
#include "data/Point2D.h"
#include "units/Unit.h"
#include "utils/Str.h"

//class RingBufferVoid;

/**
 *  Wraps a pair of double precision (8-byte) floating point numbers
 *  and units for storage in a DataElement.  (Storage is in the most canonical
 *  form of the specified unit).  Provides methods for reading and
 *  writing the value to and from other DataValues, performing
 *  unit conversions as needed.
 *
 *  Location objects are serialized as a pair of 4-byte single precision
 *  floating point numbers by casting both numbers to single-precision
 *  floating point before storage.
 *
 *  Note: since these DataValues are created and destructed
 *  frequently, the new and delete operators have been overridden,
 *  so that objects can be re-used as needed.
 *
 *  Location::MallocRing_  stores deleted objects.
 *
 * \ingroup data
 */
class Location: public I1D<double>
{
public:
    /// Default constructor
    Location();

    /// Multi value constructior
    Location( const Unit& unit, const double& lat, const double& lon, bool scaleValues = true );

    /// Copy Constructor
    Location( const Location& value );
    /*
        /// Constructor from binary
        Location( const Unit& unit, const void *binary );
    */
    /// Destructor
    virtual ~Location()
    {}
    ;

    /// Override new
    //void* operator new( size_t size );

    /// Override delete
    //void operator delete( void* p );

    const double getLat( const Unit& unit ) const;

    const double getLon( const Unit& unit ) const;

    void setLat( const Unit& unit, const double& lat );

    void setLon( const Unit& unit, const double& lat );

    /// Return a pointer to a new copy
    Location* copy() const;
    /*
        /// Subtraction of another DataValue to yield another DataValue
        virtual void minus( const DataValue* rhs, DataValue* assignTo ) const;

        /// Multiplication by a double to yield another DataValue
        virtual void times( const double rhs, DataValue* assignTo ) const;

        /// Return the absolute value of this difference from another DataValue
        virtual double absDiff( const DataValue* rhs ) const;
    */
    /// Copy value to unsigned char
    virtual bool copyTo( const Unit& unit, unsigned char& assignTo ) const
    {
        return false;
    }

    /// Copy value to float
    virtual bool copyTo( const Unit& unit, float& assignTo ) const
    {
        return false;
    }

    /// Copy value to double
    virtual bool copyTo( const Unit& unit, double& assignTo ) const
    {
        return false;
    }

    /// Copy n-dimensional value to double
    virtual bool copyTo( const Unit& unit, double& assignTo, unsigned int dim ) const;

    /// Copy value to unsigned int
    virtual bool copyTo( const Unit& unit, int& assignTo ) const
    {
        return false;
    }
    /*
        /// Comparison with unsigned char
        virtual int compare( const Unit& unit, const unsigned char& compareTo ) const
        {
            return -1;
        }

        /// Comparison with double
        virtual int compare( const Unit& unit, const double& compareTo ) const
        {
            return -1;
        }

        /// Comparison with float
        virtual int compare( const Unit& unit, const float& compareTo ) const
        {
            return -1;
        }

        /// Comparison with int
        virtual int compare( const Unit& unit, const int& compareTo ) const
        {
            return -1;
        }

        /// Comparison with another DataValue
        virtual int compare( const DataValue& compareTo ) const;

        /// Equality test with with unsigned char
        virtual bool equals( const Unit& unit, const unsigned char& compareTo ) const
        {
            return false;
        }

        /// Equality test with double
        virtual bool equals( const Unit& unit, const double& compareTo ) const
        {
            return false;
        }

        /// Equality test with float
        virtual bool equals( const Unit& unit, const float& compareTo ) const
        {
            return false;
        }

        /// Equality test with int
        virtual bool equals( const Unit& unit, const int& compareTo ) const
        {
            return false;
        }

        /// Equality test with another DataValue
        virtual bool equals( const DataValue& compareTo ) const;
    */
    /// set from unsigned char value
    bool setFrom( const Unit& unit, unsigned char value )
    {
        return false;
    }

    /// set from double value
    bool setFrom( const Unit& unit, double value )
    {
        return false;
    }

    /// set from float value
    bool setFrom( const Unit& unit, float value )
    {
        return false;
    }

    /// set int value
    bool setFrom( const Unit& unit, int value )
    {
        return false;
    }

    /// set DataValue value
    bool setFrom( const DataValue& value );

    // as a Str
    Str toString( const Unit& unit ) const;
    /*
        /// Send the data value to the indicated stream.
        /// Return the number of bytes written.
        /// For now, assume little-endian.
        unsigned int toStream( OutStream& outStream ) const;

        /// Load the data from the a binary representation in the buffer
        /// Typically just a memcopy of the variable, though endianness
        /// may come into it.
        void fromBinary( const void *buffer );
    */
    Location& operator=( const Location& rhs );

    Location& operator+=( const Location& rhs );

    Location& operator-=( const Location& rhs );

    Location& operator*=( const double mult );

    const bool operator==( const Location& rhs ) const;

    const bool operator!=( const Location& rhs ) const;

    const Location operator+( const Location& rhs ) const;

    const Location operator-( const Location& rhs ) const;

    const Location operator*( const double mult ) const;

    bool isNan() const;

    // Gives the bearing (in radians) between two locations (in radians).
    static double GetBearing( const double& latitude0, const double& longitude0, const double& latitude1, const double& longitude1 );

    // Gives the bearing (in radians) between this location and another (in radians).
    double getBearing( const double& latitude, const double& longitude ) const;

    // Gives the bearing (in radians) between this location and another.
    double getBearing( const Location& location1 ) const;

    // Calculates the vector (in meters) between two locations (in radians).
    static void CalcVector( const double& lat0, const double& lon0, const double& lat1, const double& lon1, double& toEast, double& toNorth );

    // Calculates the vector (in meters) between this location and another.
    void calcVector( const Location& location1, double& toEast, double& toNorth ) const;

    // Calculates the vector (in meters, northings & eastings) between this location and another.
    Point2D calcVector( const Location& location1 ) const;

    // Gives the distance (in meters) between two locations (in radians).
    static double GetDistance( const double& latitude0, const double& longitude0, const double& latitude1, const double& longitude1 );

    // Gives the distance (in meters) between two locations (in radians).
    // Derived from Spherical Law of Cosines, no inverse trig.
    // Less accurate at long distances (0.25% error at 4000km), but very accurate for short distances
    static double GetDistanceFast( const double& latitude0, const double& longitude0, const double& latitude1, const double& longitude1 );

    // Gives the distance (in meters) between this location and another (in radians).
    double getDistance( const double& latitude, const double& longitude ) const;

    // Gives the distance (in meters) between this location and another.
    double getDistance( const Location& location1 ) const;

    // Gives the distance (in meters) between this location and another using the fast algorithm described above.
    double getDistanceFast( const Location& location1 ) const;

    static void AtBearing( const float bearing, const float distance, double& lat, double& lon );

    static void AtBearingEllipsoid( const float bearing, const float distance, double& lat, double& lon );

    // Given a heading (the parameter "bearing"), and a distance, sets this location
    void atBearing( const double bearing, const double distance );

    // Calculate a new latitude due to an absolute change in northings
    static double NorthingsDelta( const double startLat, const float northingsDelta );

    // Calculate a new longitude due to an absolute change in eastings
    static double EastingsDelta( const double startLat, const double startLon, const float eastingsDelta );

    // Adjust latitude & longitude for an absolute change in northings and eastings
    static void Delta( double& lat, double& lon, double northings, double eastings );

    void delta( double northings, double eastings ) const
    {
        Delta( this->lat_, this->lon_, northings, eastings );
    }

    // Adjust latitude & longitude for an absolute change in northings and eastings from a vector
    void delta( const Point2D& vector )
    {
        Delta( this->lat_, this->lon_, vector.getX(), vector.getY() );
    }

    double distanceFromBaseline( const Location pointOnLine1, const Location pointOnLine2 ) const
    {
        return DistanceFromBaseline( *this, pointOnLine1, pointOnLine2 );
    }

    static double DistanceFromBaseline( const Location location, const Location pointOnLine1, const Location pointOnLine2 );

    // Caculate two locations from intesecting circles around two center locations and radii
    // Return values:
    //   ref intersect1, ref intersect2: locations where the circles intersect, or if they don't, where they are the closest
    //   intersecting: true if the two circles intersects, false if the two circles do not intersects
    static bool IntersectingCircles( const Location& c1, const double& r1, const Location& c2, const double& r2,
                                     Location& intersect1, Location& intersect2 );

    // Haversine function
    static double HavSin( double angle );

    static const double EARTH_RADIUS;
    static const double EARTH_RADIUS_INVERTED;

    /// Implement abstract I1D method
    /// Note that Location, when accessed via I1D methods, returns RADIANS
    virtual double* getPtr1d()
    {
        return value_;
    }

    /// Implement abstract I2D method
    virtual int getM() const
    {
        return 2;
    }

protected:

    // The actual data
    double value_[2];

    /// Latitude, in radians north
    double& lat_;

    /// Longitude, in radians east
    double& lon_;

    /// Declared as a pointer to avoid including RingBuffer.h header here.
    //static RingBufferVoid* MallocRing_;

    /// Makes sure that mallocRing_s is deleted on shutdown
    //class StaticDestructor
    //{
    //public:
    //    ~StaticDestructor();
    //};

    /// Static instance of StaticDestructor
    //static Location::StaticDestructor StaticDestructor_;

};

#endif /*LOCATION_H_*/
