#define PJ_LIB__

#include <errno.h>
#include <math.h>

#include "proj.h"
#include "proj_internal.h"

PROJ_HEAD(igh, "Interrupted Goode Homolosine") "\n\tPCyl, Sph";

C_NAMESPACE PJ *pj_sinu(PJ *), *pj_moll(PJ *);

/* 40d 44' 11.8" [degrees] */
/*
static const double d4044118 = (40 + 44/60. + 11.8/3600.) * DEG_TO_RAD;
has been replaced by this define, to eliminate portability issue:
Initializer element not computable at load time
*/
#define d4044118 ((40 + 44/60. + 11.8/3600.) * DEG_TO_RAD)

static const double d10  =  10 * DEG_TO_RAD;
static const double d20  =  20 * DEG_TO_RAD;
static const double d30  =  30 * DEG_TO_RAD;
static const double d40  =  40 * DEG_TO_RAD;
static const double d50  =  50 * DEG_TO_RAD;
static const double d60  =  60 * DEG_TO_RAD;
static const double d80  =  80 * DEG_TO_RAD;
static const double d90  =  90 * DEG_TO_RAD;
static const double d100 = 100 * DEG_TO_RAD;
static const double d140 = 140 * DEG_TO_RAD;
static const double d160 = 160 * DEG_TO_RAD;
static const double d180 = 180 * DEG_TO_RAD;

static const double EPSLN = 1.e-10; /* allow a little 'slack' on zone edge positions */

namespace { // anonymous namespace
struct pj_opaque {
    struct PJconsts* pj[12]; \
    double dy0;
};
} // anonymous namespace


static PJ_XY igh_s_forward (PJ_LP lp, PJ *P) {           /* Spheroidal, forward */
    PJ_XY xy;
    struct pj_opaque *Q = static_cast<struct pj_opaque*>(P->opaque);
    int z;

    if (lp.phi >=  d4044118) {          /* 1|2 */
      z = (lp.lam <= -d40 ? 1: 2);
    }
    else if (lp.phi >=  0) {            /* 3|4 */
      z = (lp.lam <= -d40 ? 3: 4);
    }
    else if (lp.phi >= -d4044118) {     /* 5|6|7|8 */
           if (lp.lam <= -d100) z =  5; /* 5 */
      else if (lp.lam <=  -d20) z =  6; /* 6 */
      else if (lp.lam <=   d80) z =  7; /* 7 */
      else z = 8;                       /* 8 */
    }
    else {                              /* 9|10|11|12 */
           if (lp.lam <= -d100) z =  9; /* 9 */
      else if (lp.lam <=  -d20) z = 10; /* 10 */
      else if (lp.lam <=   d80) z = 11; /* 11 */
      else z = 12;                      /* 12 */
    }

    lp.lam -= Q->pj[z-1]->lam0;
    xy = Q->pj[z-1]->fwd(lp, Q->pj[z-1]);
    xy.x += Q->pj[z-1]->x0;
    xy.y += Q->pj[z-1]->y0;

    return xy;
}


static PJ_LP igh_s_inverse (PJ_XY xy, PJ *P) {           /* Spheroidal, inverse */
    PJ_LP lp = {0.0,0.0};
    struct pj_opaque *Q = static_cast<struct pj_opaque*>(P->opaque);
    const double y90 = Q->dy0 + sqrt(2.0); /* lt=90 corresponds to y=y0+sqrt(2) */

    int z = 0;
    if (xy.y > y90+EPSLN || xy.y < -y90+EPSLN) /* 0 */
      z = 0;
    else if (xy.y >=  d4044118)       /* 1|2 */
      z = (xy.x <= -d40? 1: 2);
    else if (xy.y >=  0)              /* 3|4 */
      z = (xy.x <= -d40? 3: 4);
    else if (xy.y >= -d4044118) {     /* 5|6|7|8 */
           if (xy.x <= -d100) z =  5; /* 5 */
      else if (xy.x <=  -d20) z =  6; /* 6 */
      else if (xy.x <=   d80) z =  7; /* 7 */
      else z = 8;                     /* 8 */
    }
    else {                            /* 9|10|11|12 */
           if (xy.x <= -d100) z =  9; /* 9 */
      else if (xy.x <=  -d20) z = 10; /* 10 */
      else if (xy.x <=   d80) z = 11; /* 11 */
      else z = 12;                    /* 12 */
    }

    if (z) {
        int ok = 0;

        xy.x -= Q->pj[z-1]->x0;
        xy.y -= Q->pj[z-1]->y0;
        lp = Q->pj[z-1]->inv(xy, Q->pj[z-1]);
        lp.lam += Q->pj[z-1]->lam0;

        switch (z) {
        case  1: ok = (lp.lam >= -d180-EPSLN && lp.lam <=  -d40+EPSLN) ||
                     ((lp.lam >=  -d40-EPSLN && lp.lam <=  -d10+EPSLN) &&
                      (lp.phi >=   d60-EPSLN && lp.phi <=   d90+EPSLN)); break;
        case  2: ok = (lp.lam >=  -d40-EPSLN && lp.lam <=  d180+EPSLN) ||
                     ((lp.lam >= -d180-EPSLN && lp.lam <= -d160+EPSLN) &&
                      (lp.phi >=   d50-EPSLN && lp.phi <=   d90+EPSLN)) ||
                     ((lp.lam >=  -d50-EPSLN && lp.lam <=  -d40+EPSLN) &&
                      (lp.phi >=   d60-EPSLN && lp.phi <=   d90+EPSLN)); break;
        case  3: ok = (lp.lam >= -d180-EPSLN && lp.lam <=  -d40+EPSLN); break;
        case  4: ok = (lp.lam >=  -d40-EPSLN && lp.lam <=  d180+EPSLN); break;
        case  5: ok = (lp.lam >= -d180-EPSLN && lp.lam <= -d100+EPSLN); break;
        case  6: ok = (lp.lam >= -d100-EPSLN && lp.lam <=  -d20+EPSLN); break;
        case  7: ok = (lp.lam >=  -d20-EPSLN && lp.lam <=   d80+EPSLN); break;
        case  8: ok = (lp.lam >=   d80-EPSLN && lp.lam <=  d180+EPSLN); break;
        case  9: ok = (lp.lam >= -d180-EPSLN && lp.lam <= -d100+EPSLN); break;
        case 10: ok = (lp.lam >= -d100-EPSLN && lp.lam <=  -d20+EPSLN); break;
        case 11: ok = (lp.lam >=  -d20-EPSLN && lp.lam <=   d80+EPSLN); break;
        case 12: ok = (lp.lam >=   d80-EPSLN && lp.lam <=  d180+EPSLN); break;
        }
      z = (!ok? 0: z); /* projectable? */
    }

    if (!z) lp.lam = HUGE_VAL;
    if (!z) lp.phi = HUGE_VAL;

    return lp;
}


static PJ *destructor (PJ *P, int errlev) {
    int i;
    if (nullptr==P)
        return nullptr;

    if (nullptr==P->opaque)
        return pj_default_destructor (P, errlev);

    for (i = 0; i < 12; ++i) {
        if (static_cast<struct pj_opaque*>(P->opaque)->pj[i])
            static_cast<struct pj_opaque*>(P->opaque)->pj[i]->destructor(static_cast<struct pj_opaque*>(P->opaque)->pj[i], errlev);
    }

    return pj_default_destructor(P, errlev);
}



/*
  Zones:

    -180            -40                       180
      +--------------+-------------------------+    Zones 1,2,9,10,11 & 12:
      |1             |2                        |      Mollweide projection
      |              |                         |
      +--------------+-------------------------+    Zones 3,4,5,6,7 & 8:
      |3             |4                        |      Sinusoidal projection
      |              |                         |
    0 +-------+------+-+-----------+-----------+
      |5      |6       |7          |8          |
      |       |        |           |           |
      +-------+--------+-----------+-----------+
      |9      |10      |11         |12         |
      |       |        |           |           |
      +-------+--------+-----------+-----------+
    -180    -100      -20         80          180
*/

#define SETUP(n, proj, x_0, y_0, lon_0) \
    if (!(Q->pj[n-1] = pj_##proj(nullptr))) return destructor(P, ENOMEM); \
    if (!(Q->pj[n-1] = pj_##proj(Q->pj[n-1]))) return destructor(P, ENOMEM); \
    Q->pj[n-1]->ctx = P->ctx; \
    Q->pj[n-1]->x0 = x_0; \
    Q->pj[n-1]->y0 = y_0; \
    Q->pj[n-1]->lam0 = lon_0;


PJ *PROJECTION(igh) {
    PJ_XY xy1, xy3;
    PJ_LP lp = { 0, d4044118 };
    struct pj_opaque *Q = static_cast<struct pj_opaque*>(pj_calloc (1, sizeof (struct pj_opaque)));
    if (nullptr==Q)
        return pj_default_destructor (P, ENOMEM);
    P->opaque = Q;


    /* sinusoidal zones */
    SETUP(3, sinu, -d100, 0, -d100);
    SETUP(4, sinu,   d30, 0,   d30);
    SETUP(5, sinu, -d160, 0, -d160);
    SETUP(6, sinu,  -d60, 0,  -d60);
    SETUP(7, sinu,   d20, 0,   d20);
    SETUP(8, sinu,  d140, 0,  d140);

    /* mollweide zones */
    SETUP(1, moll, -d100, 0, -d100);

    /* y0 ? */
    xy1 = Q->pj[0]->fwd(lp, Q->pj[0]); /* zone 1 */
    xy3 = Q->pj[2]->fwd(lp, Q->pj[2]); /* zone 3 */
    /* y0 + xy1.y = xy3.y for lt = 40d44'11.8" */
    Q->dy0 = xy3.y - xy1.y;

    Q->pj[0]->y0 = Q->dy0;

    /* mollweide zones (cont'd) */
    SETUP( 2, moll,   d30,  Q->dy0,   d30);
    SETUP( 9, moll, -d160, -Q->dy0, -d160);
    SETUP(10, moll,  -d60, -Q->dy0,  -d60);
    SETUP(11, moll,   d20, -Q->dy0,   d20);
    SETUP(12, moll,  d140, -Q->dy0,  d140);

    P->inv = igh_s_inverse;
    P->fwd = igh_s_forward;
    P->destructor = destructor;
    P->es = 0.;

    return P;
}
