/*
 * Copyright (c) 1998-2001 Caucho Technology -- all rights reserved
 *
 * Caucho Technology permits redistribution, modification and use
 * of this file in source and binary form ("the Software") under the
 * Caucho Developer Source License ("the License").  The following
 * conditions must be met:
 *
 * 1. Each copy or derived work of the Software must preserve the copyright
 *    notice and this notice unmodified.
 *
 * 2. Redistributions of the Software in source or binary form must include 
 *    an unmodified copy of the License, normally in a plain ASCII text
 *
 * 3. The names "Resin" or "Caucho" are trademarks of Caucho Technology and
 *    may not be used to endorse products derived from this software.
 *    "Resin" or "Caucho" may not appear in the names of products derived
 *    from this software.
 *
 * 4. Caucho Technology requests that attribution be given to Resin
 *    in any manner possible.  We suggest using the "Resin Powered"
 *    button or creating a "powered by Resin(tm)" link to
 *    http://www.caucho.com for each page served by Resin.
 *
 * This Software is provided "AS IS," without a warranty of any kind. 
 * ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
 *
 * CAUCHO TECHNOLOGY AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE OR ANY THIRD PARTY AS A RESULT OF USING OR
 * DISTRIBUTING SOFTWARE. IN NO EVENT WILL CAUCHO OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE SOFTWARE, EVEN IF HE HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.      
 *
 * @author Scott Ferguson
 */

#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/fs.h>
#include <linux/smp_lock.h>
#include <linux/time.h>
#include <asm/uaccess.h>
#include <net/ip.h>

#include "hardcore.h"

#define SEC_PER_DAY (24 * 60 * 60)
#define SEC_PER_EON (SEC_PER_DAY * (265 * 400 + 100 - 3))

typedef struct date_t {
  int time;
  
  int days;
  
  int year;
  int is_leap_year;
  int day_of_year;
  
  int month;
  int day_of_month;

  int hour;
  int minute;
  int second;
} date_t;

static int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static char *day_names[] =  {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static char *month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 
                              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

static char g_date_buffer[128];
static int g_date_length;
static int g_date_time = 0xdeadbeef;

static int
is_leap_year(int year)
{
  return ! ((year % 4) != 0 || ((year % 100) == 0 && (year % 400) != 0));
}

static int
div_floor(int n, int d)
{
  if (n > 0)
    return n / d;
  else
    return (n - d + 1) / d;
}

static void
calculate_year(date_t *date)
{
  int days = date->days;
  int n400;
  int n100;
  int n4;
  int n1;

  // shift to using 1601 as a base
  days += (1970 - 1601) * 365 + (1970 - 1601) / 4 - 3;

  n400 = div_floor(days, 400 * 365 + 100 - 3);
  days -= n400 * (400 * 365 + 100 - 3);

  n100 = div_floor(days, 100 * 365 + 25 - 1);
  if (n100 == 4)
    n100 = 3;
  days -= n100 * (100 * 365 + 25 - 1);

  n4 = div_floor(days, 4 * 365 + 1);
  if (n4 == 25)
    n4 = 24;
  days -= n4 * (4 * 365 + 1);

  n1 = div_floor(days, 365);
  if (n1 == 4)
    n1 = 3;

  date->year = 400 * n400 + 100 * n100 + 4 * n4 + n1 + 1601;
  date->is_leap_year = is_leap_year(date->year);
  
  date->day_of_year = days - 365 * n1;
}

static void
calculate_month(date_t *date)
{
  int day_of_month = date->day_of_year;
  int is_leap_year = date->is_leap_year;
  int month;

  for (month = 0; month < 12; month++) {
    if (month == 1 && is_leap_year) {
      if (day_of_month < 29)
        break;
      else
        day_of_month -= 29;
    }
    else if (day_of_month < days_in_month[month])
      break;
    else
      day_of_month -= days_in_month[month];
  }

  date->month = month;
  date->day_of_month = day_of_month;
}

static void
calculate_date(date_t *date, int time)
{
  int time_of_day;
  
  date->time = time;

  date->days = div_floor(time, SEC_PER_DAY);
  time_of_day = time - SEC_PER_DAY * date->days;

  calculate_year(date);
  calculate_month(date);

  date->hour = time_of_day / 3600;
  date->minute = time_of_day / 60 % 60;
  date->second = time_of_day % 60;
}

int
format_date(char *buf, int time)
{
  date_t date;
  int len;
  
  calculate_date(&date, time);
    
  len = sprintf(buf, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
                day_names[(date.days % 7 + 11) % 7],
                date.day_of_month + 1,
                month_names[date.month],
                date.year,
                date.hour,
                date.minute,
                date.second);

  return len;
}

int
format_now(char *buf)
{
  struct timeval time_now;
  int now;

  get_fast_time(&time_now);

  now = time_now.tv_sec;

  if (now != g_date_time) {
    g_date_length = format_date(g_date_buffer, now);
    
    g_date_time = now;
  }

  strcpy(buf, g_date_buffer);
  
  return g_date_length;
}

static int
year_to_day(int year)
{
  if (year > 0) {
    year -= 1601;
    return (365 * year + year / 4 - year / 100 + year / 400 -
            ((1970 - 1601) * 365 + (1970 - 1601) / 4 - 3));
  } else {
    year = 2000 - year;

    return ((2000 - 1970) * 365 + (2000 - 1970) / 4 - 
            (365 * year + year / 4 - year / 100 + year / 400));
  }
}

static int
month_to_day(int month, int is_leap_year)
{
  int day = 0;
  int i;

  for (i = 0; i < month && i < 12; i++) {
    day += days_in_month[i];
    if (i == 1 && is_leap_year)
      day++;
  }

  return day;
}

/*
 * Scan to whitespace or ':'
 */
static char *
scan(char **p_string, int dash)
{
  char *string = *p_string;
  char ch;

  // skip over leading whitespace
  for (; (ch = *string); string++) {
    if (ch != ' ' && ch != '\t' && ch != ':' && (! dash || ch != '-'))
      break;
  }
  *p_string = string;

  for (; (ch = *string); string++) {
    if (ch == ' ' || ch == '\t' || ch == ':' || (dash && ch == '-'))
      break;
  }

  return string;
}

static int
parse_int(char *string)
{
  int ch;
  int value = 0;
  int sign = 1;

  if (*string == '-') {
    sign = -1;
    string++;
  }
  else if (*string == '+')
    string++;
  
  for (; (ch = *string) >= '0' && ch <= '9'; string++)
    value = 10 * value + ch - '0';

  return sign * value;
}
  
/*
 * Mon, 17 Jan 1994 11:14:55 -0500 (EST)
 *
 * In GMT time
 */
int
parse_date(char *string)
{
  int ch;
  int day_of_month;
  int month;
  int time_of_day;
  int year;
  int time;
  char *next;

  for (; (ch = *string) == ' ' || ch == '\t'; string++) {
  }
  
  next = scan(&string, 1);

  if ((ch = *string) && (ch < '0' || ch > '9')) {
    string = next;
    next = scan(&string, 1);
  }

  day_of_month = parse_int(string);
  string = next;
  
  next = scan(&string, 1);
  
  for (month = 0; month < 12; month++) {
    if (! strnicmp(month_names[month], string, 3))
      break;
  }
  
  if (month == 12)
    return 0;

  string = next;
  next = scan(&string, 1);

  year = parse_int(string);
  if (year < 50)
    year += 2000;
  else if (year < 100)
    year += 1900;

  string = next;
  next = scan(&string, 0);
  time_of_day = parse_int(string) * 3600;

  string = next;
  next = scan(&string, 0);
  time_of_day += parse_int(string) * 60;

  string = next;
  next = scan(&string, 0);
  time_of_day += parse_int(string);

  time = (SEC_PER_DAY * (year_to_day(year) + 
                         month_to_day(month, is_leap_year(year)) + 
                         day_of_month - 1) +
          time_of_day);

  // assume gmt
  
  return time;
}

