/*
   File:        timestring.c
   Author(s):   Andrew Moore
   Created:     Wed Oct 22 14:11:15 EDT 2003
   Description: Parse and manipulate times
   Copyright (c) Carnegie Mellon University
*/

#include "timestring.h"
#include "amma.h"
#include "am_string.h"

/* Glossary:

    date    -   A structure for representing dates (year,month,day)
    daycode -   An integer index for dates such that 
                  daycode(tomorrow) = 1 + daycode(today)
               
                The date API is in am_time.h

    hourmin -   A structure for representing times (hour,min)
    hmcode  -   An integer index for hourmins such that 
                  hmcode(next minute) = 1 + hmcode(this minute)
                and hmcode(00:00) = 0 and hmcode(23:59) = 24 * 60 - 1

                The hourmin API is here in timestring.h

    dateandtime - A structure for representing date/time combination
                  (year,month,day,hour,min)
    mincode     - An integer index for minutes of dateandtime such that  
                  mincode(next minute) = 1 + mincode(this minute)
                  and which codes the number of minutes since a fixed time
                  in the early 19th century, so

                   mincode(00:00 tomorrow) = 1 + mincode(23:59 today)
*/


hourmin *mk_hourmin(int hour,int minute)
{
  hourmin *x = AM_MALLOC(hourmin);

  my_assert(hour >= 0);
  my_assert(hour < 24);
  my_assert(minute >= 0);
  my_assert(minute < 60);

  x -> hour = hour;
  x -> minute = minute;

  return x;
}

void free_hourmin(hourmin *x)
{
  AM_FREE(x,hourmin);
}

void fprintf_hourmin(FILE *s,char *m1,hourmin *x,char *m2)
{
  char *buff;

  buff = mk_printf("%s -> hour",m1);
  fprintf_int(s,buff,x->hour,m2);
  free_string(buff);

  buff = mk_printf("%s -> minute",m1);
  fprintf_int(s,buff,x->minute,m2);
  free_string(buff);
}

void phourmin(hourmin *x)
{
  fprintf_hourmin(stdout,"hourmin",x,"\n");
}

hourmin *mk_copy_hourmin(hourmin *old)
{
  hourmin *nu = AM_MALLOC(hourmin);

  nu -> hour = old -> hour;
  nu -> minute = old -> minute;

  return nu;
}

int int_from_string_helper(char *string,char *name,int maxval,char **r_errmess)
{
  int result = -77;

  my_assert(*r_errmess == NULL);

  if ( !is_a_number(string) )
    *r_errmess = mk_printf("%s should be %s but cannot be parsed as a "
			   "number between 0 and %d",
			   string,maxval-1);
  else
  {
    result = atoi(string);
    if ( result < 0 || result >= maxval )
      *r_errmess = mk_printf("%s should be %s but is not a "
			     "number between 0 and %d",
			     result,maxval-1);
  }

  if ( *r_errmess != NULL )
    result = -77;

  return result;
}


/* Parses a time. A time must be 2 components separated by 
   "ignore characters" where "ignore characters" are :-/. and space

   Times must be in 24-hour clock. If needed someone should update this
   function to survive times such as 3.45pm

   If a parse error returns NULL, and MAKES and RETURNS an error message
   (which must later be free_string()'d in *r_errmess.

   If happy, returns hourmin, and sets *r_errmess to NULL.
*/
hourmin *mk_hourmin_from_string(char *s,char **r_errmess)
{
  string_array *sa = mk_broken_string_using_seppers(s," -:/.");
  int size = string_array_size(sa);
  hourmin *hm = NULL;

  *r_errmess = NULL;

  if ( size != 2 )
    *r_errmess = mk_printf("The string \"%s\" can't be parsed as a time",s);
  else 
  {
    char *s0 = (size>0) ? string_array_ref(sa,0) : NULL;
    char *s1 = (size>1) ? string_array_ref(sa,1) : NULL;
    int hour = -77;
    int minute = -77;

    if ( *r_errmess == NULL )
      hour = int_from_string_helper(s0,"an hour",24,r_errmess);

    if ( *r_errmess == NULL )
      minute = int_from_string_helper(s1,"a minute",60,r_errmess);

    if ( *r_errmess == NULL )
      hm = mk_hourmin(hour,minute);
  }

  free_string_array(sa);

  my_assert(hm == NULL || *r_errmess == NULL);
  my_assert(hm != NULL || *r_errmess != NULL);

  return hm;
}

int hourmin_hour(hourmin *hm)
{
  return hm->hour;
}

int hourmin_minute(hourmin *hm)
{
  return hm->minute;
}

char *mk_string_from_hourmin(hourmin *hm)
{
  return mk_printf("%02d:%02d",hourmin_hour(hm),hourmin_minute(hm));
}
	
char *mk_string_from_hmcode(int hmcode)
{
  hourmin *hm = mk_hourmin_from_hmcode(hmcode);
  char *s = mk_string_from_hourmin(hm);
  free_hourmin(hm);
  return s;
}

int string_to_hmcode(char *string,char **r_errmess)
{
  hourmin *hm = mk_hourmin_from_string(string,r_errmess);
  int hmcode = (hm == NULL) ? -777 : hourmin_to_hmcode(hm);
  free_hourmin(hm);
  return hmcode;
}

string_array *mk_string_array_from_hmcodes(ivec *hmcodes)
{
  int size = ivec_size(hmcodes);
  string_array *result = mk_string_array(size);
  int i;

  for ( i = 0 ; i < size ; i++ )
  {
    char *string = mk_string_from_hmcode(ivec_ref(hmcodes,i));
    string_array_set(result,i,string);
    free_string(string);
  }

  return result;
}
	   
int hourmin_to_hmcode(hourmin *hm)
{
  return 60 * hourmin_hour(hm) + hourmin_minute(hm);
}

hourmin *mk_hourmin_from_hmcode(int hmcode)
{
  return mk_hourmin(hmcode/60,hmcode % 60);
}

dateandtime *mk_dateandtime(date *d,hourmin *hm)
{
  dateandtime *x = AM_MALLOC(dateandtime);

  x -> d = mk_copy_date(d);
  x -> hm = mk_copy_hourmin(hm);

  return x;
}

void free_dateandtime(dateandtime *x)
{
  free_date(x->d);
  free_hourmin(x->hm);

  AM_FREE(x,dateandtime);
}

void fprintf_dateandtime(FILE *s,char *m1,dateandtime *x,char *m2)
{
  char *buff;

  buff = mk_printf("%s -> d",m1);
  fprintf_date(s,buff,x->d,m2);
  free_string(buff);

  buff = mk_printf("%s -> hm",m1);
  fprintf_hourmin(s,buff,x->hm,m2);
  free_string(buff);
}

void pdateandtime(dateandtime *x)
{
  fprintf_dateandtime(stdout,"dateandtime",x,"\n");
}

dateandtime *mk_copy_dateandtime(dateandtime *old)
{
  dateandtime *nu = AM_MALLOC(dateandtime);

  nu -> d = mk_copy_date(old->d);
  nu -> hm = mk_copy_hourmin(old->hm);

  return nu;
}

/* Parses a date/time. The date must be parseable according to the
   rules described for 

   mk_date_from_string in am_time.h

   The time must be parseable using the rules described for

   mk_hourmin_from_string in timestring.h

   If a parse error returns NULL, and MAKES and RETURNS an error message
   (which must later be free_string()'d in *r_errmess.

   If happy, returns dateandtime, and sets *r_errmess to NULL.
*/
dateandtime *mk_dateandtime_from_strings(char *date_string,char *time_string,
					 char **r_errmess)
{
  char *date_errmess = NULL;
  char *time_errmess = NULL;
  date *d = mk_date_from_string(date_string,&date_errmess);
  hourmin *mod = mk_hourmin_from_string(time_string,&time_errmess);
  dateandtime *dt = NULL;

  if ( date_errmess != NULL || time_errmess != NULL )
  {
    if ( date_errmess == NULL ) date_errmess = mk_printf("The date is fine.");
    if ( time_errmess == NULL ) time_errmess = mk_printf("The time is fine.");
    *r_errmess = mk_printf("Problem parsing ( date , time ) = ( %s , %s ):\n"
			   "%s\n%s\n",date_string,time_string,
			   date_errmess,time_errmess);
  }
  else
    dt = mk_dateandtime(d,mod);

  if ( date_errmess != NULL ) free_string(date_errmess);
  if ( time_errmess != NULL ) free_string(time_errmess);
  if ( d != NULL ) free_date(d);
  if ( mod != NULL ) free_hourmin(mod);

  my_assert(dt == NULL || *r_errmess == NULL);
  my_assert(dt != NULL || *r_errmess != NULL);

  return dt;
}

/* Just like mk_dateandtime_from_strings except my_error if there's
   a problem (with suitable error message) */
dateandtime *mk_dateandtime_from_strings_simple(char *date_string,
						char *time_string)
{
  char *errmess = NULL;
  dateandtime *dt = mk_dateandtime_from_strings(date_string,time_string,
						&errmess);
  if ( dt == NULL ) my_error(errmess);

  return dt;
}

date *dateandtime_date(dateandtime *dt)
{
  return dt->d;
}

hourmin *dateandtime_hourmin(dateandtime *dt)
{
  return dt->hm;
}

char *mk_string_from_dateandtime_date(dateandtime *dt)
{
  return mk_string_from_date(dateandtime_date(dt));
}

char *mk_string_from_dateandtime_hourmin(dateandtime *dt)
{
  return mk_string_from_hourmin(dateandtime_hourmin(dt));
}

char *mk_string_from_dateandtime(dateandtime *dt)
{
  char *date_string = mk_string_from_dateandtime_date(dt);
  char *hourmin_string = mk_string_from_dateandtime_hourmin(dt);
  int day_of_week = dateandtime_to_day_of_week(dt);
  char *result = mk_printf("%s %s %s",day_of_week_to_name(day_of_week),
			   date_string,hourmin_string);

  free_string(date_string);
  free_string(hourmin_string);

  return result;
}

#define MINUTES_PER_DAY (24 * 60)

int daycode_hmcode_to_mincode(int daycode,int hmcode)
{
  return daycode * MINUTES_PER_DAY + hmcode;
}

int dateandtime_to_mincode(dateandtime *dt)
{
  int daycode = date_to_daycode(dateandtime_date(dt));
  int hmcode = hourmin_to_hmcode(dateandtime_hourmin(dt));

  return daycode_hmcode_to_mincode(daycode,hmcode);
}

int mincode_to_daycode(int mincode)
{
  return mincode / MINUTES_PER_DAY;
}

int mincode_to_hmcode(int mincode)
{
  return mincode % MINUTES_PER_DAY;
}

dateandtime *mk_dateandtime_from_mincode(int mincode)
{
  int daycode = mincode_to_daycode(mincode);
  int hmcode = mincode_to_hmcode(mincode);
  date *d = mk_date_from_daycode(daycode);
  hourmin *hm = mk_hourmin_from_hmcode(hmcode);
  dateandtime *dt = mk_dateandtime(d,hm);

  free_date(d);
  free_hourmin(hm);

  return dt;
}

int strings_to_mincode(char *date_string,char *time_string,char **r_errmess)
{
  dateandtime *dt = mk_dateandtime_from_strings(date_string,time_string,
						r_errmess);
  int mincode = (dt == NULL) ? -777 : dateandtime_to_mincode(dt);

  if ( dt != NULL ) free_dateandtime(dt);

  return mincode;
}

ivec *mk_daycodes_from_mincodes(ivec *mincodes)
{
  int size = ivec_size(mincodes);
  ivec *daycodes = mk_ivec(size);
  int i;

  for ( i = 0 ; i < size ; i++ )
    ivec_set(daycodes,i,mincode_to_daycode(ivec_ref(mincodes,i)));

  return daycodes;
}

ivec *mk_hmcodes_from_mincodes(ivec *mincodes)
{
  int size = ivec_size(mincodes);
  ivec *hmcodes = mk_ivec(size);
  int i;

  for ( i = 0 ; i < size ; i++ )
    ivec_set(hmcodes,i,mincode_to_hmcode(ivec_ref(mincodes,i)));

  return hmcodes;
}

void timestring_main(int argc,char *argv[])
{
  char *date_string = string_from_args("date",argc,argv,"10/22/2004");
  char *time_string = string_from_args("time",argc,argv,"16:21");
  dateandtime *tt = mk_dateandtime_from_strings_simple(date_string,time_string);
  int mincode = dateandtime_to_mincode(tt);
  char *now_string = mk_string_from_dateandtime(tt);
  dateandtime *later = mk_dateandtime_from_mincode(mincode + 25 * 60);
  char *later_string = mk_string_from_dateandtime(later);

  printf("Time and Date now = %s\n",now_string);
  printf("Time and Date 25 hours later = %s\n",later_string);

  free_dateandtime(tt);
  free_string(now_string);
  free_string(later_string);
  free_dateandtime(later);
}

dateandtime *mk_dateandtime_from_args(char *key,int argc,char *argv[],
				      char *default_date_string,
				      char *default_time_string)
{
  char *date_key = mk_printf("%s_date",key);
  char *time_key = mk_printf("%s_time",key);
  char *date_string = string_from_args(date_key,argc,argv,default_date_string);
  char *time_string = string_from_args(time_key,argc,argv,default_time_string);
  
  return mk_dateandtime_from_strings_simple(date_string,time_string);
}

ivec *mk_mincodes_from_daycodes_and_hmcodes(ivec *daycodes,ivec *hmcodes)
{
  int size = ivec_size(daycodes);
  ivec *mincodes = mk_ivec(size);
  int i;

  my_assert(size == ivec_size(hmcodes));

  for ( i = 0 ; i < size ; i++ )
  {
    int daycode = ivec_ref(daycodes,i);
    int hmcode = ivec_ref(hmcodes,i);
    int mincode = daycode_hmcode_to_mincode(daycode,hmcode);
    ivec_set(mincodes,i,mincode);
  }

  return mincodes;
}

int dateandtime_to_day_of_week(dateandtime *dt)
{
  return date_to_day_of_week(dateandtime_date(dt));
}

char *mk_string_from_mincode(int mincode)
{
  dateandtime *dt = mk_dateandtime_from_mincode(mincode);
  char *s = mk_string_from_dateandtime(dt);
  free_dateandtime(dt);
  return s;
}

string_array *mk_string_array_from_mincodes(ivec *mincodes)
{
  int size = ivec_size(mincodes);
  int i;
  string_array *sa = mk_string_array(size);

  for ( i = 0 ; i < size ; i++ )
  {
    int mincode = ivec_ref(mincodes,i);
    char *s = mk_string_from_mincode(mincode);
    string_array_set(sa,i,s);
    free_string(s);
  }

  return sa;
}


