/* persian calendar functions original code is by Kees Couprie
   http://www.geocities.com/couprie/calmath/      */

/* islamic conversion and moonphase calculation is taken from
 *
 * hdate
 *
 * Copyright (c) 1992 by Waleed A. Muhanna
 *
 * Permission for nonprofit use and redistribution of this software and
 * its documentation is hereby granted without fee, provided that the
 * above copyright notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting documentation.
 *
 * No representation is made about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 * Send any comments/suggestions/fixes/additions to:
 *		wmuhanna@magnus.acs.ohio-state.edu
 *
 */

#include "calendarfunc.h"
#include <stdio.h>



long persian_jdn(struct icaltimetype dt)
{
    const long PERSIAN_EPOCH = 1948321; /* The JDN of 1 Farvardin 1*/
    int epbase;
    long epyear;
    long mdays;
    long jdn;
    epbase = dt.year - 474;
    epyear = 474 + (epbase % 2820);
    if (dt.month <= 7)
        mdays = (dt.month - 1) * 31;
    else
        mdays = (dt.month - 1) * 30 + 6;
    jdn = dt.day + mdays ;
    jdn += (((epyear * 682) - 110) / 2816) ;
    jdn	+= (epyear - 1) * 365;
    jdn += (epbase / 2820) * 1029983 ;
    jdn += (PERSIAN_EPOCH - 1);
    return jdn;
}

double tmoonphase( long n, int nph)
{
    double jd, t, t2, t3, k, ma, sa, tf, xtra;
    k = n + nph/4.0;  t = k/1236.85;  t2 = t*t; t3 = t2*t;
    jd =  2415020.75933 + 29.53058868*k - 1.178e-4 * t2
	- 1.55e-7 * t3
	+ 3.3e-4 * sin (RPD * (166.56 +132.87*t -0.009173*t2));

        /* Sun's mean anomaly */
    sa =  RPD * (359.2242 + 29.10535608*k - 3.33e-5 * t2 - 3.47e-6 * t3);

    /* Moon's mean anomaly */
    ma =  RPD * (306.0253 + 385.81691806*k + 0.0107306*t2 +1.236e-5 *t3);

    /* Moon's argument of latitude */
    tf = RPD * 2.0 * (21.2964 + 390.67050646*k -0.0016528*t2
                      -2.39e-6 * t3);

    /* should reduce to interval 0-1.0 before calculating further */
    if (nph==0 || nph==2)
	/* Corrections for New and Full Moon */
	xtra = (0.1734 - 0.000393*t) * sin(sa)
	    +0.0021*sin(sa*2)
	    -0.4068*sin(ma) +0.0161*sin(2*ma) -0.0004*sin(3*ma)
	    +0.0104*sin(tf)
	    -0.0051*sin(sa+ma) -0.0074*sin(sa-ma)
	    +0.0004*sin(tf+sa) -0.0004*sin(tf-sa)
	    -0.0006*sin(tf+ma) +0.0010*sin(tf-ma)
	    +0.0005*sin(sa+ 2*ma);
    else if (nph==1 || nph==3) {
	xtra = (0.1721 - 0.0004*t) * sin(sa)
	    +0.0021*sin(sa*2)
	    -0.6280*sin(ma) +0.0089*sin(2*ma) -0.0004*sin(3*ma)
	    +0.0079*sin(tf)
	    -0.0119*sin(sa+ma) -0.0047*sin(sa-ma)
	    +0.0003*sin(tf+sa) -0.0004*sin(tf-sa)
	    -0.0006*sin(tf+ma) +0.0021*sin(tf-ma)
	    +0.0003*sin(sa+ 2*ma) +0.0004*sin(sa-2*ma)
	    -0.0003*sin(2*sa+ma);
	if (nph==1)
	    xtra = xtra +0.0028 -0.0004*cos(sa) +0.0003*cos(ma);
	else
	    xtra = xtra -0.0028 +0.0004*cos(sa) -0.0003*cos(ma);
    } else {
	printf("tmoonphase: illegal phase number\n");
	exit(1);
    }
    /* convert from Ephemeris Time (ET) to (approximate)
       Universal Time (UT) */
    jd += xtra - (0.41 +1.2053*t +0.4992*t2)/1440;
    return (jd);
}


double visible(long n,double * rjd)
{
    double jd;
    float tf;
    long d;

    jd = tmoonphase(n,0);
    *rjd = jd;
    d = jd;
    tf = (jd - d);
    if (tf<=0.5)  /*new moon starts in the afternoon */
	return(jd+1.0);
    /* new moon starts before noon */
    tf = (tf-0.5)*24 +TIMZ;  /* local time */
    if (tf>TIMDIF) return(jd+1.0);  /*age at sunset < min for visiblity*/
    return(jd);
}


struct icaltimetype jdn_islamic(long jdn)
{
    struct icaltimetype h;
    double mjd, rjd;
    long k, hm;

    /* obtain first approx. of how many new moons since the beginning
       of the year 1900 */
    h = jdn_civil(jdn);
    k = 0.6 + (h.year + ((int) (h.month - 0.5)) / 12.0 + h.day
	       / 365.0 - 1900) * 12.3685;
    do{
	mjd = visible(k--, &rjd);
    } while (mjd > jdn);
    k++;
    /*first of the month is the following day*/
    hm = k - 1048;
    h.year = 1405 + (hm / 12);

    h.month =  (hm % 12) +1;
    if (hm != 0 && h.month <= 0) {
	h.month += 12;
	h.year--;
    }
    if (h.year<=0)
	h.year--;
    h.day = jdn - mjd + 1;

    return h;
}


long islamic_jdn(struct icaltimetype dt)
{
        double jd, rjd;
        long k;

        k = dt.month + dt.year * 12 - NMONTHS; /* # of m since 1/1/1405 */
        jd = visible(k + 1048L, &rjd) + dt.day;
        return jd;
}




struct icaltimetype jdn_persian(long jdn)
{
    struct icaltimetype ret, h;
    int iYear, iMonth, iDay;
    int depoch;
    int cycle;
    int cyear;
    int ycycle;
    int aux1, aux2;
    int yday;
    h.day = 1;
    h.month = 1;
    h.year = 475;
    depoch = jdn - persian_jdn(h);
    cycle = depoch / 1029983;
    cyear = depoch % 1029983;
    if( cyear == 1029982)
        ycycle = 2820;
    else{
        aux1 = cyear / 366;
        aux2 = cyear % 366;
        ycycle = (((2134 * aux1) + (2816 * aux2) + 2815) / 1028522) + aux1 + 1;
    }
    iYear = ycycle + (2820 * cycle) + 474;
    if (iYear <= 0)
        iYear = iYear - 1;
    h.year = iYear;
    yday = (jdn - persian_jdn(h)) + 1;
    if(yday <= 186 )
        iMonth = Ceil((yday-1) / 31);
    else
        iMonth = Ceil((yday - 7) / 30);
    iMonth++;
    h.month = iMonth;
    iDay = (jdn - persian_jdn(h)) + 1;
    ret.day = iDay;
    ret.month = iMonth;
    ret.year = iYear;
    ret.is_date = 1;
    return ret;
}



int Ceil(float number)
{
    int ret;
    if(number>0)
	number += 0.5;
    ret = number;
    return ret;
}



long civil_jdn(struct icaltimetype dt)
{
    long jdn = ((1461 * (dt.year + 4800 + ((dt.month - 14) / 12))) / 4)
	+ ((367 * (dt.month - 2 - 12 * (((dt.month - 14) / 12)))) / 12)
	- ((3 * (((dt.year + 4900 + ((dt.month - 14) / 12)) / 100))) / 4)
	+ dt.day - 32075;
    return jdn;
}



struct icaltimetype jdn_civil(long jdn)
{
    long l, n, i, j;
    struct icaltimetype ret;
    int iday, imonth, iyear;
    l = jdn + 68569;
    n = ((4 * l) / 146097);
    l = l - ((146097 * n + 3) / 4);
    i = ((4000 * (l + 1)) / 1461001);
    l = l - ((1461 * i) / 4) + 31;
    j = ((80 * l) / 2447);
    iday = l - ((2447 * j) / 80);
    l = (j / 11);
    imonth = j + 2 - 12 * l;
    iyear = 100 * (n - 49) + i + l;
    ret.day = iday;
    ret.month = imonth;
    ret.year = iyear;
    ret.hour = 0;
    ret.minute = 0;
    ret.second = 0;
    return ret;
}



struct icaltimetype civil_persian(struct icaltimetype dt)
{
    return(jdn_persian(civil_jdn(dt)));
}


struct icaltimetype civil_islamic(struct icaltimetype dt)
{
    return(jdn_islamic(civil_jdn(dt)));
}



struct icaltimetype persian_civil(struct icaltimetype dt)
{
    return(jdn_civil(persian_jdn(dt)));
}


struct icaltimetype islamic_civil(struct icaltimetype dt)
{
    return(jdn_civil(islamic_jdn(dt)));
}

struct icaltimetype get_civil(struct icaltimetype dt, int calendar){
    if(calendar == 0)
	return dt;
    else if(calendar == 1)
	return persian_civil(dt);
    else if(calendar == 2)
	return islamic_civil(dt);

}


int isPersianLeap(int year)
{
    struct icaltimetype dt, dold;

    dt.year = year;
    dt.month = 12;
    dt.day = 30;
    dold = jdn_civil(2453108);
    dt = civil_persian(persian_civil(dt));
    if(dt.day == 1)
	return 0;
    return 1 ;
}

int isGregorianLeap(int year)
{
    if(year % 4 !=0)
	return 0;
    if(year % 100 == 0 && year % 400 != 0)
	return 0;
    return 1;
}

int days_in_month(int month, int year, int calendar)
{
    if(calendar == 0)
	return days_in_gregorian_month(month, year);
    else if(calendar == 1)
	return days_in_persian_month(month, year);
    else if(calendar == 2)
	return days_in_islamic_month(month, year);
}



int days_in_persian_month(int month, int year)
{
    if(month < 7)
	return 31;
    if(month < 12)
	return 30;
    if(isPersianLeap(year))
	return 30;
    return 29;
}


int days_in_islamic_month(int month, int year)
{
    struct icaltimetype dt, dold;
    month++;
    if(month == 13){
	month = 1;
	year++;
    }
    dt.year = year;
    dt.month = month;
    dt.day = 1;
    dt = jdn_islamic(islamic_jdn(dt) - 1);
    return dt.day;

}

int days_in_gregorian_month(int month, int year)
{
    switch(month){
    case 4:
    case 6:
    case 9:
    case 11:
	return 30;
    case 2:
	return 28 + isGregorianLeap(year);
    default:
	return 31;
    }
}

int day_of_week(struct icaltimetype dt)
{
    int jd;
    jd = civil_jdn(dt);
    jd --;
    return jd % 7;
}

int moon(struct icaltimetype h)
{
    int k, jd, i ,j,  jdn;
    long mjd = 0;
    jd = civil_jdn(h);
    if(datemoon[jd%200][1] == jd)
	return datemoon[jd%200][0];

    k = 1 + (h.year + ((int) (h.month - 0.5)) / 12.0 + h.day / 365.0 - 1900) * 12.3685;
    do {
	mjd = tmoonphase(k--, 0);
    } while (mjd > jd);

    k--;

    for(i = tmoonphase(k,0); i < tmoonphase(k+5,0 ); i++){
	datemoon[i%200][0] = 0;
	datemoon[i%200][1] = i;
    }

    for(j = 0; j < 6; j++){
	for(i = 0; i < 4; i++){
	    mjd = tmoonphase(k, i);
	    datemoon[mjd%200][0] = i+1;
	    datemoon[mjd%200][1] = mjd;
	}
	k++;
    }

    if(datemoon[jd%200][1] == jd)
	return datemoon[jd%200][0];
    return 0;
}


int daysComp(struct icaltimetype d1, struct icaltimetype d2){
    if(d1.year < d2.year)
	return 1; /* d1 < d2 */
    if(d1.year > d2.year)
	return 2; /* d1 > d2 */

    /* years are equal */
    if(d1.month < d2.month)
	return 1; /* d1 < d2 */
    if(d1.month > d2.month)
	return 2; /* d1 > d2 */

    /* months are equal */
    if(d1.day < d2.day)
	return 1; /* d1 < d2 */
    if(d1.day > d2.day)
	return 2; /* d1 > d2 */

    return 0; /* days are equal */
}


int daysEqual(struct icaltimetype d1, struct icaltimetype d2){
    if(daysComp(d1, d2) == 0)
	return 1;
    return 0;
}


int daysLater(struct icaltimetype d1, struct icaltimetype d2){
    if(daysComp(d1, d2) == 1)
	return 1;
    return 0;
}


int daysEarlier(struct icaltimetype d1, struct icaltimetype d2){
    if(daysComp(d1, d2) == 2)
	return 1;
    return 0;
}


int daysLaterEqual(struct icaltimetype d1, struct icaltimetype d2){
    if(daysComp(d1, d2) != 2)
	return 1;
    return 0;
}


int daysEarlierEqual(struct icaltimetype d1, struct icaltimetype d2){
    if(daysComp(d1, d2) != 1)
	return 1;
    return 0;
}