#ifndef GOOGLEPROJECTION_H
#define GOOGLEPROJECTION_H

#include <vector>
using std::vector;
#include <cmath>

#define max(a,b) (((a)>(b))?(a):(b))
#define min(a,b) (((a)<(b))?(a):(b))

struct ScreenPos
{
  int x,y;
  ScreenPos() { x=y=0; }
  ScreenPos(int x,int y) { this->x=x; this->y=y; }
};

struct EarthPoint
{
  double x,y; 
  EarthPoint() { x=y=0.0; }
  EarthPoint(double x,double y) { this->x=x; this->y=y; }
};

class GoogleProjection
{
   private:

   vector<double> Bc,Cc,zc,Ac;
   int levels;

  double minmax (double a,double b, double c)
  {
      a = max(a,b);
      a = min(a,c);
      return a;
  }

  public:
   GoogleProjection(int levels=18)
   {
       this->levels=levels;
        double c = 256;
    double e;
        for (int d=0; d<levels; d++) 
    {
            e = c/2;
            Bc.push_back(c/360.0);
            Cc.push_back(c/(2 * M_PI));
            zc.push_back(e);
            Ac.push_back(c);
            c *= 2;
    }
  }
                
  ScreenPos fromLLToPixel(double lon,double lat,int zoom)
  {
    double d = zc[zoom];
    double e = round(d + lon * Bc[zoom]);
    double f = minmax(sin((M_PI/180.0) * lat),-0.9999,0.9999);
    double g = round(d + 0.5*log((1+f)/(1-f))*-Cc[zoom]);
    return ScreenPos(e,g);
  }

  EarthPoint fromPixelToLL(int x,int y,int zoom)
  {
    double e = zc[zoom];
    double f = (x - e)/Bc[zoom];
    double g = (y - e)/-Cc[zoom];
    double h = (180.0/M_PI) * ( 2 * atan(exp(g)) - 0.5 * M_PI);
    return EarthPoint(f,h);
  }

  // convert to the zoom independent Google system; TBH I don't really
  // understand what it represents.... 
  static EarthPoint fromLLToGoog(double lon,double lat)
  {
    double a = log(tan((90+lat)*M_PI / 360))/(M_PI / 180);
     double custLat = a * 20037508.34 / 180;
    double custLon=lon;
     custLon = custLon * 20037508.34 / 180;
    return EarthPoint(custLon,custLat);
  }

  // other way round
  static EarthPoint fromGoogToLL(double x,double y)
  {
    double lat_deg,lon_deg;
    lat_deg = (y / 20037508.34) * 180;
         lon_deg = (x / 20037508.34) * 180;
         lat_deg = 180/M_PI * 
           (2 * atan(exp(lat_deg * M_PI / 180)) - M_PI / 2);
    return EarthPoint(lon_deg,lat_deg);
  }
};

#endif // GOOGLEPROJECTION_H