/******************************************************************************
 * $id$
 *
 * Project:  MapServer
 * Purpose:  Date/Time utility functions.
 * Author:   Steve Lime and the MapServer team.
 *
 ******************************************************************************
 * Copyright (c) 1996-2005 Regents of the University of Minnesota.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of this Software or works derived from this Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#define _GNU_SOURCE  /* glibc2 needs this for strptime() */
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#include "mapserver.h"
#include "maptime.h"
#include "maperror.h"
#include "mapthread.h"

typedef struct {
  char pattern[64];
  ms_regex_t *regex;
  char  format[32];
  char userformat[32];
  MS_TIME_RESOLUTION resolution;
} timeFormatObj;

#define MS_NUMTIMEFORMATS 13

timeFormatObj ms_timeFormats[MS_NUMTIMEFORMATS] = {
  {"^[0-9]{8}", NULL, "%Y%m%d","YYYYMMDD",TIME_RESOLUTION_DAY},
  {"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z", NULL, "%Y-%m-%dT%H:%M:%SZ","YYYY-MM-DDTHH:MM:SSZ",TIME_RESOLUTION_SECOND},
  {"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}", NULL, "%Y-%m-%dT%H:%M:%S", "YYYY-MM-DDTHH:MM:SS",TIME_RESOLUTION_SECOND},
  {"^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}", NULL, "%Y-%m-%d %H:%M:%S", "YYYY-MM-DD HH:MM:SS", TIME_RESOLUTION_SECOND},
  {"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}", NULL, "%Y-%m-%dT%H:%M", "YYYY-MM-DDTHH:MM",TIME_RESOLUTION_MINUTE},
  {"^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}", NULL, "%Y-%m-%d %H:%M", "YYYY-MM-DD HH:MM",TIME_RESOLUTION_MINUTE},
  {"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}", NULL, "%Y-%m-%dT%H", "YYYY-MM-DDTHH",TIME_RESOLUTION_HOUR},
  {"^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}", NULL, "%Y-%m-%d %H", "YYYY-MM-DD HH",TIME_RESOLUTION_HOUR},
  {"^[0-9]{4}-[0-9]{2}-[0-9]{2}", NULL, "%Y-%m-%d", "YYYY-MM-DD", TIME_RESOLUTION_DAY},
  {"^[0-9]{4}-[0-9]{2}", NULL, "%Y-%m", "YYYY-MM",TIME_RESOLUTION_MONTH},
  {"^[0-9]{4}", NULL, "%Y", "YYYY",TIME_RESOLUTION_YEAR},
  {"^T[0-9]{2}:[0-9]{2}:[0-9]{2}Z", NULL, "T%H:%M:%SZ", "THH:MM:SSZ",TIME_RESOLUTION_SECOND},
  {"^T[0-9]{2}:[0-9]{2}:[0-9]{2}", NULL, "T%H:%M:%S", "THH:MM:SS", TIME_RESOLUTION_SECOND}
};

int *ms_limited_pattern = NULL;
int ms_num_limited_pattern;

int ms_time_inited = 0;
int msTimeSetup() 
{
  if(!ms_time_inited) {
    msAcquireLock(TLOCK_TIME);
    if(!ms_time_inited) {
      int i;
      for(i=0;i<MS_NUMTIMEFORMATS;i++) {
        ms_timeFormats[i].regex = msSmallMalloc(sizeof(ms_regex_t));
        if(0!=ms_regcomp(ms_timeFormats[i].regex, ms_timeFormats[i].pattern, MS_REG_EXTENDED|MS_REG_NOSUB)) {
          msSetError(MS_REGEXERR, "Failed to compile expression (%s).", "msTimeSetup()", ms_timeFormats[i].pattern);
          return MS_FAILURE;
          /* TODO: free already init'd regexes */
        }
      }
      ms_limited_pattern = (int *)msSmallMalloc(sizeof(int)*MS_NUMTIMEFORMATS);
      ms_num_limited_pattern = 0;
      ms_time_inited = 1;
    }
    msReleaseLock(TLOCK_TIME);
  }
  return MS_SUCCESS;
}

void msTimeCleanup() 
{
  if(ms_time_inited) {
    int i;
    for(i=0;i<MS_NUMTIMEFORMATS;i++) {
      if(ms_timeFormats[i].regex) {
        ms_regfree(ms_timeFormats[i].regex);
        msFree(ms_timeFormats[i].regex);
        ms_timeFormats[i].regex = NULL;
      }
    }
    msFree(ms_limited_pattern);
    ms_time_inited = 0;
  }
}

void msTimeInit(struct tm *time) 
{
  time->tm_sec = 0; /* set all members to zero */
  time->tm_min = 0;
  time->tm_hour = 0;
  time->tm_mday = 0;
  time->tm_mon = 0;
  time->tm_year = 0;
  time->tm_wday = 0;
  time->tm_yday = 0;
  time->tm_isdst = 0;

  return;
}

static int compareIntVals(int a, int b)
{
  if(a<b) return -1;
  else if(a>b) return 1;
  else return 0;
}

int msDateCompare(struct tm *time1, struct tm *time2)
{
  int result;

  if((result = compareIntVals(time1->tm_year, time2->tm_year)) != 0)
    return result; /* not equal based on year */
  else if((result = compareIntVals(time1->tm_mon, time2->tm_mon)) != 0)
    return result; /* not equal based on month */
  else if((result = compareIntVals(time1->tm_mday, time2->tm_mday)) != 0)
    return result; /* not equal based on day of month */

  return(0); /* must be equal */
}

int msTimeCompare(struct tm *time1, struct tm *time2)
{
  int result;

  // fprintf(stderr, "in msTimeCompare()...\n");
  // fprintf(stderr, "time1: %d %d %d %d %d %d\n", time1->tm_year, time1->tm_mon, time1->tm_mday, time1->tm_hour, time1->tm_min, time1->tm_sec);
  // fprintf(stderr, "time2: %d %d %d %d %d %d\n", time2->tm_year, time2->tm_mon, time2->tm_mday, time2->tm_hour, time2->tm_min, time2->tm_sec);

  if((result = compareIntVals(time1->tm_year, time2->tm_year)) != 0)
    return result; /* not equal based on year */
  else if((result = compareIntVals(time1->tm_mon, time2->tm_mon)) != 0)
    return result; /* not equal based on month */
  else if((result = compareIntVals(time1->tm_mday, time2->tm_mday)) != 0)
    return result; /* not equal based on day of month */
  else if((result = compareIntVals(time1->tm_hour, time2->tm_hour)) != 0)
    return result; /* not equal based on hour */
  else if((result = compareIntVals(time1->tm_min, time2->tm_min)) != 0)
    return result; /* not equal based on minute */
  else if((result = compareIntVals(time1->tm_sec, time2->tm_sec)) != 0)
    return result; /* not equal based on second */

  return(0); /* must be equal */
}

#if defined(_WIN32) && !defined(__CYGWIN__)
#include <sys/timeb.h>
void msGettimeofday(struct mstimeval* tp, void* tzp)
{
  struct _timeb theTime;

  _ftime(&theTime);
  tp->tv_sec = theTime.time;
  tp->tv_usec = theTime.millitm * 1000;
}
#endif

#if defined(_WIN32) && !defined(__CYGWIN__)
/* we need to provide our own prototype on windows. */
char *strptime( const char *buf, const char *format, struct tm *timeptr );
#endif

char *msStrptime(const char *s, const char *format, struct tm *tm)
{
  memset(tm, 0, sizeof(struct tm));
  return strptime(s, format, tm);
}

/**
   return MS_TRUE if the time string matchs the timeformat.
   else return MS_FALSE.
 */
int msTimeMatchPattern(const char *timestring, const char *timeformat)
{
  int i =-1;
  if(msTimeSetup() != MS_SUCCESS) {
    return MS_FALSE;
  }

  /* match the pattern format first and then check if the time string  */
  /* matchs the pattern. If it is the case retrurn the MS_TRUE */
  for (i=0; i<MS_NUMTIMEFORMATS; i++) {
    if (strcasecmp(ms_timeFormats[i].userformat, timeformat) == 0)
      break;
  }

  if (i >= 0 && i < MS_NUMTIMEFORMATS) {
    int match = ms_regexec(ms_timeFormats[i].regex, timestring, 0, NULL, 0);
    if(match == 0)
      return MS_TRUE;
  }
  return MS_FALSE;
}


void msUnsetLimitedPatternToUse()
{
  msTimeSetup();
  ms_num_limited_pattern = 0;
}

void msSetLimitedPatternsToUse(const char *patternstring)
{
  int *limitedpatternindice = NULL;
  int numpatterns=0, i=0, j=0, ntmp=0;
  char **patterns = NULL;
  msTimeSetup();

  limitedpatternindice = (int *)msSmallMalloc(sizeof(int)*MS_NUMTIMEFORMATS);

  /* free previous setting */
  msUnsetLimitedPatternToUse();

  if (patternstring) {
    patterns = msStringSplit(patternstring, ',', &ntmp);
    if (patterns && ntmp >= 1) {

      for (i=0; i<ntmp; i++) {
        for (j=0; j<MS_NUMTIMEFORMATS; j++) {
          if (strcasecmp( ms_timeFormats[j].userformat, patterns[i]) ==0) {
            limitedpatternindice[numpatterns] = j;
            numpatterns++;
            break;
          }
        }
      }
    }
    msFreeCharArray(patterns, ntmp);
  }

  if (numpatterns > 0) {
    for (i=0; i<numpatterns; i++)
      ms_limited_pattern[i] = limitedpatternindice[i];

    ms_num_limited_pattern = numpatterns;
  }
  free (limitedpatternindice);
}

int msParseTime(const char *string, struct tm *tm)
{
  int i, indice = 0;
  int num_patterns = 0;
  
  if(MS_STRING_IS_NULL_OR_EMPTY(string)) return MS_FALSE; /* nothing to parse so bail */

  if(msTimeSetup() != MS_SUCCESS) {
    return MS_FALSE;
  }

  /* if limited patterns are set, use them, else use all the patterns defined */
  if (ms_num_limited_pattern > 0)
    num_patterns = ms_num_limited_pattern;
  else
    num_patterns = MS_NUMTIMEFORMATS;

  for(i=0; i<num_patterns; i++) {
    int match;
    if (ms_num_limited_pattern > 0)
      indice = ms_limited_pattern[i];
    else
      indice = i;

    match = ms_regexec(ms_timeFormats[indice].regex, string, 0,NULL, 0);
    /* test the expression against the string */
    if(match == 0) {
      /* match    */      
      msStrptime(string, ms_timeFormats[indice].format, tm);
      return(MS_TRUE);
    }
  }

  msSetError(MS_REGEXERR, "Unrecognized date or time format (%s).", "msParseTime()", string);
  return(MS_FALSE);
}

/**
 * Parse the time string and return the reslution
 */
int msTimeGetResolution(const char *timestring)
{
  int i=0;

  if (!timestring)
    return -1;

  for(i=0; i<MS_NUMTIMEFORMATS; i++) {
    ms_regex_t *regex = (ms_regex_t *) msSmallMalloc(sizeof(ms_regex_t));
    if(ms_regcomp(regex, ms_timeFormats[i].pattern,
                  MS_REG_EXTENDED|MS_REG_NOSUB) != 0) {
      msSetError(MS_REGEXERR, "Failed to compile expression (%s).", "msParseTime()", ms_timeFormats[i].pattern);
      msFree(regex);
      return -1;
    }
    /* test the expression against the string */
    if(ms_regexec(regex, timestring, 0, NULL, 0) == 0) {
      /* match    */
      ms_regfree(regex);
      msFree(regex);
      return ms_timeFormats[i].resolution;
    }
    ms_regfree(regex);
    msFree(regex);
  }

  return -1;
}


int _msValidateTime(const char *timestring,  const char *timeextent)
{
  int numelements, numextents, i, numranges;
  struct tm  tmtimestart, tmtimeend, tmstart, tmend;
  char **atimerange = NULL, **atimeelements= NULL, **atimeextents=NULL;

  if (!timestring || !timeextent)
    return MS_FALSE;

  if (strlen(timestring) <= 0 || strlen(timeextent) <= 0)
    return MS_FALSE;

  /* we first need to parse the timesting that is passed
     so that we can determine if it is a descrete time
     or a range */

  numelements = 0;
  atimeelements = msStringSplit (timestring, '/', &numelements);
  msTimeInit(&tmtimestart);
  msTimeInit(&tmtimeend);

  if (numelements == 1) { /*descrete time*/
    /*start end end times are the same*/
    if (msParseTime(timestring, &tmtimestart) != MS_TRUE) {
      msFreeCharArray(atimeelements, numelements);
      return  MS_FALSE;
    }
    if (msParseTime(timestring, &tmtimeend) != MS_TRUE) {
      msFreeCharArray(atimeelements, numelements);
      return  MS_FALSE;
    }
  } else if (numelements >=2) { /*range */
    if (msParseTime(atimeelements[0], &tmtimestart) != MS_TRUE) {
      msFreeCharArray(atimeelements, numelements);
      return  MS_FALSE;
    }
    if (msParseTime(atimeelements[1], &tmtimeend) != MS_TRUE) {
      msFreeCharArray(atimeelements, numelements);
      return  MS_FALSE;
    }
  }

  msFreeCharArray(atimeelements, numelements);


  /* Now parse the time extent. Extents can be
    -  one range (2004-09-21/2004-09-25/resolution)
    -  multiple rages 2004-09-21/2004-09-25/res1,2004-09-21/2004-09-25/res2
    - one value 2004-09-21
    - mutiple values 2004-09-21,2004-09-22,2004-09-23
  */

  numextents = 0;
  atimeextents = msStringSplit (timeextent, ',', &numextents);
  if (numextents <= 0) {
    msFreeCharArray(atimeextents, numextents);
    return MS_FALSE;
  }

  /*the time timestring should at be valid in one of the extents
    defined */

  for (i=0; i<numextents; i++) {
    /* build time structure for the extents */
    msTimeInit(&tmstart);
    msTimeInit(&tmend);

    numranges = 0;
    atimerange = msStringSplit (atimeextents[i], '/', &numranges);
    /* - one value 2004-09-21 */
    if (numranges == 1) {
      /*time tested can either be descrete or a range */

      if (msParseTime(atimerange[0], &tmstart) == MS_TRUE &&
          msParseTime(atimerange[0], &tmend) == MS_TRUE &&
          msTimeCompare(&tmstart, &tmtimestart) <= 0 &&
          msTimeCompare(&tmend, &tmtimeend) >= 0) {
        msFreeCharArray(atimerange, numranges);
        msFreeCharArray(atimeextents, numextents);
        return MS_TRUE;
      }
    }
    /*2004-09-21/2004-09-25/res1*/
    else if (numranges >= 2) {
      if (msParseTime(atimerange[0], &tmstart) == MS_TRUE &&
          msParseTime(atimerange[1], &tmend) == MS_TRUE &&
          msTimeCompare(&tmstart, &tmtimestart) <= 0 &&
          msTimeCompare(&tmend, &tmtimeend) >= 0) {
        msFreeCharArray(atimerange, numranges);
        msFreeCharArray(atimeextents, numextents);
        return MS_TRUE;
      }
    }
    msFreeCharArray(atimerange, numranges);


  }
  msFreeCharArray(atimeextents, numextents);
  return MS_FALSE;

}

int msValidateTimeValue(const char *timestring, const char *timeextent)
{
  char **atimes =  NULL;
  int i, numtimes=0;

  /* we need to validate the time passsed in the request */
  /* against the time extent defined */

  if (!timestring || !timeextent)
    return MS_FALSE;

  /* parse the time string. We support descrete times (eg 2004-09-21), */
  /* multiple times (2004-09-21, 2004-09-22, ...) */
  /* and range(s) (2004-09-21/2004-09-25, 2004-09-27/2004-09-29) */
  if (strstr(timestring, ",") == NULL && strstr(timestring, "/") == NULL) { /* discrete time */
    return _msValidateTime(timestring,  timeextent);
  } else {
    atimes = msStringSplit(timestring, ',', &numtimes);
    if (numtimes >=1) { /* multiple times */
      if (strstr(atimes[0], "/") == NULL) { /* multiple descrete times */
        for (i=0; i<numtimes; i++) {          
          if (_msValidateTime(atimes[i], timeextent) == MS_FALSE) {
            msFreeCharArray(atimes, numtimes);
            return MS_FALSE;
          }
        }
        msFreeCharArray(atimes, numtimes);
        return MS_TRUE;
      } else { /* multiple ranges */
        for (i=0; i<numtimes; i++) {          
          if (_msValidateTime(atimes[i], timeextent) == MS_FALSE) {
            msFreeCharArray(atimes, numtimes);
            return MS_FALSE;
          }
        }
        msFreeCharArray(atimes, numtimes);
        return MS_TRUE;
      }
    } else {
      msFreeCharArray(atimes,numtimes);
    }
  }
  return MS_FALSE;
}

