/*
 * $RCSfile: timer_funcs.c,v $  $Revision: 1.10 $  $Name:  $
 * $Id: timer_funcs.c,v 1.10 2006/10/22 15:22:42 bpaauwe Exp $
 * $Author: bpaauwe $
 * $Date: 2006/10/22 15:22:42 $
 * ----------------------------------------------------------------------------
 *
 *  Copyright (c) Bob Paauwe (2006)
 *
 *  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
 *
 * ----------------------------------------------------------------------------
 *
 *  Description:
 *    Functions for reading and writing timer entries in the PLC.
 *
 * ----------------------------------------------------------------------------
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <time.h>
#include "libilib/ilib.h"
/*
#include "insteon_msg.h"
#include "scenes.h"
#include "util.h"
*/
#include "timers.h"

extern int command_to_num(char *cmd);
unsigned char cvt_days (char *days);
void timer_locations(ilib_t *iplc);

/*
 * print_timer_record
 *
 * Print out a timer record in a nice easy to read format. This is mostly
 * for debugging purposes.
 */
void print_timer_record(timer_timerec_t *timer)
{
	int i, j;

	while (timer) {
		printf("%3d: ", timer->tid);
		if (timer->type == T_ABSOLUTE) {
			printf("%02d:%02d ", (timer->time / 60),
					((timer->time - (timer->time / 60) * 60)));
		} else if (timer->type == T_SUNRISE) {
			printf("Sunrise + %d ", timer->time);
		} else if (timer->type == T_SUNSET) {
			printf("Sunset + %d ", timer->time);
		} else {
			printf("Unknown ");
		}

		printf("[");
		if (timer->flag0 & T_SECURITY) {
			printf("S");
		}
		if (timer->flag0 & T_RNDSECURITY) {
			printf("Rs");
		}
		if (timer->flag0 & T_HOLIDAY) {
			printf("H");
		}
		if (timer->flag0 & T_ACTIVE) {
			printf("A");
		}
		if (timer->flag0 & T_DELETED) {
			printf("D");
		}
		printf("] ");

		if (timer->dow & T_SATURDAY) {
			printf("Sa");
		}
		if (timer->dow & T_SUNDAY) {
			printf("Su");
		}
		if (timer->dow & T_MONDAY) {
			printf("M");
		}
		if (timer->dow & T_TUESDAY) {
			printf("Tu");
		}
		if (timer->dow & T_WEDNESDAY) {
			printf("W");
		}
		if (timer->dow & T_THURSDAY) {
			printf("Th");
		}
		if (timer->dow & T_FRIDAY) {
			printf("F");
		}
		printf(" -- ");

		for (i = 0; i < timer->cmd_group.count; i++) {
			if (timer->cmd_group.cmds[i].type == T_NONE) {
				printf("None ");
			}
			if (timer->cmd_group.cmds[i].type == T_INSTEON) {
				printf("INSTEON ");
			}
			if (timer->cmd_group.cmds[i].type == T_X10) {
				printf("X10 ");
			}
			if (timer->cmd_group.cmds[i].type == T_RS232) {
				printf("rs232 ");
			}
			if (timer->cmd_group.cmds[i].type == T_VAR) {
				printf("VAR ");
			}
			if (timer->cmd_group.cmds[i].type == T_MACRO) {
				printf("Macro ");
			}

			for (j = 0; j < timer->cmd_group.cmds[i].len; j++) {
				printf("0x%02x ", timer->cmd_group.cmds[i].data[j]);
			}
		}
		printf("\n");

		timer = timer->next;
	}
}



/*
 * get_timer_info
 *
 * Read the timer blocks from the PLC and create a linked list of
 * structures that holds that information.
 */

timer_timerec_t *get_timer_info(ilib_t *iplc)
{
	timer_timerec_t *timers = NULL, *t = NULL, *last = NULL;
	timer_cmd_t *cmd;
	unsigned short addr = 0x1800;
	unsigned char buf[11];
	int i;
	int tid = 1;

	/*
	 * Use this for debugging timer address issues */
	/* timer_locations(iplc); */

	do {
		ibios_read_memory(iplc, addr, 11, buf);

		if (buf[0] != 0xff) {
			if (timers == NULL) {
				timers = (timer_timerec_t *)malloc(sizeof(timer_timerec_t));
				t = timers;
			} else {
				t->next = (timer_timerec_t *)malloc(sizeof(timer_timerec_t));
				t = t->next;
			}

			t->tid = tid++;
			t->type = buf[0];
			t->time = (buf[1] * 256) + buf[2];
			t->flag0 = buf[3];
			t->dow = buf[4];
			t->rpt = buf[5];
			t->rpts = buf[6];
			t->cmd_group_id = (buf[7] * 256) + buf[8];
			t->filter_group_id = (buf[9] * 256) + buf[10];
			t->next = NULL;
			last = t;
		}
		addr += 11;
	} while (buf[0] != 0xff);

	/* Loop through each timer and get the commands it should execute */
	t = timers;
	while (t) {
		last = t;
		if (t->cmd_group_id) {
			ibios_read_memory(iplc, t->cmd_group_id, 3, buf);
			t->cmd_group.id = buf[0];
			t->cmd_group.count = buf[1];
			t->cmd_group.pos = buf[2];

			addr = t->cmd_group_id + 3;
			for (i = 0; i < t->cmd_group.count; i++) {
				ibios_read_memory(iplc, addr, 2, buf);
				t->cmd_group.cmd_ptrs[i] = (buf[0] * 256) + buf[1];

				ibios_read_memory(iplc, t->cmd_group.cmd_ptrs[i], 2, buf);
				t->cmd_group.cmds[i].type = buf[0];
				t->cmd_group.cmds[i].len = buf[1];
				t->cmd_group.cmds[i].data = (unsigned char *)malloc(buf[1]);
				ibios_read_memory(iplc, (t->cmd_group.cmd_ptrs[i] + 2),
						t->cmd_group.cmds[i].len, t->cmd_group.cmds[i].data);

				addr += 2;
				cmd++;
			}
		}
		t = t->next;
	}

	return timers;
}


/*
 * write_timers
 *
 * Takes a link list of timer information and writes it to the PLC. This
 * will overwrite any timers currently stored in the PLC.  The PLC is
 * initialized to start processing the timers.
 *
 * This would problaby be simplified if we placed the various tables
 * far away from each other.  Some of the timercore code implies that
 * this is how it should be done, but the SDM is storing them one after
 * the other.
 *
 */
int write_timers(ilib_t *iplc, timer_timerec_t *timers)
{
	timer_timerec_t *t;
	int cnt = 0;
	int i;
	unsigned short f_addr;
	unsigned short t_addr;
	unsigned short c_addr;
	unsigned short t_end, c_end, cs_end;
	unsigned char buf[13];
	unsigned char wp;

	/* Clear any existing timers */
	ibios_reset_timers(iplc);

	/*
	 * Count the number of entries so we can calculate where the command
	 * set table entries will go.
	 */
	t = timers;
	while (t != NULL) {
		cnt++;
		t = t->next;
	}

	printf("Writing %d timers\n", cnt);

	t_addr = 0x1800;  /* Timer table start address */
	f_addr = 0x1800 + (11 * (cnt + 1)); /* CommandSet table start address */
	t_end = f_addr; /* Timer table end address */

	/*
	 * Loop through the timers, write the timer table entrys.
	 */
	t = timers;
	while (t != NULL) {
		t->cmd_group_id = f_addr;
		buf[0] = t->type;
		buf[1] = (t->time & 0xff00) >> 8;
		buf[2] = t->time & 0x00ff;
		buf[3] = t->flag0;
		buf[4] = t->dow;
		buf[5] = t->rpt;
		buf[6] = t->rpts;
		buf[7] = (t->cmd_group_id & 0xff00) >> 8;
		buf[8] = t->cmd_group_id & 0x00ff;
		buf[9] = 0x00;
		buf[10] = 0x00;

		ibios_write_memory(iplc, t_addr, 11, buf);

		t_addr += 11;
		f_addr += (3 + (t->cmd_group.count * 2));
		t = t->next;
	}

	c_addr = f_addr + 3; /* Beginning of command table address */

	/* write an end of timers marker */
	memset(buf, 0xff, 11);
	ibios_write_memory(iplc, t_addr, 11, buf);

	/*
	 * Loop through the timers and write the command set table and
	 * the command table.
	 *
	 * f_addr is used to keep track of the address to write the next command
	 *        set entry.
	 * c_addr is used to keep track of the address to write the next command
	 *        entry.
	 */
	printf("Writing command pointer table\n");
	f_addr = 0x1800 + (11 * (cnt + 1));
	t = timers;
	while (t != NULL) {
		cs_end = f_addr;
		buf[0] = t->cmd_group.id;
		buf[1] = t->cmd_group.count;
		buf[2] = t->cmd_group.pos;
		ibios_write_memory(iplc, f_addr, 3, buf);
		f_addr += 3;
		for (i = 0; i < t->cmd_group.count; i++) {
			t->cmd_group.cmd_ptrs[i] = c_addr;
			buf[0] = (t->cmd_group.cmd_ptrs[i] & 0xff00) >> 8;
			buf[1] = t->cmd_group.cmd_ptrs[i] & 0x00ff;
			ibios_write_memory(iplc, f_addr, 2, buf);
			cs_end = f_addr + 5;  /* end of command set + 3 */

			/* Write command... */
			buf[0] = t->cmd_group.cmds[i].type;
			buf[1] = t->cmd_group.cmds[i].len;
			ibios_write_memory(iplc, c_addr, 2, buf);
			ibios_write_memory(iplc, (c_addr + 2), t->cmd_group.cmds[i].len,
					t->cmd_group.cmds[i].data);

			c_end = (c_addr + 8); /* end of commands */
			f_addr += 2;
			c_addr += (t->cmd_group.cmds[i].len + 2);
		}
		t = t->next;
	}

	/* Write end of command set table marker */
	buf[0] = 0xff;
	buf[1] = 0xff;
	buf[2] = 0xff;
	ibios_write_memory(iplc, f_addr, 3, buf);

	/*
	 * Remove write protect from salad code area.  Yeah, this is
	 * probably pretty dangerous. But the variables that the core
	 * code uses for the various tables is stored here so this is
	 * the only way we can update them.
	 */
	ibios_read_memory(iplc, 0x016b, 1, &wp);
	wp |= 0x08;
	ibios_write_memory(iplc, 0x016b, 1, &wp);

	/* Update static timer table variables */
	buf[4] = 0x18;
	buf[5] = 0x00;
	buf[6] = (t_end & 0xff00) >> 8;
	buf[7] = (t_end & 0x00ff);
	buf[8] = buf[6];
	buf[9] = buf[7];
	buf[10] = (cs_end & 0xff00) >> 8;
	buf[11] = (cs_end & 0x00ff);
	buf[0] = buf[10];
	buf[1] = buf[11];
	buf[2] = (c_end & 0xff00) >> 8;
	buf[3] = (c_end & 0x00ff);
	ibios_write_memory(iplc, 0x0224, 12, buf);

#if 0
	printf("StaticCommands     :   0x%04x\n", (buf[0] << 8) | buf[1]);
	printf("StaticCommandsEnd  :   0x%04x\n", (buf[2] << 8) | buf[3]);
	printf("StaticTimeTable    :   0x%04x\n", (buf[4] << 8) | buf[5]);
	printf("StaticTimeTableEnd :   0x%04x\n", (buf[6] << 8) | buf[7]);
	printf("StaticCommandSet   :   0x%04x\n", (buf[8] << 8) | buf[9]);
	printf("StaticCommandSetEnd:   0x%04x\n", (buf[10] << 8) | buf[11]);
#endif

	/* Turn write protect back on */
	wp &= ~0x08;
	ibios_write_memory(iplc, 0x016b, 1, &wp);


	/* Send an ibios event to initialize the timers */
	printf("Refresh timer\n");
	ibios_timer_refresh(iplc);

	return 0;
}


/*
 * load_timers
 *
 * Load timers from a file. The file format looks like this:
 *
 *   device name=<time>,<device_id>:<cmd>,<days>*<NOSEC>
 */
void load_timers(ilib_t *iplc, char *fname)
{
	FILE *fp;
	char line[256];
	timer_timerec_t *timers = NULL, *tim, *ptr = NULL;
	unsigned char group_id = 0x01;

	fp = fopen(fname, "r");
	if (fp == NULL) {
		printf("Can't open %s\n", fname);
		return;
	}

	while (getline(fp, line, 256) != EOF) {
		/* Initialize timer record with constants */
		if ((line[0] == '#') || (line[0] == '\n') || (line[0] == '\r')) {
			continue;
		}

		if (strncasecmp(line, "timer:", 6) == 0) {
			tim = parse_timer(line);

			/* Assign groups starting from 0 */
			tim->cmd_group.id = group_id++;

			if (timers == NULL) {
				timers = tim;
				ptr = tim;
			} else {
				ptr->next = tim;
				ptr = tim;
			}
		}
	}

	print_timer_record(timers);
	write_timers(iplc, timers);

	fclose (fp);
}


/*
 * Parse a line from the file that contains a timer record.
 */
timer_timerec_t *parse_timer(char *line)
{
	char *t, *o;
	int h, m;
	unsigned char id[3];
	int cmd;
	timer_timerec_t *tim;

	tim = (timer_timerec_t *)malloc(sizeof(timer_timerec_t));
	tim->flag0 = T_ACTIVE;
	tim->rpt = 0x00;
	tim->rpts = 0x00;
	tim->cmd_group_id = 0x0000;
	tim->cmd_group.count = 1;
	tim->cmd_group.pos = 0;
	tim->cmd_group.cmds[0].type = T_INSTEON;
	tim->cmd_group.cmds[0].len = 6;
	tim->cmd_group.cmds[0].data = (unsigned char *)malloc(6);
	tim->next = NULL;


	strtok(line, "=,*");
	/* Skip device name for now */
	t = strtok(NULL, "=,*");
	if (t == NULL) {
		printf("What happened %s\n", line);
		return NULL;
	}

	/* Check for sunset/sunrise */
	if (strncasecmp(t, "sunset", 6) == 0) {
		tim->type = T_SUNSET;
		tim->time = atoi(t+6);
	} else if (strncasecmp(t, "sunrise", 7) == 0) {
		tim->type = T_SUNRISE;
		tim->time = atoi(t+7);
	} else {
		tim->type = T_ABSOLUTE;;
		h = atoi(t);
		o = index(t, ':');
		o++;
		m = atoi(o);
		tim->time = (h * 60) + m;
	}
	t = strtok(NULL, "=,*");
	/*
	 * To field:
	 *   For direct messages, this is the device id.
	 *   For group message this is 0x00 0x00 <group>
	 */
	memcpy(id, str_id_to_hex(t), 3);
	tim->cmd_group.cmds[0].data[0] = id[0];
	tim->cmd_group.cmds[0].data[1] = id[1];
	tim->cmd_group.cmds[0].data[2] = id[2];
	if ((id[0] == 0) && (id[1] == 0)) {
		/* Assuming this is a group entry */
		tim->cmd_group.cmds[0].data[3] = MSG_STANDARD | MSG_GROUP |
			MSG_2_HOP;
	} else {
		/* Standard message */
		tim->cmd_group.cmds[0].data[3] = MSG_STANDARD | MSG_DIRECT |
			MSG_1_HOP;
	}
	o = t + 9;
	cmd = command_to_num(o);
	if (cmd != -1) {
		tim->cmd_group.cmds[0].data[4] = cmd;
		if (cmd == 0x11) { /* on */
			tim->cmd_group.cmds[0].data[5] = 0xff;
		} else if (cmd == 0x13) { /* off */
			tim->cmd_group.cmds[0].data[5] = 0x00;
		}
	} else {
		tim->cmd_group.cmds[0].data[4] = command_to_num("ON");
		tim->cmd_group.cmds[0].data[5] = (255 * atoi(o)) / 100;
	}

	/* Will need to convert this to a bitmap */
	t = strtok(NULL, "=,*");
	tim->dow = cvt_days(t);

	/* Security bit */
	t = strtok(NULL, "=,*");
	if (t) {
		if (strncasecmp(t, "NOSEC", 5) == 0) {
			tim->flag0 &= ~T_SECURITY;
		}
	} else {
		tim->flag0 |= T_SECURITY;
	}

	return tim;
}

/*
 * clear_timers
 *
 * Clear the timer records from the PLC
 */
void clear_timers(ilib_t *iplc)
{
#if 0
	unsigned short t_addr = 0x1800; /* Start of timers address */
	unsigned char buf[11];

	/* write an end of timers marker */
	memset(buf, 0xff, 11);
	ibios_write_memory(iplc, t_addr, 11, buf);
#endif
	/* ibios_erase_timers(iplc); */
	ibios_reset_timers(iplc);

	return;
}


/*
 * free_timers
 *
 * Free memory allocated for a list of timers
 */
void free_timers(timer_timerec_t *timers)
{
	timer_timerec_t *tmp;

	while (timers != NULL) {
		tmp = timers->next;
		free(timers);
		timers = tmp;
	}
}


/*
 * add_timer
 *
 * Add a timer to the end of the list
 */
void add_timer(timer_timerec_t *list, timer_timerec_t *new_timer)
{
	timer_timerec_t *last;

	if (list == NULL) {
		printf("add_timer: timer list is NULL!\n");
		return;
	}

	new_timer->next = NULL;

	while (list != NULL) {
		if ((list->type == new_timer->type) &&
				(list->time == new_timer->time) &&
				(list->flag0 == new_timer->flag0) &&
				(list->dow == new_timer->dow)) {
			/* Duplicate timer, add command to list */
			list->cmd_group.cmds[list->cmd_group.count] =
				new_timer->cmd_group.cmds[0];
			list->cmd_group.count++;
			/*
			 * FIXME: The memory allocated for the new timer should
			 * be freed.  Probably by the caller but this will have
			 * to return something to indicate that.
			 */
			return;
		}
		last = list;
		list = list->next;
	}

	/* Add timer to end of list */
	new_timer->tid = last->tid + 1;

	last->next = new_timer;
}

/*
 * delete_timer
 *
 * Delete a timer from the list.
 */
void delete_timer(timer_timerec_t **list, int tid, int cid)
{
	timer_timerec_t *prev = NULL;
	timer_timerec_t *node;
	int i;

	node = *list;
	while (node) {
		if (node->tid == tid) {
			if (node->cmd_group.count > 1) {
				/* Multiple commands at this time, only delete one */
				for (i = cid; i < node->cmd_group.count; i++) {
					node->cmd_group.cmds[i] = node->cmd_group.cmds[i+1];
				}
				node->cmd_group.count--;
				break;
			} else {
				/* Remove node */
				if (prev) {
					prev->next = node->next;
				} else {
					*list = node->next;
				}
				free (node);
				break;
			}
		}
		prev = node;
		node = node->next;
	}
}


/*
 * cvt_days
 *
 * Convert a string of day abrevations to a bitmap as used in the timer
 * code.
 */
unsigned char cvt_days (char *days)
{
	unsigned char day_bm = 0;

	if (strstr(days, "M") != NULL) {
		day_bm |= T_MONDAY;
	}

	if (strstr(days, "Tu") != NULL) {
		day_bm |= T_TUESDAY;
	}

	if (strstr(days, "W") != NULL) {
		day_bm |= T_WEDNESDAY;
	}

	if (strstr(days, "Th") != NULL) {
		day_bm |= T_THURSDAY;
	}

	if (strstr(days, "F") != NULL) {
		day_bm |= T_FRIDAY;
	}

	if (strstr(days, "Sa") != NULL) {
		day_bm |= T_SATURDAY;
	}

	if (strstr(days, "Su") != NULL) {
		day_bm |= T_SUNDAY;
	}

	return day_bm;
}


void timer_locations(ilib_t *iplc)
{
	unsigned char buffer[20];

	ibios_read_memory(iplc, 0x222, 14, buffer);

	printf("TimerVariableTable :   0x%04x\n", (buffer[0] << 8) | buffer[1]);
	printf("StaticCommands     :   0x%04x\n", (buffer[2] << 8) | buffer[3]);
	printf("StaticCommandsEnd  :   0x%04x\n", (buffer[4] << 8) | buffer[5]);
	printf("StaticTimeTable    :   0x%04x\n", (buffer[6] << 8) | buffer[7]);
	printf("StaticTimeTableEnd :   0x%04x\n", (buffer[8] << 8) | buffer[9]);
	printf("StaticCommandSet   :   0x%04x\n", (buffer[10] << 8) | buffer[11]);
	printf("StaticCommandSetEnd:   0x%04x\n", (buffer[12] << 8) | buffer[13]);
}

