/*
 * $RCSfile: scenes.c,v $  $Revision: 1.30 $  $Name:  $
 * $Id: scenes.c,v 1.30 2007/02/20 22:42:49 bpaauwe Exp $
 * $Author: bpaauwe $
 * $Date: 2007/02/20 22:42:49 $
 * ----------------------------------------------------------------------------
 *
 *  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:
 *    A program to build a copy of device link tables.
 * ----------------------------------------------------------------------------
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <memory.h>
#include <ctype.h>
#include <time.h>
#include "libilib/ilib.h"
#include "scenes.h"
#include "globals.h"
#include "timers.h"

extern scene_t *scene_list;
extern device_t device_array[2000];
extern int last_device;

extern int file_changed(int i);
extern void status_message(char *msg);
extern void progress_bar_update();

#if 0

	if ((iplc = iusb_initialize()) != NULL) {
		/* Send some test commands */
		if (ibios_get_version(iplc, &test[0], buffer) == 0) {
			printf("PLC ID: %s   Firmware version %02x\n", buffer, test[0]);
		} else {
			printf("Command failed\n");
		}

		get_device_list(); /* Get devices (name, type, id) from file */
		get_scenes(); /* get scene definitions from file */
		build_tables(); /* build all the device link tables */

		iusb_close(iplc);
	}
#endif


/*
 * get_device_link
 *
 * link_num is zero based.
 */
int get_device_link(ilib_t *iplc, unsigned char *dev_id,
		int link_num, link_record_t *entry)
{
	unsigned short laddr;
	int ret;
	int i;

	memset(entry, 0, sizeof(link_record_t));

	/* Link data base grows downward from 0x0ff8 and are 8 bytes each */
	laddr = 0x0ff8 - (link_num * 8);

	if ((ret = insteon_read_byte(iplc, dev_id, laddr++)) != -1) {
		entry->flags = ret & 0xff;
		if (!(ret & 0x80)) { /* record not in use */
			return 0;
		} else if ((ret & 0x02) == ret) { /* record was in use, but now deleted */
			entry->group_number = 0;
			entry->device_id[0] = 0;
			entry->device_id[1] = 0;
			entry->device_id[2] = 0;
			entry->data1 = 0;
			entry->data2 = 0;
			entry->data3 = 0;
			return entry->flags;
		}

		if ((ret = insteon_read_byte(iplc, dev_id, laddr++)) != -1) {
			entry->group_number = ret & 0xff;
		} else {
			return -1;
		}
		for (i = 0; i < 3; i++) {
			if ((ret = insteon_read_byte(iplc, dev_id, laddr++)) != -1) {
				entry->device_id[i] = ret & 0xff;
			} else {
				return -1;
			}
		}
		if ((ret = insteon_read_byte(iplc, dev_id, laddr++)) != -1) {
			entry->data1 = ret & 0xff;
		} else {
			return -1;
		}
		if ((ret = insteon_read_byte(iplc, dev_id, laddr++)) != -1) {
			entry->data2 = ret & 0xff;
		} else {
			return -1;
		}
		if ((ret = insteon_read_byte(iplc, dev_id, laddr++)) != -1) {
			entry->data3 = ret & 0xff;
		} else {
			return -1;
		}
	} else {
		return -1;
	}

	return entry->flags;
}


/*
 * set_device_link
 *
 * write a new device link into a device's link table.
 */
int set_device_link(ilib_t *iplc, unsigned char *dev_id,
		int link_num, link_record_t *entry)
{
	unsigned short laddr;
	int ret;

	/* Link data base grows downward from 0x0ff8 and are 8 bytes each */
	laddr = 0x0ff8 - (link_num * 8);

	if ((ret = insteon_write_byte(iplc, dev_id, laddr++, entry->flags)) == -1) {
		printf("set_device_link: Failed to send message to %02x.%02x.%02x\n",
				dev_id[0], dev_id[1], dev_id[2]);
		return 0;
	}
	progress_bar_update();

	ret = 0;
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->group_number);
	progress_bar_update();
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->device_id[0]);
	progress_bar_update();
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->device_id[1]);
	progress_bar_update();
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->device_id[2]);
	progress_bar_update();
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->data1);
	progress_bar_update();
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->data2);
	progress_bar_update();
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->data3);
	progress_bar_update();
	if (ret == -1) {
		printf("set_device_link: Failed to write link %d to %02x.%02x.%02x\n",
				link_num, dev_id[0], dev_id[1], dev_id[2]);
	}
	return ret;
}


/*
 * clear_device_link
 *
 * Clear a device link in a device's link table.
 */
int clear_device_link(ilib_t *iplc, unsigned char *dev_id, int link_num)
{
	unsigned short laddr;
	int ret;

	/* Link data base grows downward from 0x0ff8 and are 8 bytes each */
	laddr = 0x0ff8 - (link_num * 8);

	/* Write the record has been used before bit in the flag */
	if ((ret = insteon_write_byte(iplc, dev_id, laddr, 0x00)) == -1) {
		printf("clear_device_link: Failed to clear link # %d on "
				"%02x.%02x.%02x\n", link_num, dev_id[0], dev_id[1], dev_id[2]);
		return -1;
	}
	return 0;
}


/*
 * get_memory_dump
 *
 * Dump a device's memory from saddr to saddr+size. Currently, prints
 * results to the console.
 */
int get_memory_dump(ilib_t *iplc, unsigned char *dev_id, unsigned short saddr,
		int size)
{
	unsigned short laddr;
	int ret;

	laddr = saddr;
	ret = salad_send_message(iplc, SET_ADDR_MSB, (laddr >> 8) & 0xff, dev_id);
	for (; laddr < (size + saddr); laddr++) {
		ret = insteon_read_byte(iplc, dev_id, laddr);
		if (((laddr % 16) == 0) || (laddr == saddr)) {
			printf("\n0x%04x: ", laddr);
		}
		if (ret != -1) {
			printf("%02x ", (ret & 0xff));
		} else {
			printf("-- ");
		}
	}
	printf("\n");
	return 0;
}


/*
 * get_scene_info()
 *
 * Read the scene file and fill in all the data structures.
 */
void get_scene_info(char *fname)
{
	FILE *fp;
	char line[256];
	int len;
	char *tok;
	char *tstr;
	scene_t *current = NULL;
	scene_t *last_scene = NULL;
	device_list_t *dev;
	int scene_index = 0;

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

	while ((len = getline(fp, line, 256)) != EOF) {
		/* Really simple parsing for now */
		if (len == 0) {
			/* skip */
		} else if (line[0] == '#') { /* comment */
			/* skip */
		} else if (strncasecmp(line, "scene", 5) == 0) {
			/* allocate new scene record */
			if ((current = (scene_t *)malloc(sizeof(scene_t))) == NULL) {
				printf("Failed to allocate memory!\n");
				fclose(fp);
				return;
			}
			current->responders = 0;
			current->next = NULL;
			current->index = scene_index++;
			current->responder_list = NULL;
			if (scene_list == NULL) {
				scene_list = current;
			} else {
				last_scene->next = current;
			}
			last_scene = current;

			tok = index(line, '=');
			if (tok++) {
				/* copy name into scene record */
				current->name = (char *)malloc(strlen(tok)+1);
				strcpy(current->name, tok);
			}
		} else if (strncasecmp(line, "master", 6) == 0) {
			tok = index(line, '=');
			if ((tok++) && (current)) {
				/* master device id */
				current->controller = find_device((unsigned char *)tok);
				if (current == NULL) {
					printf("Unable to find device id %s\n", tok);
				}
			}
		} else if (strncasecmp(line, "group", 5) == 0) {
			tok = index(line, '=');
			if ((tok++) && (current)) {
				/* group = atoi(tok);*/
				current->group = atoi(tok);
			}
		} else if (strncasecmp(line, "responder", 9) == 0) {
			tok = index(line, '=');
			if ((tok++) && (current)) {
				/* parse responder line */
				dev = (device_list_t *)malloc(sizeof(device_list_t));
				tstr = strtok(tok, " ");
				dev->device = find_device((unsigned char *)tstr);
				tstr = strtok(NULL, " ");
				dev->link.data1 = ahtoi(tstr);
				tstr = strtok(NULL, " ");
				dev->link.data2 = ahtoi(tstr);
				tstr = strtok(NULL, " ");
				dev->link.data3 = ahtoi(tstr);
				if (current->controller) {
					memcpy(dev->link.device_id, current->controller->device_id,
							3);
				}
				dev->link.group_number = current->group;
				dev->link.flags = 0xa2; /* this is alwasy a slave record */

				dev->next = current->responder_list;
				current->responder_list = dev;
				current->responders++;
			}
		} else if (strncasecmp(line, "device_id", 9) == 0) {
			/* printf("Reading device (%s)\n", line); */
			tok = index(line, '=');
			if (tok++) {
				memcpy(device_array[last_device].device_id,
						str_id_to_hex(tok), 3);
				device_array[last_device].device_index = last_device;
				device_array[last_device].changed = 0;
			}
		} else if (strncasecmp(line, "device_name", 11) == 0) {
			tok = index(line, '=');
			if (tok++) {
				last_device++;
				device_array[last_device].device_name =
					(char *)malloc(strlen(tok) + 1);
				if (device_array[last_device].device_name != NULL) {
					strcpy(device_array[last_device].device_name, tok);
				} else {
					printf("Memory allocation failed\n");
				}
			}
		} else if (strncasecmp(line, "device_type", 11) == 0) {
			tok = index(line, '=');
			if (tok++) {
				device_array[last_device].device_type = atoi(tok);
			}
		} else if (strncasecmp(line, "device_local_level", 18) == 0) {
			tok = index(line, '=');
			if (tok++) {
				device_array[last_device].local_on_level = atoi(tok);
			}
		} else if (strncasecmp(line, "device_local_ramp", 17) == 0) {
			tok = index(line, '=');
			if (tok++) {
				device_array[last_device].local_ramp_rate = atoi(tok);
			}
		} else if (strncasecmp(line, "device_needs_update", 19) == 0) {
			tok = index(line, '=');
			if (tok++) {
				device_array[last_device].changed = atoi(tok);
			}
		} else {
			/* skip for now */
		}
	}

	fclose(fp);
}


/*
 * save_scene_info
 *
 * Dump all the scene and device info to a file.
 */
void save_scene_info(char *fname)
{
	FILE *fp;
	int i;
	scene_t *scene;
	device_t *master;
	device_list_t *respd;
	timer_timerec_t *tl;


	fp = fopen(fname, "w");
	if (fp == NULL) {
		printf("Failed to open %s\n", fname);
		return;
	}

	fprintf(fp, "#\n");
	fprintf(fp, "# ilink scene configuration file.\n");
	fprintf(fp, "#\n");

	for (i = 0; i <= last_device; i++) {
		fprintf(fp, "device_name=%s\n", device_array[i].device_name);
		fprintf(fp, "device_id=%02x.%02x.%02x\n",
				device_array[i].device_id[0],
				device_array[i].device_id[1],
				device_array[i].device_id[2]);
		fprintf(fp, "device_type=%d\n", device_array[i].device_type);
		fprintf(fp, "device_local_level=%d\n", device_array[i].local_on_level);
		fprintf(fp, "device_local_ramp=%d\n", device_array[i].local_ramp_rate);
		fprintf(fp, "device_needs_update=%d\n", device_array[i].changed);
		fprintf(fp, "\n");
	}

	fprintf(fp, "\n");

	scene = scene_list;
	while (scene) {
		master = scene->controller;
		respd = scene->responder_list;
		if (!master || !respd) {
			printf("Trying to save an invalid scene %s\n", scene->name);
			scene = scene->next;
			continue;
		}
		fprintf(fp, "scene=%s\n", scene->name);
		fprintf(fp, "group=%d\n", scene->group);
		fprintf(fp, "master=%02x.%02x.%02x\n",
				master->device_id[0],
				master->device_id[1],
				master->device_id[2]);

		i = 0;
		while(respd) {
			fprintf(fp, "responder%d=%02x.%02x.%02x 0x%02x 0x%02x 0x%02x\n", i,
					respd->device->device_id[0],
					respd->device->device_id[1],
					respd->device->device_id[2],
					respd->link.data1, respd->link.data2, respd->link.data3);
			respd = respd->next;
			i++;
		}
		fprintf(fp, "\n");
		scene = scene->next;
	}

	/* Write current timer list */
	if (timers) {
		tl = timers;
		fprintf(fp, "[timers]\n");
		while (tl) {
			if ((tl->flag0 & T_ACTIVE) == 0) {
				/* Skip records that aren't active for now */
				continue;
			}

			fprintf(fp, "timer-%03d=", tl->tid);

			/* What time? */
			if (tl->type == T_ABSOLUTE) {
				fprintf(fp, "%02d:%02d,", (tl->time / 60),
						((tl->time - (tl->time / 60) * 60)));
			} else if (tl->type == T_SUNRISE) {
				fprintf(fp, "Sunrise+%d,", tl->time);
			} else if (tl->type == T_SUNSET) {
				fprintf(fp, "Sunset+%d,", tl->time);
			} else {
				fprintf(fp, "Unknown,");
			}

			/*
			 * FIXME:
			 * Currently this output format only supports a command count
			 * of 1. At some point this all needs to be fixed to support
			 * any number of commands per timer.
			 */
			for (i = 0; i < tl->cmd_group.count; i++) {
				if (tl->cmd_group.cmds[i].type != T_INSTEON) {
					printf("Skipping command, only Insteon commands are "
							"supported at this time\n");
				} else {
					/* Print device ID or group number */
					fprintf(fp, "%02x.%02x.%02x:",
							tl->cmd_group.cmds[i].data[0],
							tl->cmd_group.cmds[i].data[1],
							tl->cmd_group.cmds[i].data[2]);
					if (tl->cmd_group.cmds[i].data[4] == ON) {
						fprintf(fp, "%d%%,",
								(tl->cmd_group.cmds[i].data[5] * 100) / 255);
					} else {
						fprintf(fp, "OFF,");
					}
				}
			}

			/* What days? */
			if (tl->dow & T_SATURDAY) {
				fprintf(fp, "Sa");
			}
			if (tl->dow & T_SUNDAY) {
				fprintf(fp, "Su");
			}
			if (tl->dow & T_MONDAY) {
				fprintf(fp, "M");
			}
			if (tl->dow & T_TUESDAY) {
				fprintf(fp, "Tu");
			}
			if (tl->dow & T_WEDNESDAY) {
				fprintf(fp, "W");
			}
			if (tl->dow & T_THURSDAY) {
				fprintf(fp, "Th");
			}
			if (tl->dow & T_FRIDAY) {
				fprintf(fp, "F");
			}

			/* Security? */
			if (tl->flag0 & T_SECURITY) {
				fprintf(fp, "*SEC\n");
			} else {
				fprintf(fp, "*NOSEC\n");
			}

			tl = tl->next;
		}
	}

	fclose (fp);
	file_changed(-1);
}


/*
 * delete_scene
 *
 * Delete a scene from the scene list. Update all the following scene index
 * values.
 */
void delete_scene(int scene_index)
{
	scene_t *s, *tmp, *prev = NULL;
	device_list_t *resp, *rnext;
	int rnum = 0;

	s = scene_list;
	while (s) {
		if ((!rnum) && (s->index == scene_index)) {
			file_changed(1);
			rnum = scene_index;
			tmp = s->next;
			if (tmp) {
				tmp->index = rnum;
			}
			if (prev) {
				prev->next = s->next;
			} else {
				scene_list = s->next;
			}

			/* Free memory allocated to scene */
			free(s->name);
			resp = s->responder_list;
			while (resp) {
				rnext = resp->next;
				free(resp);
				resp = rnext;
			}
			free(s);
			s = tmp;
			rnum++;
			continue;
		} else if (rnum) {
			s->index = rnum;
			rnum++;
		}
		prev = s;
		s = s->next;
	}
}


/*
 * build_tables
 *
 * Take all the scenes and build all the device link tables
 */
void build_tables()
{
	scene_t *scene;
	device_t *master;
	link_record_t *link;
	device_list_t *respd;

	clear_tables();

	/* walk the scene list */
	scene = scene_list;
	while (scene) {
		if (scene->controller == NULL) {
			printf("Scene controller is NULL!!!, this scene should be gone!\n");
			scene = scene->next;
			continue;
		}
		/* If controller is a switch, create a two way link? */
		if ((scene->controller->device_type == 0x0101) ||
				(scene->controller->device_type == 0x20a)) {
		}
		master = scene->controller;
		respd = scene->responder_list;
		while (respd) {
			if (respd->device == NULL) {
				printf("    responder device is NULL!!!\n");
				respd = respd->next;
				continue;
			}
			if (scene->controller->device_type != 0x0000) {
				/* build controler link entries for each responder */

				link = &master->db_records[master->num_of_entries];
				link->flags = 0xe2; /* master */
				link->group_number = scene->group;
				memcpy(link->device_id, respd->device->device_id, 3);
				link->data1 = scene->group;
				link->data2 = 0;
				link->data3 = scene->group;
				master->num_of_entries++;
			} else {
			}

			/* Add controller record to responder's table */
			link = &(respd->device->db_records[respd->device->num_of_entries]);
			memcpy(link, &(respd->link), sizeof(link_record_t));
			respd->device->num_of_entries++;

			respd = respd->next;
		}
		scene = scene->next;
	}
}


/*
 * clear_tables
 *
 * Clear out the link tables for all devices.
 */
void clear_tables()
{
	int i;

	for (i = 0; i <= last_device; i++) {
		device_array[i].num_of_entries = 0;
	}
}


/*
 * free_tables
 *
 * Free all the memory allocated for the scene tables and device lists.
 * This should be done before loading a new scene info file or when creating
 * a new installation.
 */
void free_tables()
{
	free_scene_list();
	free_device_list();
}

/*
 * free_scene_list
 *
 * Free all the memory allocated for scenes.
 */
void free_scene_list()
{
	scene_t *scene, *scene_next;
	device_list_t *respd, *respd_next;

	/* walk the scene list and free each scene */
	scene = scene_list;
	while (scene) {
		respd = scene->responder_list;
		/* walk the resonders list and free each responder */
		while (respd) {
			respd_next = respd->next;
			free(respd);
			respd = respd_next;
		}
		scene_next = scene->next;
		free(scene->name);
		free(scene);
		scene = scene_next;
	}

	scene_list = NULL;
}

/*
 * free_device_list
 *
 * Free all the memory allocated to the device list.
 */
void free_device_list()
{
	int i;

	/* Free memory allocated for device names */
	for (i = 0; i <= last_device; i++) {
		if (device_array[i].device_name) {
			free(device_array[i].device_name);
		}
		memset(&(device_array[i]), 0, sizeof(device_t));
	}
	last_device = -1;
}

/*
 * program_device
 *
 * program all the link table entries for a device. One issue is how
 * to clear out the existing links.
 */
int program_device(ilib_t *iplc, device_t *device)
{
	float wait;
	int i, g;
	link_record_t empty;
	int group_cnt[256];
	char msg[100];
	int ret;
	unsigned char class;

	memset(&empty, 0, sizeof(link_record_t));
	memset(group_cnt, 0, sizeof(group_cnt));

	/*
	 * Need to check device type. If it is a PLC then programming is
	 * different.
	 */
	if ((device->device_type == 0x0000) || (device->device_type == 0x0001)) {
		for (i = 0; i < (device->num_of_entries); i++) {
			printf("   programming entry %d %02x.%02x.%02x 0x%02x 0x%02x "
						"0x%02x\n", i,
					device->db_records[i].device_id[0],
					device->db_records[i].device_id[1],
					device->db_records[i].device_id[2],
					device->db_records[i].data1,
					device->db_records[i].data2,
					device->db_records[i].data3);

			class = ((device->db_records[i].flags & 0x40) >> 6) | 0x02;
			iplc_add_id(iplc, device->db_records[i].device_id,
					device->db_records[i].group_number, class);
		}
	} else {  /* Remote device */
		wait = (device->num_of_entries + 10) * 8;
		if ( wait > 60) {
			/* Put this in status bar */
			printf("Programming %s, this will take about %4.1f minutes\n",
					device->device_name, (wait / 60));
		} else {
			printf("Programming %s, this will take about %2.0f seconds\n",
					device->device_name, wait);
		}

		for (i = 0; i < (device->num_of_entries); i++) {
			/*
			sprintf(msg, "Programming %s  %d%% complete", device->device_name,
					((i * 100) / device->num_of_entries));
			status_message(msg);
			*/
			printf("   programming entry %d %02x.%02x.%02x 0x%02x 0x%02x "
						"0x%02x\n", i,
				device->db_records[i].device_id[0],
				device->db_records[i].device_id[1],
				device->db_records[i].device_id[2],
				device->db_records[i].data1,
				device->db_records[i].data2,
				device->db_records[i].data3);
			set_device_link(iplc, device->device_id, i, &device->db_records[i]);
			group_cnt[device->db_records[i].group_number]++;

			usleep(50000);
		}

		/* Need a function to clear the flag byte of remaining links */
		for (; i < 100; i++) {
			clear_device_link(iplc, device->device_id, i);
			progress_bar_update();
		}

		/*
		 * If the device is a ControlLinc, we need to update some EE memory
		 * locations to activate the buttons.
		 *   0x03 for button 1 (group 1) with the number of linked devices
		 *   0x04 for button 2 (group 2) with the number of linked devices
		 *   0x05 for button 3 (group 3) with the number of linked devices
		 *   0x06 for button 4 (group 4) with the number of linked devices
		 *   0x07 for button 5 (group 5) with the number of linked devices
		 */
		if (device->device_type == 0x0004) { /* ControlLinc */
			printf("Programming magic button bytes.\n");
			for (g = 1; g <= 5; g++) {
				/* Button 1 magic byte is 0x0003 (group# +2) */
				ret = insteon_write_byte(iplc, device->device_id, (g+2),
						group_cnt[g]);
				progress_bar_update();
				if (ret == -1) {
					printf("program_device: Failed to program magic button "
							"byte for button %d\n", g);
				}
			}
		}
	}
	sprintf(msg, "Programming %s finished.", device->device_name);
	status_message(msg);
	return 0;
}

/*
 * find_device
 *
 * Given a device id (as a string "xx.xx.xx" return a pointer to the
 * maching device.
 */
device_t *find_device(unsigned char *id)
{
	unsigned char *dev_id;
	int i;

	/* Check to see if this looks like a device id string */
	if (id[2] == '.') {
		dev_id = str_id_to_hex((char *)id);
	} else {
		dev_id = id;
	}

	for (i = 0; i <= last_device; i++) {
		if ((device_array[i].device_id[0] == dev_id[0]) &&
				(device_array[i].device_id[1] == dev_id[1]) &&
				(device_array[i].device_id[2] == dev_id[2])) {
			return &device_array[i];
		}
	}
	return NULL;
}

/*
 * find_scene
 *
 * Look up a scene given a scene index number.
 */
scene_t *find_scene(int index)
{
	scene_t *scene = NULL;

	scene = scene_list;
	while(scene) {
		if (scene->index == index) {
			return scene;
		}
		scene = scene->next;
	}

	return NULL;
}


/*
 * get_scene_by_name
 *
 * find a scene entry with a specific name.
 */
scene_t *get_scene_by_name(char *scene_name)
{
	scene_t *scene = NULL;

	scene = scene_list;
	while(scene) {
		if (strcmp(scene_name, scene->name) == 0) {
			return scene;
		}
		scene = scene->next;
	}

	return NULL;
}


/*
 * get_scene_by_group_master
 *
 * find a scene entry with a specific group number / master controller combo.
 */
scene_t *get_scene_by_group_master(int group, unsigned char *master)
{
	scene_t *scene = NULL;

	scene = scene_list;
	while(scene) {
		if ((scene->group == group) &&
				(memcmp(scene->controller->device_id, master, 3) == 0)) {
			return scene;
		}
		scene = scene->next;
	}

	return NULL;
}

/*
 * get_scene_by_group_plc
 *
 * find a scene entry with a specific group number controlled by the PLC.
 */
int get_scene_by_group_plc(ilib_t *iplc, int group)
{
	scene_t *scene = NULL;
	int index = 0;

	scene = scene_list;
	while(scene) {
		if (is_plc(iplc, scene->controller->device_id)) {
			if (scene->group == group) {
				return index;
			}
			index++;
		}
		scene = scene->next;
	}

	return index;
}

/*
 * get_plc_scene_by_index
 *
 * find the group number of a PLC controlled scene based on index in the
 * combo box.
 */
int get_plc_scene_by_index(ilib_t *iplc, int index)
{
	scene_t *scene = NULL;
	int cnt = 0;

	scene = scene_list;
	while(scene) {
		if (is_plc(iplc, scene->controller->device_id)) {
			if (cnt == index) {
				return scene->group;
			}
			cnt++;
		}
		scene = scene->next;
	}

	return 0;
}



/*
 * get_device_name
 *
 * Given a device ID, return the name assigned in the device_db.inf file
 */
char *get_device_name(unsigned char *dev_id)
{
	int i;

	for (i = 0; i <= last_device; i++) {
		if ((device_array[i].device_id[0] == dev_id[0]) &&
				(device_array[i].device_id[1] == dev_id[1]) &&
				(device_array[i].device_id[2] == dev_id[2])) {
			return device_array[i].device_name;
		}
	}
	return NULL;
}


/*
 * delete_device
 *
 * Remove a device from the device array and move all the following devices
 * up.
 */
void delete_device(int dev_index)
{
	int i;

	for (i = dev_index; i < last_device; i++) {
		memcpy(&device_array[i], &device_array[i+1], sizeof(device_t));
		device_array[i].device_index = i;
	}

	last_device--;
}

/*
 * delete_device_from_scenes
 *
 * Check all the scenes and remove this specific device from them.
 */
void delete_device_from_scenes(int dev_index)
{
	scene_t *scene, *p_scene = NULL;
	device_list_t *respd, *prev;

	scene = scene_list;
	while (scene) {
		if (scene->controller == &device_array[dev_index]) {
			/* Deleting controller, so delete this scene. */
			if (p_scene) {
				p_scene->next = scene->next;
			} else {
				scene_list = scene->next;
			}
			scene = scene->next;
			free(scene);
			file_changed(1);
			continue;
		}
		respd = scene->responder_list;
		prev = NULL;
		while (respd) {
			if (respd->device == &device_array[dev_index]) {
				if (prev) {
					printf("Removing responder record\n");
					prev->next = respd->next;
					free(respd);
					respd = prev->next;
					file_changed(1);
				} else {
					scene->responder_list = respd->next;
					free(respd);
					respd = scene->responder_list;
					file_changed(1);
				}
			} else {
				prev = respd;
				respd = respd->next;
			}
		}
		p_scene = scene;
		scene = scene->next;
	}
}


/*
 * update_device_links
 *
 * Query the device and read it's link table. Compare the internal table
 * with the expected results and make any necessary changes.  This should
 * make the device match our internal representation.
 */
void update_device_links(ilib_t *iplc, device_t *device)
{
	int l = 0;
	int d, i;
	int s;
	int found;

	/* Read device link table into memory. */
	while (get_device_link(iplc, device->device_id, l, &(device->current_lt[l]))
			> 0) {
		l++;
	}

	/* Delete any links that shouldn't be here */
	for (i = 0; i < l; i++) {
		found = 0;
		for (d = 0; d < MAX_LINKS; d++) {
			if (memcmp(&(device->current_lt[i]), &(device->db_records[d]),
					sizeof(link_record_t)) == 0) {
				found = 1;
				break;
			}
		}
		if (!found) {
			printf("%02x.%02x.%02x: Deleting link %d ....\n",
					device->device_id[0], device->device_id[1],
					device->device_id[2], i);
			device->current_lt[l].flags = 0x02;
			/* clear_device_link(iplc, device->device_id, l); */
		}
	}

	/* Add any missing links */
	for (i = 0; i < MAX_LINKS; i++) {
		found = 0;

		/* Skip empty records in link db. */
		if (device->db_records[i].flags == 0x00) {
			continue;
		}

		for (d = 0; d < l; d++) {
			if (memcmp(&(device->current_lt[d]), &(device->db_records[i]),
					sizeof(link_record_t)) == 0) {
				found = 1;
				break;
			}
		}
		if (!found) {
			printf("%02x.%02x.%02x: Adding link %d ....\n",
					device->device_id[0], device->device_id[1],
					device->device_id[2], i);
			/* Scan through the list looking for any deleted records to reuse */
			for (s = 0; s < l; s++) {
				if (device->current_lt[s].flags == 0x02) { /* deleted record */
					printf("    Add at postion %d\n", s);
					/*
					set_device_link(iplc, device->device_id, s,
							&(device->db_records[i]));
							*/

					break;
				}
			}
			if (s == l) { /* no deleted, add at end */
					printf("    Add at postion %d (end)\n", s);
				/*
				set_device_link(iplc, device->device_id, s,
						&(device->db_records[i]));
						*/
			}

		}
	}
}



/*
 * verify_device_links
 *
 * Query the device and read it's link table. Then compare the link
 * table with the one currently in memory. If the match, great, if not
 * then do something.
 */
void verify_device_links(ilib_t *iplc, device_t *device)
{
	int l = 0;
	int d;
	unsigned char internal_ok[MAX_LINKS];
	unsigned char external_ok[MAX_LINKS];
	int fix = 0;

	memset(internal_ok, 0, MAX_LINKS);
	memset(external_ok, 0, MAX_LINKS);
	printf("Querying %s\n", device->device_name);
	while (get_device_link(iplc, device->device_id, l, &(device->current_lt[l]))
			> 0) {
		/*
		printf("xx: 0x%02x %d %02x.%02x.%02x\n", device->current_lt[l].flags,
				device->current_lt[l].group_number,
				device->current_lt[l].device_id[0],
				device->current_lt[l].device_id[1],
				device->current_lt[l].device_id[2]);
		*/
		l++;
	}
	for (; l < MAX_LINKS; l++) {
		device->current_lt[l].flags = 0;
	}

	/* Now compare the data in current_lt with the data in db_records */
	printf("Comparing link data...\n");
	for (l = 0; l < MAX_LINKS; l++) {
		for (d = 0; d < MAX_LINKS; d++) {
			if (memcmp(&(device->current_lt[l]), &(device->db_records[d]),
					sizeof(link_record_t)) == 0) {
				internal_ok[d] = 1;
			}
			if (memcmp(&(device->current_lt[d]), &(device->db_records[l]),
					sizeof(link_record_t)) == 0) {
				external_ok[d] = 1;
			}
		}
	}

	/* list the problem links */
	for (l = 0; l < MAX_LINKS; l++) {
		if (internal_ok[l] == 0) {
			fix = 1;
			printf("%s: 0x%02x %d %02x.%02x.%02x Not in link table\n",
					device->device_name,
					device->db_records[l].flags,
					device->db_records[l].group_number,
					device->db_records[l].device_id[0],
					device->db_records[l].device_id[1],
					device->db_records[l].device_id[2]);
		}
	}

	for (l = 0; l < MAX_LINKS; l++) {
		if (external_ok[l] == 0) {
			fix = 1;
			printf("%s: 0x%02x %d %02x.%02x.%02x Not in text file\n",
					device->device_name,
					device->current_lt[l].flags,
					device->current_lt[l].group_number,
					device->current_lt[l].device_id[0],
					device->current_lt[l].device_id[1],
					device->current_lt[l].device_id[2]);
		}
	}
	if (!fix) {
		printf("...  Ok!\n");
	} else {
		/* fix links */
		for (l = 0; l < 417; l++) {
			clear_device_link(iplc, device->device_id, l);
		}
		program_device(iplc, device);
	}
}



/*
 * discover_scenes
 *
 * Query all the known devices and build the link tables. From there,
 * try and discover what scenes are currently programmed into the
 * devices.
 */
void discover_scenes(ilib_t *iplc)
{
	scene_t *scene, *last_scene = NULL;
	int i;
	int l;
	char name[256];
	int scene_index = 0;

	/* clear out any existing scenes */
	free_scene_list();

	/* query each known device for all it's links */
	for (i = 0; i <= last_device; i++) {
		l = 0;
		printf("Querying %s\n", device_array[i].device_name);
		while (get_device_link(iplc, device_array[i].device_id, l,
					&device_array[i].current_lt[l]) > 0) {
			l++;
		}
		for (; l < MAX_LINKS; l++) {
			device_array[i].current_lt[l].flags = 0;
		}
	}

	/* build scene tables */
	/*
	 * Loop through all devices and all entries looking for ones that
	 * have the master flag set.  When we find one, start looking for
	 * records that have the same master's id and group to fill in the
	 * responders list.
	 */
	for (i = 0; i <= last_device; i++) {
		for (l = 0; l < MAX_LINKS; l++) {
			if ((device_array[i].current_lt[l].flags & 0xc0) == 0xc0) {
				if (!scene_exist(&device_array[i],
							device_array[i].current_lt[l].group_number)) {
					/* Create a new scene record */
					file_changed(1);
					scene = (scene_t *)malloc(sizeof(scene_t));
					scene->responders = 0;
					scene->next = NULL;
					scene->index = scene_index++;
					scene->group = device_array[i].current_lt[l].group_number;
					scene->responder_list = NULL;
					scene->controller = &device_array[i];
					sprintf(name, "Scene %d (%s)", scene->index,
							device_array[i].device_name);
					scene->name = malloc(strlen(name)+1);
					strcpy (scene->name, name);
					if (scene_list == NULL) {
						scene_list = scene;
					} else {
						last_scene->next = scene;
					}
					last_scene = scene;
					/* Need to find all the responders linked to this device */
					find_responders(scene, device_array[i].device_id);
				}
				device_array[i].changed = 1;
			}

			/*
			 * Copy over the link table from the one queried so that
			 * it can be used to program the device.  This will also
			 * make it show up in the device window
			 */
			memcpy(&device_array[i].db_records[l],
					&device_array[i].current_lt[l], sizeof(link_record_t));
		}
	}
}

void find_responders(scene_t *scene, unsigned char *id)
{
	int i;
	int l;
	device_list_t *dev;

	for (i = 0; i <= last_device; i++) {
		for (l = 0; l < MAX_LINKS; l++) {
			if ((device_array[i].current_lt[l].flags & 0xc0) == 0x80) {
				if (memcmp(device_array[i].current_lt[l].device_id, id, 3) == 0) {
					if (device_array[i].current_lt[l].group_number == scene->group) {
						/* add responder record to scene */
						dev = (device_list_t *)malloc(sizeof(device_list_t));
						dev->device = &device_array[i];
						dev->link.data1 = device_array[i].current_lt[l].data1;
						dev->link.data2 = device_array[i].current_lt[l].data2;
						dev->link.data3 = device_array[i].current_lt[l].data3;
						memcpy(dev->link.device_id,
								scene->controller->device_id, 3);
						dev->link.group_number = scene->group;
						dev->link.flags = device_array[i].current_lt[l].flags;
						dev->next = scene->responder_list;
						scene->responder_list = dev;
						device_array[i].changed = 1;
					}
				}
			}
		}
	}
}



/*
 * scene_exist
 *
 * Scan the scene list looking for a specific controller/group combination.
 */
int scene_exist(device_t *control, unsigned char group)
{
	scene_t *scene;

	scene = scene_list;
	while (scene) {
		if ((scene->controller == control) && (scene->group == group)) {
			return 1;
		}
		scene = scene->next;
	}

	return 0;
}


/*
 * rename_scene
 *
 * Given a scene index and a new name, rename the scene.
 */
void rename_scene(int scene_index, char *new_name)
{
	scene_t *scene;

	scene = scene_list;
	while (scene) {
		if (scene->index == scene_index) {
			file_changed(1);
			if (scene->name) {
				free(scene->name);
			}
			scene->name = malloc(strlen(new_name) + 1);
			strcpy(scene->name, new_name);
			return;
		}
		scene = scene->next;
	}
}
