/*
 # This file is part of the Astrometry.net suite.
 # Licensed under a 3-clause BSD style license - see LICENSE
 */

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <regex.h>

#include "os-features.h"
#include "keywords.h"
#include "mathutil.h"
#include "starutil.h"
#include "errors.h"

#define POGSON 2.51188643150958
#define LOGP   0.92103403719762

#define InlineDefine InlineDefineC
#include "starutil.inc"
#undef InlineDefine

void radec_derivatives(double ra, double dec, double* dra, double* ddec) {
    double cosd = cos(deg2rad(dec));
    double cosra = cos(deg2rad(ra));
    double sinra = sin(deg2rad(ra));
    if (dra) {
        dra[0] = cosd * -sinra;
        dra[1] = cosd *  cosra;
        dra[2] = 0.0;
        normalize_3(dra);
    }
    if (ddec) {
        double sind = sin(deg2rad(dec));
        ddec[0] = -sind * cosra;
        ddec[1] = -sind * sinra;
        ddec[2] =  cosd;
        normalize_3(ddec);
    }
}

void radecrange2xyzrange(double ralo, double declo, double rahi, double dechi,
                         double* minxyz, double* maxxyz) {
    double minmult, maxmult;
    double uxlo, uxhi, uylo, uyhi;
    // Dec only affects z, and is monotonic (z = sin(dec))
    minxyz[2] = radec2z(0, declo);
    maxxyz[2] = radec2z(0, dechi);

    // min,max of cos(dec).  cos(dec) is concave down.
    minmult = MIN(cos(deg2rad(declo)), cos(deg2rad(dechi)));
    maxmult = MAX(cos(deg2rad(declo)), cos(deg2rad(dechi)));
    if (declo < 0 && dechi > 0)
        maxmult = 1.0;
    // unscaled x (ie, cos(ra))
    uxlo = MIN(cos(deg2rad(ralo)), cos(deg2rad(rahi)));
    if (ralo < 180 && rahi > 180)
        uxlo = -1.0;
    uxhi = MAX(cos(deg2rad(ralo)), cos(deg2rad(rahi)));
    minxyz[0] = MIN(uxlo * minmult, uxlo * maxmult);
    maxxyz[0] = MAX(uxhi * minmult, uxhi * maxmult);
    // unscaled y (ie, sin(ra))
    uylo = MIN(sin(deg2rad(ralo)), sin(deg2rad(rahi)));
    if (ralo < 270 && rahi > 270)
        uylo = -1.0;
    uyhi = MAX(sin(deg2rad(ralo)), sin(deg2rad(rahi)));
    if (ralo < 90 && rahi > 90)
        uyhi = -1.0;
    minxyz[1] = MIN(uylo * minmult, uylo * maxmult);
    maxxyz[1] = MAX(uyhi * minmult, uyhi * maxmult);
}


static int parse_hms_string(const char* str,
                            int* sign, int* term1, int* term2, double* term3) {
    anbool matched;
    regmatch_t matches[6];
    int nmatches = 6;
    regex_t re;
    regmatch_t* m;
    const char* s;

    const char* restr = 
        "^([+-])?([[:digit:]]{1,2}):"
        "([[:digit:]]{1,2}):"
        "([[:digit:]]*(\\.[[:digit:]]*)?)$";

    if (!str)
        return 1;

    if (regcomp(&re, restr, REG_EXTENDED)) {
        ERROR("Failed to compile H:M:S regex \"%s\"", restr);
        return -1;
    }
    matched = (regexec(&re, str, nmatches, matches, 0) == 0);
    regfree(&re);
    if (!matched)
        return 1;

    // sign
    m = matches + 1;
    s = str + m->rm_so;
    if (m->rm_so == -1 || s[0] == '+')
        *sign = 1;
    else
        *sign = -1;
    // hrs / deg
    m = matches + 2;
    s = str + m->rm_so;
    if (s[0] == '0')
        s++;
    *term1 = atoi(s);
    // Min
    m = matches + 3;
    s = str + m->rm_so;
    if (s[0] == '0')
        s++;
    *term2 = atoi(s);
    // Sec
    m = matches + 4;
    s = str + m->rm_so;
    *term3 = atof(s);
    return 0;
}

double atora(const char* str) {
    char* eptr;
    double ra;
    int sgn, hr, min;
    double sec;
    int rtn;

    if (!str) {
        //ERROR("Null string to atora()");
        return HUGE_VAL;
    }
    rtn = parse_hms_string(str, &sgn, &hr, &min, &sec);
    if (rtn == -1) {
        ERROR("Failed to run regex");
        return HUGE_VAL;
    }
    if (rtn == 0)
        return sgn * hms2ra(hr, min, sec);

    ra = strtod(str, &eptr);
    if (eptr == str)
        // no conversion
        return HUGE_VAL;
    return ra;
}

double atodec(const char* str) {
    char* eptr;
    double dec;
    int sgn, deg, min;
    double sec;
    int rtn;

    rtn = parse_hms_string(str, &sgn, &deg, &min, &sec);
    if (rtn == -1) {
        ERROR("Failed to run regex");
        return HUGE_VAL;
    }
    if (rtn == 0)
        return dms2dec(sgn, deg, min, sec);

    dec = strtod(str, &eptr);
    if (eptr == str)
        // no conversion
        return HUGE_VAL;
    return dec;
}

double mag2flux(double mag) {
    return pow(POGSON, -mag);
}

double flux2mag(double flux) {
    return -log(flux) * LOGP;
}

inline void xyzarr2radecarr(const double* xyz, double *radec) {
    xyz2radec(xyz[0], xyz[1], xyz[2], radec+0, radec+1);
}

double distsq_between_radecdeg(double ra1, double dec1,
                               double ra2, double dec2) {
    double xyz1[3];
    double xyz2[3];
    radecdeg2xyzarr(ra1, dec1, xyz1);
    radecdeg2xyzarr(ra2, dec2, xyz2);
    return distsq(xyz1, xyz2, 3);
}

double arcsec_between_radecdeg(double ra1, double dec1,
                               double ra2, double dec2) {
    return distsq2arcsec(distsq_between_radecdeg(ra1, dec1, ra2, dec2));
}

double deg_between_radecdeg(double ra1, double dec1, double ra2, double dec2) {
    return arcsec2deg(arcsec_between_radecdeg(ra1, dec1, ra2, dec2));
}

void project_equal_area(double x, double y, double z, double* projx, double* projy) {
    double Xp = x*sqrt(1./(1. + z));
    double Yp = y*sqrt(1./(1. + z));
    Xp = 0.5 * (1.0 + Xp);
    Yp = 0.5 * (1.0 + Yp);
    assert(Xp >= 0.0 && Xp <= 1.0);
    assert(Yp >= 0.0 && Yp <= 1.0);
    *projx = Xp;
    *projy = Yp;
}

void project_hammer_aitoff_x(double x, double y, double z, double* projx, double* projy) {
    double theta = atan(x/z);
    double r = sqrt(x*x+z*z);
    double zp, xp;
    /* Hammer-Aitoff projection with x-z plane compressed to purely +z side
     * of xz plane */
    if (z < 0) {
        if (x < 0) {
            theta = theta - M_PI;
        } else {
            theta = M_PI + theta;
        }
    }
    theta /= 2.0;
    zp = r*cos(theta);
    xp = r*sin(theta);
    assert(zp >= -0.01);
    project_equal_area(xp, y, zp, projx, projy);
}

/* makes a star object located uniformly at random within the limits given
 on the sphere */
void make_rand_star(double* star, double ramin, double ramax,
                    double decmin, double decmax)
{
    double decval, raval;
    if (ramin < 0.0)
        ramin = 0.0;
    if (ramax > (2*M_PI))
        ramax = 2 * M_PI;
    if (decmin < -M_PI / 2.0)
        decmin = -M_PI / 2.0;
    if (decmax > M_PI / 2.0)
        decmax = M_PI / 2.0;

    decval = asin(uniform_sample(sin(decmin), sin(decmax)));
    raval = uniform_sample(ramin, ramax);
    star[0] = radec2x(raval, decval);
    star[1] = radec2y(raval, decval);
    star[2] = radec2z(raval, decval);
}


// RA in degrees to Mercator X coordinate [0, 1).
inline double ra2mercx(double ra) {
    double mx = ra / 360.0;
    if (mx < 0.0 || mx > 1.0) {
        mx = fmod(mx, 1.0);
        if (mx < 0.0)
            mx += 1.0;
    }
    return mx;
}

// Dec in degrees to Mercator Y coordinate [0, 1).
inline double dec2mercy(double dec) {
    return 0.5 + (asinh(tan(deg2rad(dec))) / (2.0 * M_PI));
}

double hms2ra(int h, int m, double s) {
    return 15.0 * (h + ((m + (s / 60.0)) / 60.0));
}

double dms2dec(int sgn, int d, int m, double s) {
    return sgn * (d + ((m + (s / 60.0)) / 60.0));
}

// RA in degrees to H:M:S
inline void ra2hms(double ra, int* h, int* m, double* s) {
    double rem;
    ra = fmod(ra, 360.0);
    if (ra < 0.0)
        ra += 360.0;
    rem = ra / 15.0;
    *h = floor(rem);
    // remaining (fractional) hours
    rem -= *h;
    // -> minutes
    rem *= 60.0;
    *m = floor(rem);
    // remaining (fractional) minutes
    rem -= *m;
    // -> seconds
    rem *= 60.0;
    *s = rem;
}

// Dec in degrees to D:M:S
void dec2dms(double dec, int* sign, int* d, int* m, double* s) {
    double rem;
    double flr;
    *sign = (dec >= 0.0) ? 1 : -1;
    dec *= (*sign);
    flr = floor(dec);
    *d = flr;
    // remaining degrees:
    rem = dec - flr;
    // -> minutes
    rem *= 60.0;
    *m = floor(rem);
    // remaining (fractional) minutes
    rem -= *m;
    // -> seconds
    rem *= 60.0;
    *s = rem;
}

void ra2hmsstring(double ra, char* str) {
    int h, m;
    double s;
    int ss;
    int ds;
    ra2hms(ra, &h, &m, &s);

    // round to display to 3 decimal places
    ss = (int)floor(s);
    ds = (int)round((s - ss) * 1000.0);
    if (ds >= 1000) {
        ss++;
        ds -= 1000;
    }
    if (ss >= 60) {
        ss -= 60;
        m += 1;
    }
    if (m >= 60) {
        m -= 60;
        h += 1;
    }
    sprintf(str, "%02i:%02i:%02i.%03i", h, m, ss, ds);
}

void dec2dmsstring(double dec, char* str) {
    int sign, d, m;
    double s;
    int ss, ds;
    dec2dms(dec, &sign, &d, &m, &s);
    ss = (int)floor(s);
    ds = (int)round((s - ss) * 1000.0);
    if (ds >= 1000) {
        ss++;
        ds -= 1000;
    }
    if (ss >= 60) {
        ss -= 60;
        m += 1;
    }
    if (m >= 60) {
        m -= 60;
        d += 1;
    }
    sprintf(str, "%c%02i:%02i:%02i.%03i", (sign==1 ? '+':'-'), d, m, ss, ds);
}

