/*
   geometry.c
   Copyright (C) 2003 Antoine Drouin

  This file is part of paparazzi.

  paparazzi is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.

  paparazzi is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with paparazzi; see the file COPYING.  If not, write to
  the Free Software Foundation, 59 Temple Place - Suite 330,
  Boston, MA 02111-1307, USA.
*/

#include "geometry.h"

//#include <math.h>
#include "math_papabench.h"

const  struct ellipsoid ellipsoids[ ELLIPSOID_NB ] = {
  {-168.,  -60.,   320., 6378249.2, 0.0034075495234250643, 0.08248325676},  /* NTF   */
  {   0.,    0.,     0., 6378137.0, 0.0033528106647474805, 0.08181919106},  /* WGS84 */
  {-87.0,  -98., -121.0, 6378388.0, 0.003367003367003367,  0.08199188998},  /* ED50  */
  {  0.0, 125.0,  194.0, 6378067.6, 0.0033155460257,       0.08181919106}   /* NAD27 */
};
/* From http://www.tandt.be/wis/WiS/eqntf.html and http://www.ign.fr/MP/GEOD/geodesie/coordonnees.html */
const struct lambert lamberts[ LAMBERT_NB ] = {
  {                                           /* LAMBERTI   */
    &ellipsoids[ NTF ],
    RAD_OF_DEG( DECIMAL( 49., 30., 0. ) ), /* phi0    */
    0.991996665,                          /* lphi0   */
    5457616.674,                          /* r0      */
    RAD_OF_DEG( DECIMAL( 2., 20., 14.025 ) ), /* lambda0 */
    600000,                               /* x0      */
    200000,                               /* y0      */
    5657617,                              /* ys      */
    0.99987734                            /* k0      */
  },
  {                                           /* LAMBERTII  */
    &ellipsoids[ NTF ],
    RAD_OF_DEG( DECIMAL( 46., 48., 0. ) ), /* phi0    */
    0.921557361,                          /* lphi0   */
    5999695.77,                           /* r0      */
    RAD_OF_DEG( DECIMAL( 2., 20., 14.025 ) ), /* lambda0 */
    600000,                               /* x0      */
    2200000,                              /* y0      */
    6199696,                              /* ys      */
    0.99987742                            /* k0      */
  },
  {                                           /* LAMBERTIIE */
    &ellipsoids[ NTF ],
    RAD_OF_DEG( DECIMAL( 46., 48., 0. ) ), /* phi0    */
    0.921557361,                          /* lphi0   */
    5999695.77,                           /* r0      */
    RAD_OF_DEG( DECIMAL( 2., 20., 14.025 ) ), /* lambda0 */
    600000,                               /* x0      */
    2200000,                              /* y0      */
    8199696,                              /* ys      */
    0.99987742                            /* k0      */
  },
  {                                           /* LAMBERTIII */
    &ellipsoids[ NTF ],
    RAD_OF_DEG( DECIMAL( 44., 6., 0. ) ), /* phi0    */
    0.921557361,                          /* lphi0   */
    6591905.08,                           /* r0      */
    RAD_OF_DEG( DECIMAL( 2., 20., 14.025 ) ), /* lambda0 */
    600000,                               /* x0      */
    3200000,                              /* y0      */
    6791905,                              /* ys      */
    0.99987750                            /* k0      */
  },
  {                                           /* LAMBERTIV  */
    &ellipsoids[ NTF ],
    RAD_OF_DEG( DECIMAL( 42., 9., 54. ) ), /* phi0    */
    0.921557361,                          /* lphi0   */
    7053300.18,                           /* r0      */
    RAD_OF_DEG( DECIMAL( 2., 20., 14.025 ) ), /* lambda0 */
    234,                                  /* x0      */ /* FIXME mmmmmmm suspicious */
    4185861,                              /* y0      */
    7239162,                              /* ys      */
    0.99994471                            /* k0      */
  },
};

double distance_c2d( const struct double_c2d a, const struct double_c2d b )
{
  return sqrt( ( a.x - b.x ) * ( a.x - b.x ) + ( a.y - b.y ) * ( a.y - b.y ) );
}

struct double_c2d vector_c2d( const struct double_c2d a,
                              const struct double_c2d b )
{
  struct double_c2d v;
  v.x = b.x - a.x;
  v.y = b.y - a.y;
  return v;
}

struct double_c2d unit_vector_c2d( const struct double_c2d a,
                                   const struct double_c2d b )
{
  struct double_c2d v = vector_c2d( a, b );
  scale_c2d_s( &v, 1 / module_c2d( v ) );
  return v;
}

struct double_c2d normal_vector_c2d( const struct double_c2d u )
{
  struct double_c2d v;
  v.x = -u.y;
  v.y = u.x;
  return v;
}

double vect_prod_c2d( const struct double_c2d a, const struct double_c2d b )
{
  return a.x * b.y - a.y * b.x;
}

double scal_prod_c2d( const struct double_c2d a, const struct double_c2d b )
{
  return  a.x * b.x + a.y * b.y;
}

void rotate_c2d( struct double_c2d *a, double alpha )
{
  double tmp;
  tmp  = a->x * cos( alpha ) - a->y * sin( alpha );
  a->y = a->x * sin( alpha ) + a->y * cos( alpha );
  a->x = tmp;
}

double norme2_c2d( const struct double_c2d a )
{
  return a.x * a.x + a.y * a.y;
}

double module_c2d( const struct double_c2d a )
{
  return sqrt( norme2_c2d( a ) );
}

void   translate_c2d( struct double_c2d *a, const struct double_c2d v )
{
  add_c2d_s( a, v );
}

void add_c2d_s( struct double_c2d *a, const struct double_c2d b )
{
  a->x = a->x + b.x;
  a->y = a->y + b.y;
}

struct double_c2d add_c2d( struct double_c2d a, const struct double_c2d b )
{
  add_c2d_s( &a, b );
  return a;
}

void normalise_c2d_s( struct double_c2d *a )
{
  scale_c2d_s( a, 1 / module_c2d( *a ) );
}

struct double_c2d normalise_c2d( struct double_c2d a )
{
  normalise_c2d_s( &a );
  return a;
}

void minus_c2d_s( struct double_c2d *a, const struct double_c2d b )
{
  a->x = a->x - b.x;
  a->y = a->y - b.y;
}

struct double_c2d minus_c2d( struct double_c2d a, const struct double_c2d b )
{
  minus_c2d_s( &a, b );
  return a;
}

void scale_c2d_s( struct double_c2d *v, double factor )
{
  v->x = v->x * factor;
  v->y = v->y * factor;
}

struct double_c2d scale_c2d( struct double_c2d v, double factor )
{
  scale_c2d_s( &v, factor );
  return v;
}



#define MPI_PI(val)\
{\
  while (val> M_PI) val -= 2. * M_PI;\
  while (val<-M_PI) val += 2. * M_PI;\
}

double mpi_pi( double val )
{
  MPI_PI( val );
  return val;
};

struct double_c2d lin_interp_c2d( struct double_c2d a, double ta,
                                  struct double_c2d b, double tb, double t )
{
  double delta_t = tb - ta;
  struct double_c2d res;
  res.x = a.x + ( t - ta ) / delta_t *( b.x - a.x );
  res.y = a.y + ( t - ta ) / delta_t *( b.y - a.y );
  return res;
}

double wind_dir_from_angle_deg( double deg )
{
  double w = 180. + 90. - deg;
  while ( w > 360 ) w -= 360;
  return w;
}

double wind_dir_from_angle_rad( double rad )
{
  double w = M_PI + M_PI_2 - rad;
  while ( w > 2 * M_PI ) w -= ( 2 * M_PI );
  return w;
}

double heading_of_to_angle_deg( double angle )
{
  double a = 90. - angle + 360.;
  while ( a >= 180. ) a -= 360.;
  return a;
}

double heading_of_to_angle_rad( double angle )
{
  double two_pi = 2 * M_PI;
  double a = M_PI_2 - angle + two_pi;
  while ( a >= two_pi ) a -= two_pi;
  return a;
}



#define G 9.81
#define ACCEL_DOWN 0
double compute_phi( struct double_c2d curspeed, struct double_c2d last_speed,
                    double delta_t )
{
  double accel_norm, accel_tgt, phi;
  struct double_c2d accel = minus_c2d( curspeed, last_speed );
  //  printf("%lf %lf %lf %lf %lf\n", curspeed.x, curspeed.y, last_speed.x, last_speed.y, delta_t);
  scale_c2d_s( &accel, 1 / delta_t );
  accel_norm = vect_prod_c2d( accel, curspeed ) / module_c2d( curspeed );
  accel_tgt  = scal_prod_c2d( accel, curspeed ) / module_c2d( curspeed );
  phi = -atan2( accel_norm, G - ACCEL_DOWN );
  return phi;
}

double angle_c2d( struct double_c2d u, struct double_c2d v )
{
  double alpha1 = atan2( u.y, u.x );
  double alpha2 = atan2( v.y, v.x );
  double alpha = alpha2 - alpha1;
  MPI_PI( alpha );
  return alpha;
}

struct double_p2d p2d_of_c2d( const struct double_c2d c )
{
  struct double_p2d p;
  p.r = module_c2d( c );
  p.teta = atan2( c.y, c.x );
  return p;
}

struct double_c2d c2d_of_p2d( const struct double_p2d p )
{
  struct double_c2d c;
  c.x = p.r * cos( p.teta );
  c.y = p.r * sin( p.teta );
  return c;
}

struct double_p2d add_p2d( const struct double_p2d p1,
                           const struct double_p2d p2 )
{
  /*   p.r = sqrt(p1.r*p1.r + p2.r*p2.r + 2 * p1.r * p2.r * cos ( p1.teta - p2.teta )); */
  /*   p.teta = 0; */
  struct double_c2d c1 = c2d_of_p2d( p1 );
  struct double_c2d c2 = c2d_of_p2d( p2 );
  struct double_c2d c3 = add_c2d( c1, c2 );
  struct double_p2d p = p2d_of_c2d( c3 );
  return p;
}


struct double_p2d minus_p2d( const struct double_p2d p1,
                             const struct double_p2d p2 )
{
  struct double_c2d c1 = c2d_of_p2d( p1 );
  struct double_c2d c2 = c2d_of_p2d( p2 );
  struct double_c2d c3 = minus_c2d( c1, c2 );
  struct double_p2d p = p2d_of_c2d( c3 );
  return p;
}


struct double_c2d c2d_of_polar( double r, double teta )
{
  struct double_c2d res = {r * cos( teta ), r * sin( teta )};
  return res;
}

void  translate_c3d( struct double_c3d *a, const struct double_c3d v )
{
  a->x = a->x + v.x;
  a->y = a->y + v.y;
  a->z = a->z + v.z;
}

void  scale_c3d( struct double_c3d *v, double factor )
{
  v->x = v->x * factor;
  v->y = v->y * factor;
  v->z = v->z * factor;
}

static double inverse_latitude_isometrique( double lat, double e,
    double epsilon )
{
  double exp_l = exp( lat );
  double phi0 = 2. * atan( exp_l - M_PI_2 );
  double loop( double phi ) {
    double sin_phi = e * sin( phi );
    double new_phi = 2. * atan ( pow( ( 1. + sin_phi ) / ( 1. - sin_phi ),
                                      e / 2 ) * exp_l ) - M_PI_2;
    if ( fabs( phi - new_phi ) < epsilon ) return new_phi;
    return loop( new_phi );
  }
  return loop( phi0 );
}

static double lambert_n ( const enum type_lambert lmb )
{
  return sin( lamberts[ lmb ].phi0 );
}
static double lambert_c ( const enum type_lambert lmb )
{
  return lamberts[ lmb ].r0 * exp( lamberts[ lmb ].phi0 * lambert_n( lmb ) );
}

void lambert_of_e2d ( const enum type_lambert lmb,
                      const enum type_ellipsoid elps,
                      struct double_e2d e2d_pos, struct double_c2d *lmb_pos )
{
  double n, ll, r, gamma;
  e2d_of_e2d( NTF, elps, &e2d_pos );
  n  = lambert_n( lmb );
  ll = LATITUDE_ISOMETRIQUE( e2d_pos.lat, lamberts[ lmb ].ellipsoid->e );
  r  = lamberts[ lmb ].r0 * exp( lamberts[ lmb ].lphi0 * sin( lamberts[ lmb ].phi0 ) ) *
       exp( -ll * n );
  gamma = ( e2d_pos.lon - lamberts[ lmb ].lambda0 ) * n;
  lmb_pos->x = ( double )lamberts[ lmb ].x0 + r * sin( gamma );
  lmb_pos->y = ( double )lamberts[ lmb ].ys - r * cos( gamma );
}

void e2d_of_lambert ( const enum type_lambert lmb,
                      const enum type_ellipsoid elps,
                      struct double_c2d c_pos, struct double_e2d *e_pos )
{
  double dx = c_pos.x - lamberts[ lmb ].x0;
  double dy = c_pos.y - lamberts[ lmb ].ys;
  double r = sqrt( dx * dx + dy * dy );
  double gamma = atan2( dx, -dy );
  double lambda = lamberts[ lmb ].lambda0 + gamma / lambert_n( lmb );
  double ll = -1. / lambert_n( lmb ) * log( fabs( r / lambert_c( lmb ) ) );
  double phi = inverse_latitude_isometrique( ll, lamberts[ lmb ].ellipsoid->e,
               1e-11 );
  e_pos->lon = lambda;
  e_pos->lat = phi;
  e2d_of_e2d( elps, NTF, e_pos );
}

void e2d_of_e2d( const enum type_ellipsoid dest, const enum type_ellipsoid src,
                 struct double_e2d *e2d_pos )
{
  if ( dest != src ) {
    double sin_lat = sin( e2d_pos->lat );
    double cos_lat = cos( e2d_pos->lat );
    double sin_lon = sin( e2d_pos->lon );
    double cos_lon = cos( e2d_pos->lon );
    double d16 = E_SQUARE( ellipsoids[ src ].df );
    double d17 = E_SQUARE( ellipsoids[ dest ].df );
    double d18 = ellipsoids[ src ].a / sqrt ( 1.0 - d16 * sin_lat * sin_lat );
    double d20 = d18 * cos_lat * cos_lon;
    double d21 = d18 * cos_lat * sin_lon;
    double d22 = d18 * ( 1.0 - d16 ) * sin_lat;
    double d23 = d20 - ellipsoids[ dest ].dx + ellipsoids[ src ].dx;
    double d24 = d21 - ellipsoids[ dest ].dy + ellipsoids[ src ].dy;
    double d25 = d22 - ellipsoids[ dest ].dz + ellipsoids[ src ].dz;
    double d26 = sqrt ( d23 * d23 + d24 * d24 );
    double d27 = atan2( d25, d26 * ( 1.0 - ellipsoids[ dest ].df ) );
    double d28 = ellipsoids[ dest ].a * ( 1.0 - ellipsoids[ dest ].df );
    double d29 = E_PRIME_SQUARE( ellipsoids[ dest ].df );
    double d3 = atan2( ( d25 + d29 * d28 * sin( d27 ) * sin( d27 ) * sin( d27 ) ),
                       ( d26 - d17 * ellipsoids[ dest ].a * cos( d27 ) * cos( d27 ) * cos( d27 ) ) );
    double d4 = atan2( d24, d23 );
    e2d_pos->lon = d4;
    e2d_pos->lat = d3;
  }
}


/*
   converts 3d cartesian coordinate to 3d ellipsoidal
*/
void e3d_of_c3d( const enum type_ellipsoid elps, struct double_c3d c_pos,
                 struct double_e3d *e_pos )
{
#define WGS_84_SMA 6378137.00
#define WGS_84_SMB 6356752.31
#define E2 ((WGS_84_SMA*WGS_84_SMA-WGS_84_SMB*WGS_84_SMB)/(WGS_84_SMA*WGS_84_SMA))
#define PREC 0.000000000001
  double dist   = sqrt( c_pos.x * c_pos.x + c_pos.y * c_pos.y );
  double v, old_phi, new_phi = atan2( ( double )c_pos.z, dist * ( 1 - E2 ) );
  do {
    old_phi = new_phi;
    v = WGS_84_SMA / sqrt( 1 - ( E2 * sin( old_phi ) * sin( old_phi ) ) );
    new_phi = atan2( ( ( double )c_pos.z + E2 * v * sin ( old_phi ) ), dist );
  } while ( fabs( new_phi - old_phi ) > PREC );
  e_pos->lat = new_phi;
  e_pos->lon = atan2( c_pos.y, c_pos.x );
  e_pos->h = ( dist / cos ( new_phi ) ) - v;
}



