/***************************** 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 *************************************/

#include "PmProjection.h"
#include "ObjectList.h"
#include "PmGeneralProjection.h"
#include "SatelliteProjection.h"

bool IsEqual(float a, float b)
{
    double da, db;
    da = floor(double(a));
    db = floor(double(b));

    if (da != db)
        return false;

    long la, lb;
    la = (long)((a - da) * 100000.);
    lb = (long)((b - db) * 100000.);
    if (la != lb)
        return false;
    return true;
}

bool IsEqual(double a, double b)
{
    double da, db;
    da = floor(a);
    db = floor(b);

    if (da != db)
        return false;

    long la, lb;
    la = (long)((a - da) * 1000000.);
    lb = (long)((b - db) * 1000000.);
    if (la != lb)
        return false;
    return true;
}

bool operator==(PmGeneralProjection& a, PmGeneralProjection& b)
{
    if (a.Pname != b.Pname)
        return false;
    if (!IsEqual(a.Prd, b.Prd))
        return false;
    if (!IsEqual(a.Pflt, b.Pflt))
        return false;

    if (a.GPhemis != b.GPhemis)
        return false;
    if (!IsEqual(a.GPlat0, b.GPlat0))
        return false;
    if (!IsEqual(a.GPlon0, b.GPlon0))
        return false;
    if (!IsEqual(a.GPstlat1, b.GPstlat1))
        return false;

    return true;
}

bool operator==(Satellite& a, Satellite& b)
{
    if (a.Pname != b.Pname)
        return false;
    if (!IsEqual(a.Prd, b.Prd))
        return false;
    if (!IsEqual(a.Pflt, b.Pflt))
        return false;

    if (!IsEqual(a.SPri, b.SPri))
        return false;
    if (!IsEqual(a.SPrj, b.SPrj))
        return false;
    if (!IsEqual(a.SPis, b.SPis))
        return false;
    if (!IsEqual(a.SPjs, b.SPjs))
        return false;
    if (!IsEqual(a.SPla0, b.SPla0))
        return false;
    if (!IsEqual(a.SPlo0, b.SPlo0))
        return false;
    if (!IsEqual(a.SPrs, b.SPrs))
        return false;

    return true;
}

// ===========================================================
//
//  Factory for Projections
//
Cached
PmProjectionFactoryTraits::FactoryName(const MvRequest& reqst)
{
    // Get the projection name. If it does not exist in the request,
    // expand the request to search for the default projection name
    Cached projectionName = reqst("MAP_PROJECTION");
    if (!projectionName) {
        MvRequest tmpReq = ObjectList::ExpandRequest(reqst, EXPAND_DEFAULTS);
        projectionName = tmpReq("MAP_PROJECTION");
    }

    return projectionName;
}

PmProjection*
PmProjectionFactoryTraits::DefaultObject(const MvRequest& reqst)
{
    return new PmCylindEquid(reqst);
}

// ================================================================
//
//   PmProjection base class
//
//
PmProjection ::PmProjection(const MvRequest& viewRequest)
{
    // Get the projection name. If it does not exist in the request,
    // expand the request to search for the default projection name
    MvRequest tmpReq = viewRequest;
    Pname = tmpReq("MAP_PROJECTION");
    if (!Pname) {
        tmpReq = ObjectList::ExpandRequest(viewRequest, EXPAND_DEFAULTS);
        Pname = tmpReq("MAP_PROJECTION");
    }

    // Default values for radius and flatness
    Prd = EARTHRADIUS;
    Pflt = EARTHFLATNESS;

    // Initialize the bounding box in geodetic coordinates
    double lat1 = tmpReq("AREA", 0);
    double long1 = tmpReq("AREA", 1);
    double lat2 = tmpReq("AREA", 2);
    double long2 = tmpReq("AREA", 3);

    Location geodeticCoord(lat2, long1, lat1, long2);
    geodCoord_ = geodeticCoord;
}

PmProjection::~PmProjection() = default;

PmProjection& PmProjection::operator=(PmProjection& proj) = default;

bool operator==(PmProjection& a, PmProjection& b)
{
    if (a.Pname != b.Pname)
        return false;

    if (a.Pname == SATELLITE) {
        if (*(Satellite*)&a == *(Satellite*)&b)
            return true;
        else
            return false;
    }
    else  // GENERALPROJ
    {
        if (*(PmGeneralProjection*)&a == *(PmGeneralProjection*)&b)
            return true;
        else
            return false;
    }
}

//
//  LatLongTransf
//
//  PURPOSE : Transform a box on projection coordinates
//            to a box in lat/long coordinates (in degrees)
Location
PmProjection::LatLongTransf(const Location& box)
{
    // Extract the lower left and upper right points
    Point projLowerLeft(box.Left(), box.Bottom());
    Point projUpperRight(box.Right(), box.Top());

    // Calculate the coordinates in geodetic coordinates
    Point geodLowerLeft = this->PC2LL(projLowerLeft);
    Point geodUpperRight = this->PC2LL(projUpperRight);

    return {geodUpperRight.Y() * RAD_TO_DEG,
            geodLowerLeft.X() * RAD_TO_DEG,
            geodLowerLeft.Y() * RAD_TO_DEG,
            geodUpperRight.X() * RAD_TO_DEG};
}

//
//  ProjectionTransf
//
//  PURPOSE : Transform a box in lat/long coordinates (in degrees)
//            to a box in projection coordinates
Location
PmProjection::ProjectionTransf(const Location& box)
{
    // Extract the lower left and upper right points
    Point geodLowerLeft(box.Left() * DEG_TO_RAD,
                        box.Bottom() * DEG_TO_RAD);
    Point geodUpperRight(box.Right() * DEG_TO_RAD,
                         box.Top() * DEG_TO_RAD);

    // Calculate the coordinates in geodetic coordinates
    Point projLowerLeft = this->LL2PC(geodLowerLeft);
    Point projUpperRight = this->LL2PC(geodUpperRight);

    return {projUpperRight.Y(),
            projLowerLeft.X(),
            projLowerLeft.Y(),
            projUpperRight.X()};
}

void PmProjection::CalculateProjectionCoord()
{
    // Extract the lower left and upper right points
    Point geodLowerLeft(geodCoord_.Left() * DEG_TO_RAD,
                        geodCoord_.Bottom() * DEG_TO_RAD);
    Point geodUpperRight(geodCoord_.Right() * DEG_TO_RAD,
                         geodCoord_.Top() * DEG_TO_RAD);

    // Calculate the box in projection coordinates
    Point projLowerLeft = this->LL2PC(geodLowerLeft);
    Point projUpperRight = this->LL2PC(geodUpperRight);

    Location projCoord(projUpperRight.Y(),
                       projLowerLeft.X(),
                       projLowerLeft.Y(),
                       projUpperRight.X());

    // Store the projection coordinates for later use
    projCoord_ = projCoord;
}

#if 0
bool
PmProjection::CheckCoord()
{
	this->CalculateProjectionCoord ();

	if ( ( projCoord_.Left()   > projCoord_.Right() ) ||
	     ( projCoord_.Bottom() > projCoord_.Top()   ) )
		return false;
	return true;
}
#endif
