/* Copyright 2004 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu)
 *
 * This program is free software; you can redistribute it and/or modify   
 * it under the terms of the GNU General Public License as published by   
 * the Free Software Foundation; either version 2 of the License, or      
 * (at your option) any later version.                                    
 *                                                                         
 * This program is distributed in the hope that it will be useful,        
 * but WITHOUT ANY WARRANTY; without even the implied warranty of         
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          
 * GNU General Public License for more details.                           
 *                                                                         
 * You should have received a copy of the GNU General Public License      
 * along with this program; if not, write to the Free Software            
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <time.h>

#include "ipset.h"

#include <linux/netfilter_ipv4/ip_set_time.h>

#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))

static const char *const week_days[] = {
	NULL, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
};

static time_t time_parse_date(const char *s, bool end)
{
	unsigned int month = 1, day = 1, hour = 0, minute = 0, second = 0;
	unsigned int year  = end ? 2038 : 1970;
	const char *os = s;
	struct tm tm;
	time_t ret;
	char *e;

	year = strtoul(s, &e, 10);
	if ((*e != '-' && *e != '\0') || year < 1970 || year > 2038)
		goto out;
	if (*e == '\0')
		goto eval;

	s = e + 1;
	month = strtoul(s, &e, 10);
	if ((*e != '-' && *e != '\0') || month > 12)
		goto out;
	if (*e == '\0')
		goto eval;

	s = e + 1;
	day = strtoul(s, &e, 10);
	if ((*e != 'T' && *e != '\0') || day > 31)
		goto out;
	if (*e == '\0')
		goto eval;

	s = e + 1;
	hour = strtoul(s, &e, 10);
	if ((*e != ':' && *e != '\0') || hour > 23)
		goto out;
	if (*e == '\0')
		goto eval;

	s = e + 1;
	minute = strtoul(s, &e, 10);
	if ((*e != ':' && *e != '\0') || minute > 59)
		goto out;
	if (*e == '\0')
		goto eval;

	s = e + 1;
	second = strtoul(s, &e, 10);
	if (*e != '\0' || second > 59)
		goto out;

 eval:
	tm.tm_year = year - 1900;
	tm.tm_mon  = month - 1;
	tm.tm_mday = day;
	tm.tm_hour = hour;
	tm.tm_min  = minute;
	tm.tm_sec  = second;
	ret = mktime(&tm);
	if (ret >= 0)
		return ret;
	perror("mktime");
	exit_error(OTHER_PROBLEM, "mktime returned an error");

 out:
	exit_error(PARAMETER_PROBLEM, "Invalid date \"%s\" specified. Should "
	           "be YYYY[-MM[-DD[Thh[:mm[:ss]]]]]", os);
	return -1;
}

static unsigned int time_parse_minutes(const char *s)
{
	unsigned int hour, minute, second = 0;
	char *e;

	hour = strtoul(s, &e, 10);
	if (*e != ':' || hour > 23)
		goto out;

	s = e + 1;
	minute = strtoul(s, &e, 10);
	if ((*e != ':' && *e != '\0') || minute > 59)
		goto out;
	if (*e == '\0')
		goto eval;

	s = e + 1;
	second = strtoul(s, &e, 10);
	if (*e != '\0' || second > 59)
		goto out;

 eval:
	return 60 * 60 * hour + 60 * minute + second;

 out:
	exit_error(PARAMETER_PROBLEM, "invalid time \"%s\" specified, "
	           "should be hh:mm[:ss] format and within the boundaries", s);
	return -1;
}

static const char *my_strseg(char *buf, unsigned int buflen,
    const char **arg, char delim)
{
	const char *sep;

	if (*arg == NULL || **arg == '\0')
		return NULL;
	sep = strchr(*arg, delim);
	if (sep == NULL) {
		snprintf(buf, buflen, "%s", *arg);
		*arg = NULL;
		return buf;
	}
	snprintf(buf, buflen, "%.*s", (unsigned int)(sep - *arg), *arg);
	*arg = sep + 1;
	return buf;
}

static uint32_t time_parse_monthdays(const char *arg)
{
	char day[3], *err = NULL;
	uint32_t ret = 0;
	unsigned int i;

	while (my_strseg(day, sizeof(day), &arg, ',') != NULL) {
		i = strtoul(day, &err, 0);
		if ((*err != ',' && *err != '\0') || i > 31)
			exit_error(PARAMETER_PROBLEM,
			           "%s is not a valid day for --monthdays", day);
		ret |= 1 << i;
	}

	return ret;
}

static unsigned int time_parse_weekdays(const char *arg)
{
	char day[4], *err = NULL;
	unsigned int i, ret = 0;
	bool valid;

	while (my_strseg(day, sizeof(day), &arg, ',') != NULL) {
		i = strtoul(day, &err, 0);
		if (*err == '\0') {
			if (i == 0)
				exit_error(PARAMETER_PROBLEM,
				           "No, the week does NOT begin with Sunday.");
			ret |= 1 << i;
			continue;
		}

		valid = false;
		for (i = 1; i < ARRAY_SIZE(week_days); ++i)
			if (strncmp(day, week_days[i], 2) == 0) {
				ret |= 1 << i;
				valid = true;
			}

		if (!valid)
			exit_error(PARAMETER_PROBLEM,
			           "%s is not a valid day specifier", day);
	}

	return ret;
}


static void time_print_date(time_t date, const char *command)
{
	struct tm *t;

	/* If it is the default value, do not print it. */
	if (date == 0 || date == LONG_MAX)
		return;

	t = localtime(&date);
	if (command != NULL)
		/*
		 * Need a contiguous string (no whitespaces), hence using
		 * the ISO 8601 "T" variant.
		 */
		printf("%s %04u-%02u-%02uT%02u:%02u:%02u ",
		       command, t->tm_year + 1900, t->tm_mon + 1,
		       t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
	else
		printf("%04u-%02u-%02u %02u:%02u:%02u ",
		       t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
		       t->tm_hour, t->tm_min, t->tm_sec);
}

static void time_print_monthdays(uint32_t mask, bool human_readable)
{
	unsigned int i, nbdays = 0;

	for (i = 1; i <= 31; ++i)
		if (mask & (1 << i)) {
			if (nbdays++ > 0)
				printf(",");
			printf("%u", i);
			if (human_readable)
				switch (i % 10) {
					case 1:
						printf("st");
						break;
					case 2:
						printf("nd");
						break;
					case 3:
						printf("rd");
						break;
					default:
						printf("th");
						break;
				}
		}
	printf(" ");
}

static void time_print_weekdays(unsigned int mask)
{
	unsigned int i, nbdays = 0;

	for (i = 1; i <= 7; ++i)
		if (mask & (1 << i)) {
			if (nbdays > 0)
				printf(",%s", week_days[i]);
			else
				printf("%s", week_days[i]);
			++nbdays;
		}
	printf(" ");
}

static inline void divide_time(unsigned int fulltime, unsigned int *hours,
    unsigned int *minutes, unsigned int *seconds)
{
	*seconds  = fulltime % 60;
	fulltime /= 60;
	*minutes  = fulltime % 60;
	*hours    = fulltime / 60;
}

/* Initialize the create. */
static void
time_create_init(void *data UNUSED)
{
	struct ip_set_req_time_create *mydata = data;
	
	mydata->size = 0;
	
	return;
}

/* Function which parses command options; returns true if it ate an option */
static int
time_create_parse(int c, char *argv[] UNUSED, void *data, unsigned *flags)
{
	return 1;
}

/* Final check; exit if not ok. */
static void
time_create_final(void *data, unsigned int flags)
{
	return;
}

/* Create commandline options */
static const struct option create_opts[] = {
	{NULL},
};

/* Add, del, test parser */
static ip_set_ip_t
time_adt_parser(int cmd UNUSED, const char *arg, void *data)
{
	struct ip_set_req_time *mydata = data;
	char *saved = ipset_strdup(arg);
	char *ptr, *tmp = saved;	
    int  tzdiff;

	DP("%s", arg);

	/* By default, we match on every day, every daytime */
	mydata->monthdays_match = XT_TIME_ALL_MONTHDAYS;
	mydata->weekdays_match  = XT_TIME_ALL_WEEKDAYS;
	mydata->daytime_start   = XT_TIME_MIN_DAYTIME;
	mydata->daytime_stop    = XT_TIME_MAX_DAYTIME;

	/* ...and have no date-begin or date-end boundary */
	mydata->date_start = 0;
	mydata->date_stop  = INT_MAX;

	/* local time is default */
	mydata->flags |= XT_TIME_LOCAL_TZ;
	
	ptr = strsep(&tmp, "/");

	if(strcmp(ptr,"none") != 0)
	{
		mydata->date_start = time_parse_date(ptr, false);
	}

	ptr = strsep(&tmp, "/");

	if(strcmp(ptr,"none") != 0)
	{
		mydata->date_stop = time_parse_date(ptr, true);
	}

	ptr = strsep(&tmp, "/");

	if(strcmp(ptr,"none") != 0)
	{
		mydata->daytime_start = time_parse_minutes(ptr);
	}

	ptr = strsep(&tmp, "/");

	if(strcmp(ptr,"none") != 0)
	{
		mydata->daytime_stop = time_parse_minutes(ptr);
	}

	ptr = strsep(&tmp, "/");

	if(strcmp(ptr,"none") != 0)
	{
		mydata->monthdays_match = time_parse_monthdays(ptr);
	}

	ptr = strsep(&tmp, "/");

	if(strcmp(ptr,"none") != 0)
	{
		mydata->weekdays_match = time_parse_weekdays(ptr);
	}

	ptr = strsep(&tmp, "/");

#if 0
	if(strcmp(ptr,"utc") == 0)
	{
		mydata->flags &= ~XT_TIME_LOCAL_TZ;
	}	
#else
    tzdiff = atoi(ptr);
    if (tzdiff<0) {
        tzdiff = -tzdiff;
        mydata->flags = (tzdiff & 0x0F);
        mydata->flags |= 0x80;
    }
    else
    {
        mydata->flags = (tzdiff & 0x0F);
    }
    //printf("ptr = %s, tzdiff = %d, mydata->flags = 0x%X\n", ptr, tzdiff, mydata->flags);
#endif
	ipset_free(saved);
	return 1;
}

/*
 * Print and save
 */

static void
time_initheader(struct set *set, const void *data)
{
	const struct ip_set_req_time_create *header = data;
	struct ip_set_req_time_create *map = set->settype->header;
		
	memset(map, 0, sizeof(struct ip_set_req_time_create));
	map->size = header->size;
}

static void
time_printheader(struct set *set, unsigned options)
{
	printf("%s %s\n", 
	       set->name,
	       set->settype->typename);

}

static void
time_printips_sorted(struct set *set, void *data,
			u_int32_t len, unsigned options,
			char dont_align)
{
	struct ip_set_req_time *ip;
	size_t offset = 0;
	unsigned int h, m, s;

	while (offset < len) {
		ip = data + offset;
		printf("TIME ");
		if (ip->daytime_start != XT_TIME_MIN_DAYTIME || ip->daytime_stop != XT_TIME_MAX_DAYTIME) {
			divide_time(ip->daytime_start, &h, &m, &s);
			printf("from %02u:%02u:%02u ", h, m, s);
			divide_time(ip->daytime_stop, &h, &m, &s);
			printf("to %02u:%02u:%02u ", h, m, s);
		}
		if (ip->weekdays_match != XT_TIME_ALL_WEEKDAYS) {
			printf("on ");
			time_print_weekdays(ip->weekdays_match);
		}
		if (ip->monthdays_match != XT_TIME_ALL_MONTHDAYS) {
			printf("on ");
			time_print_monthdays(ip->monthdays_match, true);
		}
		if (ip->date_start != 0) {
			printf("starting from ");
			time_print_date(ip->date_start, NULL);
		}
		if (ip->date_stop != INT_MAX) {
			printf("until date ");
			time_print_date(ip->date_stop, NULL);
		}
#if 0
		if (!(ip->flags & XT_TIME_LOCAL_TZ))
			printf("UTC ");
#else
        printf("0x%X ", ip->flags);
#endif
		printf("\n");
		offset += sizeof(struct ip_set_req_time);
	}
}

static void
time_saveheader(struct set *set, unsigned options)
{
	printf("-N %s %s\n", 
	       set->name,
	       set->settype->typename);
}

static void
time_saveips(struct set *set, void *data,
		u_int32_t len, unsigned options,
		char dont_align)
{
	struct ip_set_req_time *ip;
	size_t offset = 0;
	unsigned int h, m, s;
		
	while (offset < len) {
		ip = data + offset;
		printf("-A %s ", set->name);
		if (ip->date_start != 0) {
			time_print_date(ip->date_start, NULL);
			printf("/");
		} else {
			printf("none/");
		}
		if (ip->date_stop != INT_MAX) {
			time_print_date(ip->date_stop, NULL);
			printf("/");
		} else {
			printf("none/");
		}
		if (ip->daytime_start != XT_TIME_MIN_DAYTIME || ip->daytime_stop != XT_TIME_MAX_DAYTIME) {
			divide_time(ip->daytime_start, &h, &m, &s);
			printf("%02u:%02u:%02u/", h, m, s);
			divide_time(ip->daytime_stop, &h, &m, &s);
			printf("%02u:%02u:%02u/", h, m, s);
		} else {
			printf("none/");
			printf("none/");
		}
		if (ip->weekdays_match != XT_TIME_ALL_WEEKDAYS) {
			time_print_weekdays(ip->weekdays_match);
			printf("/");
		} else {
			printf("none/");
		}
		if (ip->monthdays_match != XT_TIME_ALL_MONTHDAYS) {
			time_print_monthdays(ip->monthdays_match, true);
			printf("/");
		} else {
			printf("none/");
		}
#if 0
		if (!(ip->flags & XT_TIME_LOCAL_TZ))
			printf("utc");
		else
			printf("none");
#else
        // TODO: This is worng, but no one use save function now
        printf("-8", ip->flags);
#endif
		printf("\n");
		offset += sizeof(struct ip_set_req_time);
	}
}

static void
time_usage(void)
{
	printf
	    ("-N set time\n"
	     "-ADT set datestart/datestop/timestart/timestop/monthdays/weekdays/utc\n"
	     "(use none if the field is not set.)\n");
}

static struct settype settype_time = {
	.typename = SETTYPE_NAME,
	.protocol_version = IP_SET_PROTOCOL_VERSION,

	/* Create */
	.create_size = sizeof(struct ip_set_req_time_create),
	.create_init = time_create_init,
	.create_parse = time_create_parse,
	.create_final = time_create_final,
	.create_opts = create_opts,

	/* Add/del/test */
	.adt_size = sizeof(struct ip_set_req_time),
	.adt_parser = time_adt_parser,

	/* Printing */
	.header_size = sizeof(struct ip_set_req_time_create),
	.initheader = time_initheader,
	.printheader = time_printheader,
	.printips = time_printips_sorted,
	.printips_sorted = time_printips_sorted,
	.saveheader = time_saveheader,
	.saveips = time_saveips,
	
	.usage = time_usage,
};

CONSTRUCTOR(time)
{
	settype_register(&settype_time);

}
