/*
 * $RCSfile:   $Revision: 1.33 $  $Name:  $
 * $Id: $
 * $Author: bpaauwe $
 * $Date: 2007/09/09 18:10:48 $
 * ----------------------------------------------------------------------------
 *
 *  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:
 *    Header file for insteon message sending functions.
 * ----------------------------------------------------------------------------
 */
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <gtk/gtk.h>
#include <glade/glade.h>
#include <glade/glade-build.h>


/*
 * Device structure.  Holds information about a device.
 */
typedef struct _device {
	int id;
	unsigned char address[3];
	short type;
	short firmware;
	unsigned char level;
	unsigned char ramp;
	unsigned char link_ref;
	int buttons;
	char alias[50]; /* FIXME */
	void *pvt;
} device_t;

/*
 * Linked list of devices in a group/scene
 */
typedef struct _group_list {
	device_t *device;
	int button;
	int level;
	int ramp;
	int local;
	struct _group_list *next;
} group_list_t;


/*
 * Link entry.  Holds a link database entry.
 */
typedef struct _link {
	int id;
	char alias[50]; /* FIXME */
	unsigned char device_id[3];
	unsigned char flags;
	unsigned char device[3];
	unsigned char group;
	unsigned char data1;
	unsigned char data2;
	unsigned char data3;
	struct _link *next;
} link_t;


enum _colunmns {
	COL_SELECT = 0,
	COL_ALIAS,
	COL_ADDRESS,
	COL_TYPE,
	COL_BUTTON,
	COL_LEVEL,
	COL_RAMP,
	COL_LOCAL
} column_name_t;


static void fill_list(GtkListStore *store);
static int getline(FILE *fp, char *s, int lim);
static unsigned char *str_id_to_hex(char *device_id);
static char *device_type_name(short type);
static unsigned char atoh(char h);
static void build_links(group_list_t *group);
static void read_links(void);
static void print_links(link_t *lnk);

void level_edited_cb(GtkCellRendererText *cell, gchar *path, gchar *text,
		gpointer user_data);
void toggle_cb(GtkCellRendererText *cell, gchar *path, gpointer user_data);
void create_clicked(GtkWidget *button, gpointer user_data);


GtkWidget *main_window;
GtkWidget *tree_view;
GtkWidget *group_name;
GtkWidget *add_button;
GtkWidget *cancel_button;
device_t device_table[100];
link_t *link_table = NULL;


int main (int argc, char **argv)
{
	const char *filename = "group_mock.glade";
	const char *rootnode = NULL;
	GladeXML *xml;
	GtkCellRenderer *renderer;
	GtkTreeViewColumn *col;
	GtkListStore *store;

	/* initialise libraries */
	gtk_init(&argc, &argv);

	/* Read in the link database */
	read_links();

	/* construct the interface */
	xml = glade_xml_new(filename, rootnode, NULL);
	if (!xml) {
		g_warning("something bad happened while creating the interface");
		return 1;
	}

	main_window = glade_xml_get_widget(xml, "window1");
	g_signal_connect (main_window, "delete_event", gtk_main_quit, NULL);

	/* Get widget pointers for each of widgets that contain user entered data */
	tree_view = glade_xml_get_widget(xml, "treeview1");
	group_name = glade_xml_get_widget(xml, "groupname");
	add_button = glade_xml_get_widget(xml, "add");
	cancel_button = glade_xml_get_widget(xml, "cancel");

	glade_xml_signal_autoconnect(xml);
	g_object_unref(G_OBJECT(xml));

	/* Create the columns for the responder list widget */
	store = gtk_list_store_new(9, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING,
			G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_BOOLEAN,
			G_TYPE_INT);

	fill_list(store);

	renderer = gtk_cell_renderer_toggle_new();
	col = gtk_tree_view_column_new_with_attributes("", renderer,
			"active", COL_SELECT,
			NULL);
	gtk_tree_view_append_column((GtkTreeView *)tree_view, col);
	g_object_set(renderer, "activatable", TRUE, NULL);
	g_object_set_data(G_OBJECT(renderer), "which", GUINT_TO_POINTER(0));
	g_signal_connect(renderer, "toggled", (GCallback)toggle_cb, store);

	renderer = gtk_cell_renderer_text_new();
	col = gtk_tree_view_column_new_with_attributes("Alias", renderer,
			"text", COL_ALIAS, NULL);
	gtk_tree_view_append_column((GtkTreeView *)tree_view, col);

	renderer = gtk_cell_renderer_text_new();
	col = gtk_tree_view_column_new_with_attributes("Address", renderer,
			"text", COL_ADDRESS, NULL);
	gtk_tree_view_append_column((GtkTreeView *)tree_view, col);

	renderer = gtk_cell_renderer_text_new();
	col = gtk_tree_view_column_new_with_attributes("Type", renderer,
			"text", COL_TYPE, NULL);
	gtk_tree_view_append_column((GtkTreeView *)tree_view, col);

	renderer = gtk_cell_renderer_text_new();
	col = gtk_tree_view_column_new_with_attributes("Button", renderer,
			"text", COL_BUTTON,
			NULL);
	gtk_tree_view_append_column((GtkTreeView *)tree_view, col);

	renderer = gtk_cell_renderer_text_new();
	col = gtk_tree_view_column_new_with_attributes("Level", renderer,
			"text", COL_LEVEL,
			NULL);
	gtk_tree_view_append_column((GtkTreeView *)tree_view, col);
	g_object_set(renderer, "editable", TRUE, NULL);
	g_object_set_data(G_OBJECT(renderer), "which", GUINT_TO_POINTER(COL_LEVEL));
	g_signal_connect(renderer, "edited", (GCallback)level_edited_cb, store);

	renderer = gtk_cell_renderer_text_new();
	col = gtk_tree_view_column_new_with_attributes("Ramp", renderer,
			"text", COL_RAMP,
			NULL);
	gtk_tree_view_append_column((GtkTreeView *)tree_view, col);
	g_object_set(renderer, "editable", TRUE, NULL);
	g_object_set_data(G_OBJECT(renderer), "which", GUINT_TO_POINTER(COL_RAMP));
	g_signal_connect(renderer, "edited", (GCallback)level_edited_cb, store);

	renderer = gtk_cell_renderer_toggle_new();
	col = gtk_tree_view_column_new_with_attributes("Local", renderer,
			"active", COL_LOCAL,
			NULL);
	gtk_tree_view_append_column((GtkTreeView *)tree_view, col);
	g_object_set(renderer, "activatable", TRUE, NULL);
	g_object_set_data(G_OBJECT(renderer), "which", GUINT_TO_POINTER(COL_LOCAL));
	g_signal_connect(renderer, "toggled", (GCallback)toggle_cb, store);

	gtk_tree_view_set_headers_clickable((GtkTreeView *)tree_view, TRUE);
	gtk_tree_view_set_rules_hint((GtkTreeView *)tree_view, TRUE);

	gtk_tree_view_set_model((GtkTreeView *)tree_view,
			(GtkTreeModel *)store);

	g_signal_connect (add_button, "clicked", (GCallback)create_clicked, store);

	g_object_unref (G_OBJECT (store));
	/*
	gtk_tree_selection_set_mode(
			gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)),
			GTK_SELECTION_NONE);
	*/

	/* start the event loop */
	gtk_main();

	return 0;
}


static void parse_device_record(
		char *line,
		device_t *record)
{
	char *tok;
	/*
	 * Line looks like this:
	 *   0018|01.3e.0a|0265|2.6|000|000|2d|Hall buttons
	 */

	tok = strtok(line, "|");
	record->id = atoi(tok);

	tok = strtok(NULL, "|");
	memcpy(record->address, str_id_to_hex(tok), 3);

	tok = strtok(NULL, "|");
	record->type = atoi(tok);

	tok = strtok(NULL, "|");
	record->firmware = atoi(tok); /* FIXME */

	tok = strtok(NULL, "|");
	record->buttons = atoi(tok);

	tok = strtok(NULL, "|");
	record->level = atoi(tok);

	tok = strtok(NULL, "|");
	record->ramp = atoi(tok);

	tok = strtok(NULL, "|");
	record->link_ref = atoi(tok);

	tok = strtok(NULL, "|");
	strcpy(record->alias, tok);
}

static void fill_list(GtkListStore *store)
{
	GtkTreeIter iter;
	FILE *fh;
	char line[81];
	char address[10];
	int i = 0;
	int b;
	gboolean local;

	fh = fopen("device.db", "r");

	while (getline(fh, line, 80) != EOF) {
		parse_device_record(line, &device_table[i]);

		sprintf(address, "%02x.%02x.%02x", device_table[i].address[0],
				device_table[i].address[1], device_table[i].address[2]);

		if (device_table[i].type == 0x0109) {
			/* KeypadLinc has 6 or 8 buttons */
			for (b = 1; b <= device_table[i].buttons; b++) {
				local = (b == 1 || b == device_table[i].buttons) ? TRUE : FALSE;
				gtk_list_store_append(store, &iter);
				gtk_list_store_set(store, &iter,
						0, FALSE,
						1, device_table[i].alias,
						2, address,
						3, device_type_name(device_table[i].type),
						4, b,
						5, device_table[i].level,
						6, device_table[i].ramp,
						7, local,
						8, i,
						-1);
			}
		} else if (device_table[i].type == 0x0004) {
			for (b = 1; b <= device_table[i].buttons; b++) {
				gtk_list_store_append(store, &iter);
				gtk_list_store_set(store, &iter,
						0, FALSE,
						1, device_table[i].alias,
						2, address,
						3, device_type_name(device_table[i].type),
						4, b,
						5, device_table[i].level,
						6, device_table[i].ramp,
						7, FALSE,
						8, i,
						-1);
			}
		} else {
			gtk_list_store_append(store, &iter);
			gtk_list_store_set(store, &iter,
					0, FALSE,
					1, device_table[i].alias,
					2, address,
					3, device_type_name(device_table[i].type),
					4, device_table[i].buttons,
					5, device_table[i].level,
					6, device_table[i].ramp,
					7, FALSE,
					8, i,
					-1);
		}
		i++;
	}

	fclose(fh);
}





/*
 * Read a line of text from a file.
 */
static int getline(FILE *fp, char *s, int lim)
{
	int c, i;
	int ws = 0;

	i = 0;

	while (--lim > 0 && (c = fgetc(fp)) != EOF && c != '\n') {
		if (c != '\r') {
			if (!ws && ((c == ' ') || (c == '\t'))) {
				/* skip leading white space */
			} else {
				s[i++] = c;
				ws = 1;
			}
		}
	}

	s[i] = '\0';
	if ( c == EOF ) {
		return (c);
	} else {
		return(i);
	}
}


static unsigned char atoh(char h)
{
	if ((h >= '0') && (h <= '9')) {
		return (h - '0');
	} else if ((h >= 'a') && (h <= 'f')) {
		return (h - 'a' + 10);
	} else if ((h >= 'A') && (h <= 'F')) {
		return (h - 'A' + 10);
	}

	return 0;
}

int ahtoi(char *h)
{
	int value = 0;

	while (*h++) {
		if (isxdigit(*h)) {
			value = value << 4;
			if ((*h >= '0') && (*h <= '9')) {
				value += (*h - '0');
			} else if ((*h >= 'a') && (*h <= 'f')) {
				value += (*h - 'a' + 10);
			} else if ((*h >= 'A') && (*h <= 'F')) {
				value += (*h - 'A' + 10);
			}
		}
	}

	return value;
}




static unsigned char *str_id_to_hex(char *device_id)
{
	static unsigned char id[3];

	memset(id, 0, 3);

	id[0] = atoh(device_id[0]) * 16;
	id[0] += atoh(device_id[1]);

	id[1] = atoh(device_id[3]) * 16;
	id[1] += atoh(device_id[4]);

	id[2] = atoh(device_id[6]) * 16;
	id[2] += atoh(device_id[7]);

	return id;
}



/*
 * device_type_name
 *
 * Decode known device types.
 */
static char *device_type_name(short type)
{
	static char unknown[26];

	switch (type) {
		case 0x0000: return "PowerLinc V2 pre 1.6     ";
		case 0x0001: return "PowerLinc V2             ";
		case 0x0004: return "ControlLinc V2           ";
		case 0x0005: return "RF RemoteLinc            ";
		case 0x0006: return "ICON Tabletop Controller ";
		case 0x0009: return "SiglalLinc RF            ";
		case 0x0100: return "LampLinc V2 Dimmer       ";
		case 0x0101: return "SwitchLinc V2 Dimmer     ";
		case 0x0102: return "ICON Dimmer Lamp 3-pin   ";
		case 0x0103: return "ICON Dimmer Switch       ";
		case 0x0104: return "SwitchLinc V2 Dimmer 1kw ";
		case 0x0105: return "ICON Switch 1000w        ";
		case 0x0106: return "LampLinc V2 Dimmer 2-pin ";
		case 0x0107: return "ICON Dimmer Lamp         ";
		case 0x0109: return "KeypadLinc V2            ";
		case 0x010a: return "ICON In-wall Controller  ";
		case 0x0114: return "SwitchLinc V2 Companion  ";
		case 0x0115: return "SwitchLinc Dimmer Comp   ";
		case 0x0116: return "ICON Switch Companion hw ";
		case 0x0209: return "ApplianceLinc V2         ";
		case 0x020a: return "SwitchLinc V2 Relay      ";
		case 0x020b: return "ICON Relay Switch        ";
		case 0x020c: return "ICON Appliance Adaptor   ";
		case 0x0400: return "ICON USB Interface       ";
		default:
			sprintf(unknown, "0x%04x Unknown           ", type);
			return unknown;
	}
	return "";
}

void level_edited_cb(GtkCellRendererText *cell, gchar *path, gchar *text,
		gpointer user_data)
{
	guint column;
	GtkTreeModel *store;
	GtkTreeIter iter;
	int value;

	store = (GtkTreeModel *)user_data;

	column = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(cell), "which"));

	if (gtk_tree_model_get_iter_from_string(store, &iter, path)) {
		value = atoi(text);

		gtk_list_store_set((GtkListStore *)store, &iter, column, value, -1);
	}
}


void toggle_cb(GtkCellRendererText *cell, gchar *path, gpointer user_data)
{
	guint column;
	GtkTreeModel *store;
	GtkTreeIter iter;
	gboolean value;

	store = (GtkTreeModel *)user_data;

	column = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(cell), "which"));

	if (gtk_tree_model_get_iter_from_string(store, &iter, path)) {
		gtk_tree_model_get(store, &iter, column, &value, -1);
		value = !value;
		gtk_list_store_set((GtkListStore *)store, &iter, column, value, -1);
	}
}


/*
 * Called with the create group button is pressed.
 *
 * Figure out which devices were selected and build a list of them.
 * Based on that list, figure out what link entries need to be created.
 *
 * The device list is in device_table[].
 */
void create_clicked(GtkWidget *widget, gpointer user_data)
{
	group_list_t *list = NULL, *gtmp;
	GtkTreeModel *store;
	GtkTreeIter iter;
	gboolean selected;
	gchar *alias;
	gchar *address;
	int type;
	int button;
	int level;
	int ramp;
	gboolean local;
	int index;

	store = (GtkTreeModel *)user_data;
	if (gtk_tree_model_get_iter_first(store, &iter)) {
		do {
			gtk_tree_model_get(store, &iter,
				0, &selected,
				1, &alias,
				2, &address,
				3, &type,
				4, &button,
				5, &level,
				6, &ramp,
				7, &local,
				8, &index,
				-1);
			if (selected) {
				gtmp = (group_list_t *)malloc(sizeof(group_list_t));
				gtmp->device = &device_table[index];
				gtmp->button = button;
				gtmp->level = level;
				gtmp->ramp = ramp;
				gtmp->local = local;
				gtmp->next = list;
				list = gtmp;
			}
		} while (gtk_tree_model_iter_next(store, &iter));
	}

	if (list) {
		/* Process the list of devices that make up this group */
		build_links(list);
	}
}

int is_controller(int type) {
	switch (type) {
		case 0x0000: return 1;
		case 0x0001: return 1;
		case 0x0004: return 1;
		case 0x0005: return 0;
		case 0x0006: return 1;
		case 0x0009: return 0;
		case 0x0100: return 0;
		case 0x0101: return 2;
		case 0x0102: return 0;
		case 0x0103: return 2;
		case 0x0104: return 2;
		case 0x0105: return 2;
		case 0x0106: return 0;
		case 0x0107: return 0;
		case 0x0109: return 2;
		case 0x010a: return 1;
		case 0x0114: return 2;
		case 0x0115: return 2;
		case 0x0116: return 2;
		case 0x0209: return 0;
		case 0x020a: return 2;
		case 0x020b: return 2;
		case 0x020c: return 0;
		case 0x0400: return 1;
		default:
			return 0;
	}
	return 0;
}

static void add_link(link_t **links, link_t *lnk)
{
	link_t *tmp;

	if (*links == NULL) {
		/* List is empty, make this the first item */
		lnk->id = 1;
		*links = lnk;
	} else {
		/* Add link to end of list */
		tmp = *links;
		while (tmp->next != NULL) {
			tmp = tmp->next;
		}
		lnk->id = tmp->id + 1;
		tmp->next = lnk;
	}
}


/*
 * Build a list of link entries that will implement control between
 * a list of devices.
 */
static void build_links(group_list_t *group)
{
	group_list_t *ptr, *slv;
	link_t *links = NULL, *lnk;
	int ctrl;

	ptr = group;
	while (ptr) {
		/*
		 * If this is a controller and local is set
		 *   for every device that is not this one
		 *      create a master link for this device.
		 *      create a slave link.
		 */
		ctrl = is_controller(ptr->device->type);
		if ((ctrl == 1) || (ctrl == 2 && ptr->local)) {
			slv = group;
			printf("Making links for controller %s\n", ptr->device->alias);
			while (slv) {
				if (slv->device != ptr->device) {
					/* Controller link */
					lnk = (link_t *)malloc(sizeof(link_t));

					strcpy(lnk->alias, "Group Name");
					memcpy(lnk->device_id, ptr->device->address, 3);
					lnk->flags = 0xa2; /* Master */
					lnk->group = ptr->button;
					memcpy(lnk->device, slv->device->address, 3);
					lnk->data1 = slv->level;
					lnk->data2 = slv->ramp;
					lnk->data3 = lnk->group;
					lnk->next = NULL;
					add_link(&links, lnk);

					/* Responder link */
					lnk = (link_t *)malloc(sizeof(link_t));
					strcpy(lnk->alias, "Group Name");
					memcpy(lnk->device_id, slv->device->address, 3);
					lnk->flags = 0xe2; /* Slave */
					lnk->group = ptr->button;
					memcpy(lnk->device, ptr->device->address, 3);
					lnk->data1 = slv->level;
					lnk->data2 = slv->ramp;
					lnk->data3 = 0;
					lnk->next = NULL;
					add_link(&links, lnk);
				}
				slv = slv->next;
			}
		}
		ptr = ptr->next;
	}

	print_links(links);
}

static void print_links(link_t *link)
{
	link_t *lnk = link;

	while (lnk) {
		printf("%d | %s | %0x.%0x.%0x | 0x%02x | %d | %0x.%0x.%0x |"
				" 0x%02x 0x%02x 0x%02x\n",
				lnk->id, lnk->alias,
				lnk->device_id[0], lnk->device_id[1], lnk->device_id[2],
				lnk->flags,
				lnk->group,
				lnk->device[0], lnk->device[1], lnk->device[2],
				lnk->data1, lnk->data2, lnk->data3);
		lnk = lnk->next;
	}
}

static void parse_link_record(char *line, link_t *link)
{
	char *tok;
	/*
	 * Line looks like this:
	 *   0003|G0003|07.ae.e6|a2|05|06.04.be|ad|16|05
	 */

	tok = strtok(line, "|");
	if (!tok) printf("error 1, in %s\n", line);
	link->id = atoi(tok);

	tok = strtok(NULL, "|");
	if (!tok) printf("error 2, in %s\n", line);
	strcpy(link->alias, tok);

	tok = strtok(NULL, "|");
	if (!tok) printf("error 3, in %s\n", line);
	memcpy(link->device_id, str_id_to_hex(tok), 3);

	tok = strtok(NULL, "|");
	if (!tok) printf("error 4, in %s\n", line);
	link->flags = ahtoi(tok);

	tok = strtok(NULL, "|");
	if (!tok) printf("error 5, in %s\n", line);
	link->group = ahtoi(tok);

	tok = strtok(NULL, "|");
	if (!tok) printf("error 6, in %s\n", line);
	memcpy(link->device, str_id_to_hex(tok), 3);

	tok = strtok(NULL, "|");
	if (!tok) printf("error 7, in %s\n", line);
	link->data1 = ahtoi(tok);

	tok = strtok(NULL, "|");
	if (!tok) printf("error 8, in %s\n", line);
	link->data2 = ahtoi(tok);

	tok = strtok(NULL, "|");
	if (!tok) printf("error 9, in %s\n", line);
	link->data3 = ahtoi(tok);
}


static void read_links(void)
{
	FILE *fh;
	char line[100];
	link_t *link;

	fh = fopen("links.db", "r");
	if (fh == NULL) {
		printf("Failed to open links.db\n");
		return;
	}

	while(getline(fh, line, 100) != EOF) {
		link = (link_t *)malloc(sizeof(link_t));
		parse_link_record(line, link);
		link->next = NULL;

		add_link(&link_table, link);
	}

	fclose(fh);

	printf("Initial link table\n");
	print_links(link_table);
	printf("\n");

}
