/* strptime.c (emx+gcc) -- Copyright (c) 1994-1998 by Eberhard Mattes */

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <emx/locale.h>

#define RECURSE(FMT)  parse_fmt (s, FMT, tm)
#define NUMBER(DST,MIN,MAX,ADD)  parse_number (s, DST, MIN, MAX, ADD)
#define STRING(STR)  parse_string (s, STR)
#define TABLE(DST,TAB,N)  parse_table (s, DST, TAB, N)

static const unsigned char *parse_number (const unsigned char *s, int *dst,
                                          int min, int max, int add)
{
  int n;

  while (*s != 0 && isspace (*s))
    ++s;
  if (*s == 0 || !isdigit (*s))
    return NULL;
  n = 0;
  while (isdigit (*s))
    n = n * 10 + (*s++ - '0');  /* TODO: check for overflow */
  if (n < min || (max >= 0 && n > max))
    return NULL;
  *dst = n + add;
  return s;
}


static const unsigned char *parse_string (const unsigned char *s,
                                          const char *str)
{
  size_t len;

  while (*s != 0 && isspace (*s))
    ++s;
  len = strlen (str);
  if (len == 0 || _strnicmp (s, str, len) != 0)
    return NULL;
  return s + len;
}


static const unsigned char *parse_table (const unsigned char *s, int *dst,
                                         char **tab, int n)
{
  int i;
  size_t len;

  while (*s != 0 && isspace (*s))
    ++s;
  for (i = 0; i < n; ++i)
    {
      len = strlen (tab[i]);
      if (_strnicmp (s, tab[i], len) == 0)
        {
          *dst = i;
          return s + len;
        }
    }
  return NULL;
}


static const unsigned char *parse_fmt (const unsigned char *s,
                                       const unsigned char *f, struct tm *tm)
{
  const unsigned char *t;
  int week = -1;

  while (*f != 0)
    {
      if (isspace (*f))
        {
          while (*s != 0 && isspace (*s))
            ++s;
          ++f;
        }
      else if (*f != '%')
        {
          if (*s != *f)
            return NULL;
          ++s; ++f;
        }
      else
        {
          ++f;
          switch (*f++)
            {
            case 0:
              if (*s != '%')
                return NULL;
              return s + 1;
              break;

            case '%':
              if (*s != '%')
                return NULL;
              ++s;
              break;

            case 'a':
              s = TABLE (&tm->tm_wday, _cur_lcf_time.wdays1, 7);
              if (s == NULL) return NULL;
              break;

            case 'A':
              s = TABLE (&tm->tm_wday, _cur_lcf_time.wdays2, 7);
              if (s == NULL) return NULL;
              break;

            case 'b':
            case 'h':
              s = TABLE (&tm->tm_mon, _cur_lcf_time.months1, 12);
              if (s == NULL) return NULL;
              break;

            case 'B':
              s = TABLE (&tm->tm_mon, _cur_lcf_time.months2, 12);
              if (s == NULL) return NULL;
              break;

            case 'c':
              s = RECURSE (_cur_lcf_time.date_time_fmt);
              if (s == NULL) return NULL;
              break;

            case 'C':
              s = RECURSE ("%x %X");
              if (s == NULL) return NULL;
              break;

            case 'd':
            case 'e':
              s = NUMBER (&tm->tm_mday, 1, 31, 0);
              if (s == NULL) return NULL;
              break;

            case 'D':
              s = RECURSE ("%m/%d/%y");
              if (s == NULL) return NULL;
              break;

            case 'H':
            case 'k':
              s = NUMBER (&tm->tm_hour, 0, 23, 0);
              if (s == NULL) return NULL;
              break;

            case 'I':
            case 'l':
              s = NUMBER (&tm->tm_hour, 1, 12, 0);
              if (s == NULL) return NULL;
              break;

            case 'j':
              s = NUMBER (&tm->tm_yday, 1, 366, -1);
              if (s == NULL) return NULL;
              break;

            case 'm':
              s = NUMBER (&tm->tm_mon, 1, 12, -1);
              if (s == NULL) return NULL;
              break;

            case 'M':
              s = NUMBER (&tm->tm_min, 0, 59, 0);
              if (s == NULL) return NULL;
              break;

            case 'n':
              if (*s != '\n')
                return NULL;
              ++s;
              break;

            case 'p':
              if (tm->tm_hour < 1 || tm->tm_hour > 12)
                return NULL;
              if ((t = STRING (_cur_lcf_time.am)) != NULL)
                {
                  if (tm->tm_hour == 12)
                    tm->tm_hour = 0;
                }
              else if ((t = STRING (_cur_lcf_time.pm)) != NULL)
                {
                  if (tm->tm_hour != 12)
                    tm->tm_hour += 12;
                }
              else
                return NULL;
              s = t;
              break;

            case 'r':
              s = RECURSE ("%I:%M:%S %p");
              if (s == NULL) return NULL;
              break;

            case 'R':
              s = RECURSE ("%H:%M");
              if (s == NULL) return NULL;
              break;

            case 'S':
              s = NUMBER (&tm->tm_sec, 0, 61, 0);
              if (s == NULL) return NULL;
              break;

            case 't':
              if (*s != '\t')
                return NULL;
              ++s;
              break;

            case 'T':
              s = RECURSE ("%H:%M:%S");
              if (s == NULL) return NULL;
              break;

            case 'w':
              s = NUMBER (&tm->tm_wday, 0, 6, 0);
              if (s == NULL) return NULL;
              break;

            case 'U':
            case 'W':
              /* %U and %W are currently ignored. */
              s = NUMBER (&week, 0, 53, 0);
              if (s == NULL) return NULL;
              break;

            case 'x':
              s = RECURSE (_cur_lcf_time.date_fmt);
              if (s == NULL) return NULL;
              break;

            case 'X':
              s = RECURSE (_cur_lcf_time.time_fmt);
              if (s == NULL) return NULL;
              break;

            case 'y':
              s = NUMBER (&tm->tm_year, 0, 99, 0);
              if (s == NULL) return NULL;
              if (tm->tm_year < 69)
                tm->tm_year += 100;
              break;

            case 'Y':
              s = NUMBER (&tm->tm_year, 1900, -1, -1900);
              if (s == NULL) return NULL;
              break;

            default:
              return NULL;
            }
        }
    }
  return s;
}


char *_strptime (const char *buf, const char *format, struct tm *tm)
{
  return (char *)parse_fmt ((const unsigned char *)buf,
                            (const unsigned char *)format, tm);
}
