/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#pragma once

#include <MvRequest.h>
#include <Point.hpp>

#include "Factory.hpp"
#include "Location.h"

const Cached MERCATOR     = "MERCATOR";
const Cached CYLINDRICAL  = "CYLINDRICAL";
const Cached POLAR        = "POLAR_STEREOGRAPHIC";
const Cached SATELLITE    = "SATELLITE";
const Cached SPACEVIEW    = "SPACEVIEW";
const Cached OCEANSECTION = "OCEAN_SECTION";
const Cached AITOFF       = "AITOFF";
const Cached LAMBERT      = "LAMBERT";

enum PROJECTIONID
{
    CylindricalId,
    PolarNorthId,
    PolarSouthId,
    MercatorId,
    SpaceViewId,
    OceanSectionId,
    AitoffId,
    LambertId,
    CartesianViewId
};

const short PDATUM      = 0x1;
const short PHEMISPHERE = 0x2;
const short PLATITUDE   = 0x4;
const short PLONGITUDE  = 0x8;
const short PFIRSTLAT   = 0x10;
const short PSECDLAT    = 0x20;

const short NORTH = 1;
const short SOUTH = -1;

const double EARTHRADIUS   = 6378160.;  // Int. Astronomical Union - 1965
const double EARTHFLATNESS = 1. / 297.;

const double DEG_TO_RAD = M_PI / 180.;
const double RAD_TO_DEG = 180. / M_PI;

const double SCALE_THSD    = 1. / 1000.;     // 10**(-3)
const double SCALE_MILLION = 1. / 1000000.;  // 10**(-6) (scale factor used in GRIB )

// Class Device Factory - clones a new Device on Request
class PmProjection;

struct PmProjectionFactoryTraits
{
    typedef PmProjection Type;
    typedef const MvRequest& Parameter;
    static Cached FactoryName(Parameter);
    static Type* DefaultObject(Parameter);
};


class PmProjectionFactory : public Factory<PmProjectionFactoryTraits>
{
public:
    PmProjectionFactory(const Cached& name) :
        Factory<PmProjectionFactoryTraits>(name) {}
};

//
// .NAME:
// PmProjection.
//
// .AUTHOR:
// Ubirajara Freitas, Julio d'Alge.
//
// .SUMMARY:
//  Provides methods that are required to handle all map projection
//  definitions and georeferencing of satellite images.
//
// .DESCRIPTION:
//  Specifies earth and projection parameters that represent a common
//  ground in terms of defining conventional map projections, navigating
//  on low-resolution images of geostationary satellites. Class PmProjection
//  also encapsulates the following important information:
//		Earth equatorial radius;
//		Earth flatenning;
//		Coordinates of bounding box (in lat/long and projection meters).
//
// .DESCENDENT:
//  PMGeneralProjection, Satellite
//
// .RELATED:
//  Location, Point.
//
// .COMMENTS:
//  Modified from original INPE projection code for use in METVIEW by G.Camara
//  (included creation based on a request)

class PmProjection
{
protected:
    Cached Pname;  // PmProjection name
    double Prd;    // Earth equatorial radius (m)
    double Pflt;   // Earth flattening

    Location geodCoord_;  // Bounding box in lat/long coordinates
    Location projCoord_;  // Bounding box in projection coordinates

public:
    PmProjection(const MvRequest& reqst);
    //		Normal constructor.
    //		Description:
    //			Initializes its members
    //                      Earth radius and flattening are given default values
    //                      Calculates the bounding box (in geodetic coordinates)

    virtual ~PmProjection();
    //		Destructor.
    //		Description:
    //			Frees allocated memory.

    PmProjection& operator=(PmProjection& p);
    //		Assignment operator.
    //		Input:
    //			p: 	PmProjection to be copied.
    //		Return:
    //			Assigned projection.

    friend bool operator==(PmProjection& a, PmProjection& b);
    //		Equality operator. Objects a and b are considered equal if
    //		their projection codes are the same.
    //		Input:
    //			a:	Object of type PmProjection;
    //			b:	Object of type PmProjection.
    //		Return:
    //			TRUE:	Objects are equal;
    //			FALSE:	Objects are different.

    //      Returns the location of the bounding box in lat/long
    const Location& GeodeticCoord()
    {
        return geodCoord_;
    }

#if 0
	void SetGeodeticCoord ( const Location coord )
		{ geodCoord_ = coord; }
//              Sets the location of the bounding box in lat/long

	Location& ProjectionCoord()
		{ return projCoord_; }
//              Returns the location of the bounding box in projection coordinates
#endif

    Location LatLongTransf(const Location& box);
    //               Transformation operator that generates a location in lat/long (degrees)
    //               given a location in projection coordinates
    //
    //               Input:
    //                    box : rectangle in projection coordinates (m)
    //               Output:
    //                    Location in lat/long (degrees)
    //
    Location ProjectionTransf(const Location& box);
    //               Transformation operator that generates a location in projection
    //               coordinates (m) given a location in lat/long (degrees)
    //               Input:
    //                    box : rectangle in lat/long (degrees)
    //               Output:
    //                    Location in projection coordinates (m)
    //

    virtual void CalculateProjectionCoord();
    //              Calculate the bounding box in projection coordinates
    //              (internal function )
    //              Input:
    //                    geodCoord_ : bounding box in lat/long
    //              Output:
    //                    projCoord_ : bounding box in projection coordinates

#if 0
	const   Cached&	Name()
		       {return Pname;} 
// 		Returns projection name.
//		Return:
//			Pname:	PmProjection name.	     

	double 	EarthRadius() 			{return Prd;}
//		Returns earth equatorial radius.
//		Return:
//			Prd:	Earth equatorial radius (m).

	void   	EarthRadius (double e) 		{Prd = e;}
//		Updates earth equatorial radius.
//		Input:
//			e:	Earth equatorial radius (m).
//		Precondition:
//			e must be a valid earth equatorial radius
//			(e.g., about 6,370,000 m).

	double 	EarthFlattening() 		{return Pflt;}
//		Returns earth flattening.
//		Return:
//			Pflt:	Earth flattening.

	void   	EarthFlattening (double e) 	{Pflt = e;}
//		Updates earth flattening.
//		Input:
//			e: 	Earth flattening.
//		Precondition:
//			e must be a valid earth flattening (e.g., about
//			1/298  for an ellipsoidal earth and 0 for a 
//			spherical earth).
#endif

    virtual Point LL2PC(Point& p) = 0;
    //		Pure virtual method that transforms geodetic into
    //		projection coordinates.
    //		Description:
    //			This method is implemented for each available
    //			projection class and represents the so-called
    //			direct formulas, which compute projection
    //			coordinates from geodetic coordinates.
    //		Input:
    //			p: 	Geodetic coordinates (rad)
    //		Return:
    //			p:  	PmProjection coordinates (m)
    //		Preconditions:
    //			Geodetic coordinates must be a valid latitude
    //			([0,pi/2] or [0,-pi/2]) and a valid longitude
    //			([0,pi] or [0,-pi])

    virtual Point PC2LL(Point& p) = 0;
    //		Pure virtual method that transforms projection into
    //		geodetic coordinates.
    //		Description:
    //			This method is implemented for each available
    //			projection class and represents the so-called
    //			inverse formulas, which compute geodetic
    //			coordinates from projection coordinates.
    //		Input:
    //			p: 	PmProjection coordinates (m)
    //		Return:
    //			p:	Geodetic coordinates (rad)
    //		Preconditions:
    //			X and Y projection coordinates must be both valid
    //			within the typical range of each projection class

#if 0
	virtual double	SensorResolutionX () = 0;
//		Pure virtual method that returns the sensor angular
//		resolution along X direction.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and returns the sensor
//			angular resolution (IFOV) along X direction.

	virtual void	SensorResolutionX (double s) = 0;
//		Pure virtual method that updates the sensor angular
//		resolution along X direction.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and updates the sensor
//			angular resolution (IFOV) along X direction.
//		Input:
//			s: 	Angular resolution along X direction (rad).
//		Precondition:
//			s must be a valid angular resolution.

	virtual double 	SensorResolutionY () = 0;
//		Pure virtual method that returns the sensor angular
//		resolution along Y direction.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and returns the sensor
//			angular resolution (IFOV) along Y direction.

	virtual void    SensorResolutionY (double s) = 0;
//		Pure virtual method that updates the sensor angular
//		resolution along Y direction.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and updates the sensor
//			angular resolution (IFOV) along Y direction.
//		Input:
//			s: 	Angular resolution along Y direction (rad).
//		Precondition:
//			s must be a valid angular resolution.

	virtual double	SubSatelliteX () = 0;
//		Pure virtual method that returns the sub-satellite X 
//		coordinate.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and returns the satellite
//			X coordinate of sub-satellite point, which is the
//			pixel or column coordinate multiplied by the spatial
//			resolution in meters.

	virtual void	SubSatelliteX (double s) = 0;
//		Pure virtual method that updates the sub-satellite X 
//		coordinate.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and updates the satellite
//			X coordinate of sub-satellite point, which is the
//			pixel or column coordinate multiplied by the spatial
//			resolution in meters.
//		Input:
//			s: 	Sub-satellite X coordinate (m).
//		Precondition:
//			s must be a valid coordinate value.

	virtual double 	SubSatelliteY () = 0;
//		Pure virtual method that returns the sub-satellite Y 
//		coordinate.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and returns the satellite
//			Y coordinate of sub-satellite point, which is the
//			line coordinate multiplied by the spatial resolution
//			in meters.

	virtual void   	SubSatelliteY (double s) = 0;
//		Pure virtual method that updates the sub-satellite Y
//		coordinate.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and updates the satellite
//			Y coordinate of sub-satellite point, which is the
//			line coordinate multiplied by the spatial resolution
//			in meters.
//		Input:
//			s: 	Sub-satellite Y coordinate (m).
//		Precondition:
//			s must be a valid coordinate value.

	virtual double	ScanMode () = 0;
//		Pure virtual method that returns the sensor scanning 
//		mode: 0-WE/NS, 1-SN/EW.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and returns the sensor
//			scanning mode, which is used in relating geodetic
//			and satellite X and Y coordinates.

	virtual double 	OriginX () = 0;
//		Pure virtual method that returns the initial image X 
//		coordinate.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and returns the initial
//			image X coordinate, which is the initial pixel or
//			column coordinate multiplied by the spatial
//			resolution in meters.

	virtual double 	OriginY () = 0;
//		Pure virtual method that returns the initial image Y 
//		coordinate.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and returns the initial
//			image Y coordinate, which is the initial line
//			coordinate multiplied by the spatial resolution
//			in meters.

	virtual double 	SubSatelliteLng () = 0;
//		Pure virtual method that returns the sub-satellite geodetic 
//		longitude.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and returns the satellite
//			geodetic longitude of sub-satellite point in radians.

	virtual void   	SubSatelliteLng (double s) = 0;
//		Pure virtual method that updates the sub-satellite geodetic 
//		longitude.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and updates the satellite
//			geodetic longitude of sub-satellite point in radians.
//		Input:
//			s: 	Geodetic longitude (rad).
//		Precondition:
//			Longitude must be valid ([0,pi] or [0,-pi]).

	virtual	double 	SubSatelliteLat () = 0;
//		Pure virtual method that returns the sub-satellite geodetic 
//		latitude.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and returns the satellite
//			geodetic latitude of sub-satellite point in radians.

	virtual void   	SubSatelliteLat (double s) = 0;
//		Pure virtual method that updates the sub-satellite geodetic 
//		latitude.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and updates the satellite
//			geodetic latitude of sub-satellite point in radians.
//		Input:
//			s: 	Geodetic latitude (rad).
//		Precondition:
//			Latitude must be valid ([0,pi/2] or [0,-pi/2]).

	virtual double 	Radius () = 0;
//		Pure virtual method that returns the satellite orbit radius.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and returns the satellite
//			orbit radius in meters.

	virtual void   	Radius (double s) = 0;
//		Pure virtual method that updates the satellite orbit radius.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and updates the satellite
//			orbit radius in meters.
//		Input:
//			s: 	Orbit radius (m).
//		Precondition:
//			s must be a valid orbit radius.

	virtual double 	Yaw () = 0;
//		Pure virtual method that returns the satellite/sensor
//		orientation angle.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and returns the yaw
//			or orientation angle of the satellite/sensor
//			in radians.

	virtual void   	Yaw (double s) = 0;
//		Pure virtual method that updates the satellite/sensor
//		orientation angle.
//		Description:
//			This method is implemented, when appropriate,
//			for each descendent class and updates the yaw
//			or orientation angle of the satellite/sensor
//			in radians.
//		Input:
//			s: 	Orientation angle (rad).
//		Precondition:
//			s must be a valid orientation angle.

	virtual int	Hemisphere () = 0;
//		Pure virtual method that returns the hemisphere.
//		Description:
//			This method is implemented for the PMGeneralProjection
//			descendent classes that require an explicit knowledge
//			of the hemisphere to relate geodetic and projection
//			coordinates.

	virtual void  	Hemisphere (int h) = 0;
//		Pure virtual method that updates the hemisphere.
//		Description:
//			This method is implemented for the PMGeneralProjection
//			descendent classes that require an explicit knowledge
//			of the hemisphere to relate geodetic and projection
//			coordinates.
//		Input:
//			h: 	Hemisphere: 1-north, -1-south.
//		Precondition:
//			h must be either north (1) or south (-1).

	virtual double 	OriginLatitude () = 0;
//		Pure virtual method that returns the latitude of origin.
//		Description:
//			This method is implemented for each descendent class
//			and returns the latitude of origin in radians.

	virtual void   	OriginLatitude (double o) = 0;
//		Pure virtual method that updates the latitude of origin.
//		Description:
//			This method is implemented for each descendent class
//			and updates the latitude of origin in radians.
//		Input:
//			o: 	Latitude of origin (rad).
//		Precondition:
//			Latitude must be valid ([0,pi/2] or [0,-pi/2]).

	virtual double 	OriginLongitude () = 0;
//		Pure virtual method that returns the longitude of origin 
//		or central meridian.
//		Description:
//			This method is implemented for each descendent class
//			and returns the longitude of origin or central
//			meridian in radians.

	virtual	void   	OriginLongitude (double o) = 0;
//		Pure virtual method that updates the longitude of origin 
//		or central meridian.
//		Description:
//			This method is implemented for each descendent class
//			and updates the longitude of origin or central
//			meridian in radians.
//		Input:
//			o: 	longitude of origin or central meridian (rad).
//		Precondition:
//			Longitude must be valid ([0,pi] or [0,-pi]).

	virtual	double 	StandardLatitudeOne () = 0;
//		Pure virtual method that returns the first standard parallel.
//		Description:
//			This method is implemented for all descendent classes
//			that require the latitude of the first standard 
//			parallel as a parameter to relate geodetic and				//			projection coordinates.

	virtual void   	StandardLatitudeOne (double s) = 0;
//		Pure virtual method that updates the first standard parallel.
//		Description:
//			This method is implemented for all descendent classes
//			that require the latitude of the first standard 
//			parallel as a parameter to relate geodetic and		//			projection coordinates.
//		Input:
//			s: 	Latitude of first standard parallel (rad).
//		Precondition:
//			Latitude must be valid ([0,pi/2] or [0,-pi/2]).

	virtual bool CheckCoord ();
#endif

    virtual bool CheckGeodeticCoordinates(Location&) = 0;

    virtual double CheckOriginLongitude() = 0;

    virtual bool CanDoZoom()
    {
        return true;
    }
};
