#include "map/ellipsoid.h"
#include "krovak.h"

Krovak::Krovak(const Ellipsoid &ellipsoid, double standardParallel,
  double azimuth, double scale, double centerLatitude, double longitudeOrigin,
  double falseEasting, double falseNorthing)
{
	double phiC = deg2rad(centerLatitude);
	double sinPhiC = sin(phiC);
	double sinPhiC2 = sinPhiC * sinPhiC;
	double cosPhiC = cos(phiC);
	double cosPhiC2 = cosPhiC * cosPhiC;
	double cosPhiC4 = cosPhiC2 * cosPhiC2;
	double alphaC = deg2rad(azimuth);

	_phiP = deg2rad(standardParallel);
	_e = sqrt(ellipsoid.es());
	_a = ellipsoid.radius() * sqrt(1.0 - ellipsoid.es())
	  / (1.0 - ellipsoid.es() * sinPhiC2);
	_b = sqrt(1.0 + (ellipsoid.es() * cosPhiC4 / (1.0 - ellipsoid.es())));
	double gamma0 = asin(sinPhiC / _b);
	_t0 = tan(M_PI_4 + gamma0 / 2.0) * pow((1.0 + _e * sinPhiC) /
	  (1.0 - _e * sinPhiC), _e*_b / 2.0) / pow(tan(M_PI_4 + phiC/2.0), _b);
	_n = sin(_phiP);
	_r0 = scale * _a / tan(_phiP);
	_fe = falseEasting;
	_fn = falseNorthing;
	_cosAlphaC = cos(alphaC);
	_sinAlphaC = sin(alphaC);
	_lambda0 = deg2rad(longitudeOrigin);
}

PointD Krovak::ll2xy(const Coordinates &c) const
{
	double phi = deg2rad(c.lat());
	double lambda = deg2rad(c.lon());
	double eSinPhi = _e * sin(phi);
	double U = 2.0 * (atan(_t0 * pow(tan(phi/2.0 + M_PI_4), _b)
	  / pow((1.0 + eSinPhi) / (1.0 - eSinPhi), _e * _b/2.0)) - M_PI_4);
	double cosU = cos(U);
	double V = _b * (_lambda0 - lambda);
	double T = asin(_cosAlphaC * sin(U) + _sinAlphaC * cosU * cos(V));
	double D = asin(cosU * sin(V) / cos(T));
	double theta = _n * D;
	double r = _r0 * pow(tan(M_PI_4 + _phiP/2.0), _n)
	  / pow(tan(T/2.0 + M_PI_4), _n);

	return PointD(r * sin(theta) + _fe, r * cos(theta) + _fn);
}

Coordinates Krovak::xy2ll(const PointD &p) const
{
	double Xp = p.y() - _fn;
	double Yp = p.x() - _fe;
	double Xp2 = Xp * Xp;
	double Yp2 = Yp * Yp;
	double r = sqrt(Xp2 + Yp2);
	double theta = atan(Yp / Xp);
	double D = theta / sin(_phiP);
	double T = 2.0 * (atan(pow(_r0 / r, 1.0/_n) * tan(M_PI_4 + _phiP/2.0))
	  - M_PI_4);
	double U = asin(_cosAlphaC * sin(T) - _sinAlphaC * cos(T) * cos(D));
	double V = asin(cos(T) * sin(D) / cos(U));
	double phi = U;
	for (int i = 0; i < 3; i++)
		phi = 2.0 * (atan(pow(_t0, -1.0/_b) * pow(tan(U/2.0 + M_PI_4), 1.0/_b)
		  * pow((1.0 + _e * sin(phi))/(1.0 - _e * sin(phi)), _e/2.0)) - M_PI_4);

	return Coordinates(rad2deg(_lambda0 - V/_b), rad2deg(phi));
}

bool Krovak::operator==(const CT &ct) const
{
	const Krovak *other = dynamic_cast<const Krovak*>(&ct);
	return (other != 0 && _e == other->_e && _a == other->_a && _b == other->_b
	  && _t0 == other->_t0 && _r0 == other->_r0 && _phiP == other->_phiP
	  && _cosAlphaC == other->_cosAlphaC && _sinAlphaC == other->_sinAlphaC
	  && _lambda0 == other->_lambda0 && _fe == other->_fe && _fn == other->_fn);
}

bool KrovakNE::operator==(const CT &ct) const
{
	const KrovakNE *other = dynamic_cast<const KrovakNE*>(&ct);
	return (other != 0 && _k == other->_k);
}
