#include <stdio.h>
#include <stdint.h>
#include <math.h>		/* for floor() */

#include "cdi.h"
#include "timebase.h"

/* convert Julian date into year, months, day */
void decode_julday(int calendar,
		   int64_t julday, /* Julian day number to convert */
		   int *year,	   /* Gregorian year (out)         */
		   int *mon,	   /* Gregorian month (1-12) (out) */
		   int *day)       /* Gregorian day (1-31) (out)   */
{
  int64_t a = julday;

  double b = floor((a - 1867216.25)/36524.25);
  double c = a + b - floor(b/4) + 1525;

  if ( calendar == CALENDAR_STANDARD || calendar == CALENDAR_GREGORIAN )
    if ( a < 2299161 ) c = a + 1524;

  double d = floor((c - 122.1)/365.25);
  double e = floor(365.25*d);
  double f = floor((c - e)/30.6001);

  *day  = (int)(c - e - floor(30.6001*f));
  *mon  = (int)(f - 1 - 12*floor(f/14));
  *year = (int)(d - 4715 - floor((7 + *mon)/10));
}


/* convert year, month, day into Julian calendar day */
int64_t encode_julday(int calendar, int year, int month, int day)
{
  int iy;
  int im;
  int ib;

  if ( month <= 2 )
    {
      iy = year  - 1;
      im = month + 12;
    }
  else
    {
      iy = year;
      im = month;
    }


  if ( iy < 0 )
    ib = (int)((iy+1)/400) - (int)((iy+1)/100);
  else
    ib = (int)(iy/400) - (int)(iy/100);

  if ( calendar == CALENDAR_STANDARD || calendar == CALENDAR_GREGORIAN )
    {
      if ( year > 1582 || (year == 1582 && (month > 10 || (month == 10 && day >= 15))) )
	{
	  /*
	  ** 15th October 1582 AD or later
	  */
	}
      else
	{
	  /*
	  ** 4th October 1582 AD or earlier
	  */
	  ib = -2;
	}
    }

  int64_t julday = (int64_t) (floor(365.25*iy) + (int64_t)(30.6001*(im+1)) + ib + 1720996.5 + day + 0.5);

  return julday;
}


int64_t date_to_julday(int calendar, int64_t date)
{
  int year, month, day;
  cdiDecodeDate(date, &year, &month, &day);

  int64_t julday = encode_julday(calendar, year, month, day);

  return julday;
}


int64_t julday_to_date(int calendar, int64_t julday)
{
  int year, month, day;
  decode_julday(calendar, julday, &year, &month, &day);

  int64_t date = cdiEncodeDate(year, month, day);

  return date;
}


int time_to_sec(int time)
{
  int hour, minute, second;
  cdiDecodeTime(time, &hour, &minute, &second);

  int secofday = hour*3600 + minute*60 + second;

  return secofday;
}


int sec_to_time(int secofday)
{
  int hour   = secofday/3600;
  int minute = secofday/60 - hour*60;
  int second = secofday - hour*3600 - minute*60;

  int time = cdiEncodeTime(hour, minute, second);

  return time;
}

static
void adjust_seconds(int64_t *julday, int64_t *secofday)
{
  int64_t secperday = 86400;

  while ( *secofday >= secperday )
    {
      *secofday -= secperday;
      (*julday)++;
    }

  while ( *secofday <  0 )
    {
      *secofday += secperday;
      (*julday)--;
    }
}


void julday_add_seconds(int64_t seconds, int64_t *julday, int *secofday)
{
  int64_t sec_of_day = *secofday;

  sec_of_day += seconds;

  adjust_seconds(julday, &sec_of_day);

  *secofday = (int) sec_of_day;
}

/* add days and secs to julday/secofday */
void julday_add(int days, int secs, int64_t *julday, int *secofday)
{
  int64_t sec_of_day = *secofday;

  sec_of_day += secs;
  *julday    += days;

  adjust_seconds(julday, &sec_of_day);

  *secofday = (int) sec_of_day;
}

/* subtract julday1/secofday1 from julday2/secofday2 and returns the result in seconds */
double julday_sub(int64_t julday1, int secofday1, int64_t julday2, int secofday2, int64_t *days, int *secs)
{
  *days = julday2 - julday1;
  *secs = secofday2 - secofday1;

  int64_t sec_of_day = *secs;

  adjust_seconds(days, &sec_of_day);

  *secs = (int) sec_of_day;

  int64_t seconds = (int64_t)(*days) * (int64_t)86400 + sec_of_day;

  return (double)seconds;
}


void encode_juldaysec(int calendar, int year, int month, int day, int hour, int minute, int second, int64_t *julday, int *secofday)
{
  *julday = encode_julday(calendar, year, month, day);

  *secofday = (hour*60 + minute)*60 + second;
}


void decode_juldaysec(int calendar, int64_t julday, int secofday, int *year, int *month, int *day, int *hour, int *minute, int *second)
{
  decode_julday(calendar, julday, year, month, day);

  *hour   = secofday/3600;
  *minute = secofday/60 - *hour*60;
  *second = secofday - *hour*3600 - *minute*60;
}


#ifdef TEST
int main(void)
{
  int nmin;
  int64_t vdate0, vdate;
  int vtime0, vtime;
  int ijulinc;
  int i, j = 0;
  int year, mon, day, hour, minute, second;
  int64_t julday;
  int secofday;
  int calendar = CALENDAR_STANDARD;

  /* 1 - Check valid range of years */

  nmin = 11000;
  vdate0 = -80001201;
  vtime0 = 120500;

  printf("start time: %8d %4d\n", vdate0, vtime0);

  for ( i = 0; i < nmin; i++ )
    {
      cdiDecodeDate(vdate0, &year, &mon, &day);
      cdiDecodeTime(vtime0, &hour, &minute, &second);

      julday  = date_to_julday(calendar, vdate0);
      secofday = time_to_sec(vtime0);

      vdate = julday_to_date(calendar, julday);
      vtime = sec_to_time(secofday);

      if ( vdate0 != vdate || vtime0 != vtime )
	printf("%4d %8d %4d %8d %4d %9d %9d\n",
	       ++j, vdate0, vtime0, vdate, vtime, julday, secofday);

      year++;
      vdate0 = cdiEncodeDate(year, mon, day);
      vtime0 = cdiEncodeTime(hour, minute, second);
    }

  printf("stop time: %8d %4d\n", vdate0, vtime0);

  /* 2 - Check time increment of one minute */

  nmin = 120000;
  ijulinc = 60;
  vdate0 = 20001201;
  vtime0 = 0;

  printf("start time: %8d %4d\n", vdate0, vtime0);

  julday = date_to_julday(calendar, vdate0);
  secofday = time_to_sec(vtime0);
  for ( i = 0; i < nmin; i++ )
    {
      cdiDecodeDate(vdate0, &year, &mon, &day);
      cdiDecodeTime(vtime0, &hour, &minute, &second);

      if ( ++minute >= 60 )
	{
	  minute = 0;
	  if ( ++hour >= 24 )
	    {
	      hour = 0;
	      if ( ++day >= 32 )
		{
		  day = 1;
		  if ( ++mon >= 13 )
		    {
		      mon = 1;
		      year++;
		    }
		}
	    }
	}

      vdate0 = cdiEncodeDate(year, mon, day);
      vtime0 = cdiEncodeTime(hour, minute, second);

      julday_add_seconds(ijulinc, &julday, &secofday);

      vdate = julday_to_date(calendar, julday);
      vtime = sec_to_time(secofday);
      if ( vdate0 != vdate || vtime0 != vtime )
	printf("%4d %8d %4d %8d %4d %9d %9d\n",
	       ++j, vdate0, vtime0, vdate, vtime, julday, secofday);
    }

  printf("stop time: %8d %4d\n", vdate0, vtime0);

  return (0);
}
#endif


#ifdef TEST2
int main(void)
{
  int i;
  int julday, secofday;
  int year, month, day, hour, minute, second;
  int value = 30;
  int factor = 86400;
  int calendar = CALENDAR_STANDARD;

  year=1979; month=1; day=15; hour=12; minute=30, second=17;

  printf("%d/%02d/%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second);

  encode_juldaysec(calendar, year, month, day, hour, minute, second, &julday, &secofday);

  decode_juldaysec(calendar, julday, secofday, &year, &month, &day, &hour, &minute, &second);
  printf("%d/%02d/%02d %02d:%02d:%02d   %d %d\n", year, month, day, hour, minute, second, julday, secofday);

  for ( i = 0; i < 420; i++ )
    {

      decode_juldaysec(calendar, julday, secofday, &year, &month, &day, &hour, &minute, &second);
      printf("%2d %d/%02d/%02d %02d:%02d:%02d\n", i, year, month, day, hour, minute, second);
      julday_add_seconds(value*factor, &julday, &secofday);
    }

  return (0);
}
#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
