#define __USE_GNU
#include "../module_iface.h"
#include <string.h>
#include <stdio.h>
#include <linux/netfilter_ipv4/ipt_time.h>
#include <time.h>
#include <limits.h>

#define MODULE_TYPE MODULE_MATCH
#define MODULE_DATATYPE struct ipt_time_info
#define MODULE_NAME "time"

#if MODULE_TYPE == MODULE_TARGET
#  define MODULE_ENTRYTYPE struct ipt_entry_match
#else 
#  if MODULE_TYPE == MODULE_MATCH
#    define MODULE_ENTRYTYPE struct ipt_entry_target
#  else
#    error MODULE_TYPE is unknown!
#  endif
#endif

#define TIME_TIMESTART	(1 << 0)
#define TIME_TIMESTOP	(1 << 1)
#define TIME_DAYS		(1 << 2)
#define TIME_ALLOPTS	(TIME_TIMESTART | TIME_TIMESTOP | TIME_DAYS)

typedef struct {
	char *name;
	u_int8_t value;
} DayList;

DayList days[] = {
	{ "Sun",	1 << 6 },
	{ "Mon",	1 << 5 },
	{ "Tue",	1 << 4 },
	{ "Wed",	1 << 3 },
	{ "Thu",	1 << 2 },
	{ "Fri",	1 << 1 },
	{ "Sat",	1 << 0 }
};

static void setup(void *myinfo, unsigned int *nfcache) {
	*nfcache |= NFC_UNKNOWN;
}

static u_int16_t string_to_hrmin(char *string) {
	char *extent = NULL, *sep = NULL;
	unsigned int hr, min;

	hr = strtoul(string, &sep, 10);
	if(!sep || *sep != ':' || hr >= 24)
		return(USHRT_MAX);
	sep++;

	min = strtoul(sep, &extent, 10);
	if(!extent || *extent != '\0' || min >= 60)
		return(USHRT_MAX);
	min += hr * 60;
	return min;
}

static int parse_field(char *field, SV *value, void *myinfo,
		unsigned int *nfcache, struct ipt_entry *entry, int *flags) {
	MODULE_DATATYPE *info = (void *)(*(MODULE_ENTRYTYPE **)myinfo)->data;
	char *str = NULL, *temp = NULL;
	STRLEN len;

	if(!strcmp(field, "timestart")) {
		if(!SvPOK(value)) {
			SET_ERRSTR("%s: Must have a string arg", field);
			return(FALSE);
		}
		temp = SvPV(value, len);
		str = malloc(len + 1);
		strncpy(str, temp, len);
		str[len] = '\0';
		info->time_start = string_to_hrmin(str);
		free(str);
		if(info->time_start == USHRT_MAX) { 
			SET_ERRSTR("%s: Couldn't parse arg", field);
			return(FALSE);
		}
		*flags |= TIME_TIMESTART;
	}
	else if(!strcmp(field, "timestop")) {
		if(!SvPOK(value)) {
			SET_ERRSTR("%s: Must have a string arg", field);
			return(FALSE);
		}
		temp = SvPV(value, len);
		str = malloc(len + 1);
		strncpy(str, temp, len);
		str[len] = '\0';
		info->time_stop = string_to_hrmin(str);
		free(str);
		if(info->time_stop == USHRT_MAX) { 
			SET_ERRSTR("%s: Couldn't parse arg", field);
			return(FALSE);
		}
		*flags |= TIME_TIMESTOP;
	}
	else if(!strcmp(field, "days")) {
		SV *av = NULL;
		int i, j;
		if(!SvROK(value)) {
			SET_ERRSTR("%s: Must have a reference arg", field);
			return(FALSE);
		}
		
		av = SvRV(value);
		if(SvTYPE(av) != SVt_PVAV) {
			SET_ERRSTR("%s: Must be an array ref", field);
			return(FALSE);
		}

		for(i = 0; i <= av_len((AV *)av); i++) {
			SV **svp = av_fetch((AV *)av, i, FALSE);
			DayList *selector = NULL;
			if(!svp || !SvPOK(*svp)) {
				SET_ERRSTR("%s: Array must contain strings", field);
				return(FALSE);
			}
			temp = SvPV(*svp, len);
			str = malloc(len + 1);
			strncpy(str, temp, len);
			str[len] = '\0';
			selector = NULL;
			for(j = 0; j < sizeof(days) / sizeof(DayList) ; j++) {
				if(!strcmp(str, days[j].name)) {
					selector = &days[j];
					break;
				}
			}
			if(selector)
				info->days_match |= selector->value;
			else {
				SET_ERRSTR("%s: Unknown day %s", field, str);
				free(str);
				return(FALSE);
			}
			free(str);
		}
		*flags |= TIME_DAYS;
	}
	else
		return(FALSE);

	return(TRUE);
}

static SV *hrmin_to_sv(u_int16_t mins) {
	char *string = NULL;
	int hrs = mins / 60;

	mins %= 60;
	hrs %= 24;
	asprintf(&string, "%d:%.2d", hrs, mins);
	return(newSVpv(string, 0));
}

static void get_fields(HV *ent_hash, void *myinfo, struct ipt_entry *entry) {
	MODULE_DATATYPE *info = (void *)((MODULE_ENTRYTYPE *)myinfo)->data;
	AV *av = newAV();
	int i;

	hv_store(ent_hash, "timestart", 9, hrmin_to_sv(info->time_start), 0);
	hv_store(ent_hash, "timestop", 8, hrmin_to_sv(info->time_stop), 0);
	for(i = 0; i < sizeof(days) / sizeof(DayList); i++) {
		if(info->days_match & days[i].value)
			av_push(av, newSVpv(strdup(days[i].name), 0));
	}
	hv_store(ent_hash, "days", 4, newRV((SV *)av), 0);
}

static int final_check(void *myinfo, int flags) {
/*	MODULE_DATATYPE *info =
	    (void *)((struct ipt_entry_match *)myinfo)->data; */

	if(flags != TIME_ALLOPTS) {
		SET_ERRSTR("time match requires timestart, timestop and days fields");
		return(FALSE);
	}
	return(TRUE);
}

static ModuleDef _module = {
	NULL, /* always NULL */
	MODULE_TYPE,
	MODULE_NAME,
	IPT_ALIGN(sizeof(MODULE_DATATYPE)),
	IPT_ALIGN(sizeof(MODULE_DATATYPE)),
	setup,
	parse_field,
	get_fields,
	final_check
};

ModuleDef *init(void) {
	return(&_module);
}

/* vim: ts=4
 */
