/*
  This file is part of the Astrometry.net suite.
  Copyright 2006, 2007 Dustin Lang, Keir Mierle and Sam Roweis.

  The Astrometry.net suite 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, version 2.

  The Astrometry.net suite 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 the Astrometry.net suite ; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
*/

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

#include "mathutil.h"
#include "starutil.h"
#include "keywords.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:]]{2}):"
        "([[:digit:]]{2}):"
        "([[:digit:]]*(\\.[[:digit:]]*)?)$";
    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;

    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);
}

