#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "general.H"
#include "cli.H"
#include "x10io.H"
#include "x10map.H"




static TOKEN_TABLE commands [] = {
	{ 1,	"turn" },
	{ 1,	"switch" },
	{ 2,	"fade" },
	{ 2,	"dim" },
	{ 3,	"setunit" },
	{ 3,	"set" },
	{ 3,	"showunit" },
	{ 4,	"show" },
	{ 5,	"x10test" },
	{ 5,	"test" },
	{ 0,	NULL}
};

X10_ACTION *action_function [] = {
	NULL,
	turn,
	fade,
	setunit,
	showunit,
	x10test
};

char *program_name;
int debug_level = 0;



static int server_socket = -1;
static int map_fd = -1;
static struct sockaddr sock;
static struct sockaddr serv;


int send_command (int housecode, int unit, int command, int count)
{
	int buf[6];

	if (server_socket == -1) {
		if ((server_socket = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
			perror("socket()");
			return (FAIL_EXECUTION);
		}
		sock.sa_family = AF_UNIX;
		sprintf(sock.sa_data, "/tmp/xc%d", getpid());
		// unlink socket file that may have been left around
		unlink (sock.sa_data);
		serv.sa_family = AF_UNIX;
		strcpy(serv.sa_data, "/tmp/x10");
		if (bind(server_socket, &sock, sizeof(sock)) == -1) {
			perror("bind()");
			return (FAIL_EXECUTION);
		}
	}

	buf[0] = 1;
	buf[1] = housecode;
	buf[2] = unit;
	buf[3] = command;
	buf[4] = count;
	buf[5] = (buf[0] + buf[1] + buf[2] + buf[3] + buf[4]);
	if (sendto(server_socket, buf, sizeof(buf), 0,
		    &serv, sizeof(buf)) == -1) {
		perror ("sendto()");
		return (FAIL_EXECUTION);
	};
	return (SUCCESS);
}



int mapX10state(int housecode, int unit, struct x10state *state,
		int forwrite)
{
	off_t offset;
	ssize_t bytes_io;

	if (housecode < 'A' || housecode > 'P' || unit < 1 || unit > 16)
		return (FAIL_EXECUTION);

	if (map_fd == -1) {
		if ((map_fd = open("/tmp/x10map",
				   forwrite ? O_RDWR : O_RDONLY)) == -1) {
			perror("/tmp/x10map");
			return (FAIL_EXECUTION);
		}
	}
	offset = ((housecode - 'A') * 16 + (unit -1)) *
		 sizeof (struct x10state);
	if (debug_level) {
		fprintf (stderr, "Seeking to %ld.\n", (long) offset);
	}
	offset = lseek(map_fd, offset, SEEK_SET);
	if (offset == -1) {
		perror ("lseek()");
		return (FAIL_EXECUTION);
	};
	bytes_io = forwrite == MAPSTATE_WRITE
		? write (map_fd, state, sizeof(struct x10state))
		: read (map_fd, state, sizeof(struct x10state));
	if (bytes_io == -1) {
		perror (forwrite == MAPSTATE_WRITE ? "write()" : "read()");
		return (FAIL_EXECUTION);
	};
	if (bytes_io < sizeof (struct x10state)) {
		fprintf (stderr, "%s: mapX10state(): Inadequate I/O size.\n",
			program_name);
		return (FAIL_EXECUTION);
	}
	return (SUCCESS);
}






static void close_socket (void) {
	if (server_socket != -1) {
		if (close(server_socket) == -1)
			perror ("close()");
		if (unlink (sock.sa_data) == -1)
			perror ("unlink()");
		server_socket = -1;
	}
	if (map_fd != -1) {
		if (close(map_fd) == -1)
			perror ("close()");
		map_fd = -1;
		
	}
}











int get_token (char *command, TOKEN_TABLE *table) {
	while (table -> token && strcasecmp (table -> token, command) != 0) {
		table++;
	}
	return (table -> code);
}




typedef struct usage_struct {
	const char *name;
	const char *subname;
	const char *description;
} USAGE;



USAGE subcommand_usage [] = {
	{ "turn", "turn",
		"<unit code> <on|off|bright|dim>" },
	{ "fade", "fade",
		"<unit code> <n%|on|off|full|max|min|half> <time in minutes> [fade options]..."
		"\n    Fade options:"
		"\n\toverride - force fade across all 20 levels"
		"\n\tup - device can fade up only"
		"\n\tdown - device can fade down only"
		"\n\tfaststart - start fast and go slower as fade progresses"
		"\n\tslowstart - start slowly and go faster as fade progresses"
		 },
	{ "x10test", "test",
		"<unit code> <test condition> [compare value]"
		"\n    Test conditions:"
		"\n\ton, alive, exist, twoway, me, light"
		"\n\tat [level], above [level], below [level]" },
	{ "showunit", "show",
		"[unit code]" },
	{ "setunit", "set",
		"<unit code> [[-]options]..."
		"\n    Set options: on, alive, exist, twoway, me, light" },
	{ 0, 0, 0 }
};



void usage (void) {
	USAGE *use;

	for (use = subcommand_usage; use->name; use++) {
		if (strcasecmp (program_name, use->name) == 0) {
			fprintf (stderr, "Usage: %s [options] <house code> %s\n",
				program_name, use->description);
			break;
		}
	}
	if (!use->name) {
		fprintf (stderr, "Usages:\n");
		for (use = subcommand_usage; use->name; use++) {
			fprintf (stderr, "%s %s [options] <house code> %s\n",
				program_name, use->subname, use->description);
		}
	}
	fprintf (stderr, "Options are:\n"
		"   -h            : display help\n"
		"   -Z            : set debug mode\n"
		);
}



int main(int argc, char **argv)
{
	if ((program_name = strrchr (*argv, '/')))
		program_name++;
	else
		program_name = *argv;

	int action_number = get_token (program_name, commands);
	X10_ACTION *action = action_function [action_number];
	if (!action) {
		argv++;
		argc--;
		if (!*argv) {
			fprintf (stderr, "%s: Missing action.\n", program_name);
			usage ();
			exit (FAIL_PARSE);
		}
		action_number = get_token (*argv, commands);
		action = action_function [action_number];
		if (!action) {
			fprintf (stderr, "%s: Invalid action %s.\n",
				program_name, *argv);
			usage ();
			exit (FAIL_PARSE);
		}
	}
		
	int option;
	// <Rant by=Peri subject="gnu toolkit">
	// Stupid GNU getopt is broken, and we need to put a '+' at start
	// of getopt to make it behave standard.  Fucking stupid extensions
	// that don't work... at least there's a man page still, instead
	// of their stupid 'info' tool.  Hopefully this won't break other
	// implementations, but it probably will...
	// </Rant> ZZZZZZZZZZZ
	while ((option = getopt (argc, argv, "+hZ")) != EOF) {
		switch (option) {
			case 'Z':
				debug_level = 255;
				break;
			case 'h':
			case '?':
				usage ();
				return 1;
		}
	}
	argv += optind;

	if (!*argv) {
		fprintf (stderr, "%s: Missing house code.\n", program_name);
		usage ();
		exit (FAIL_PARSE);
	}

	int housecode = toupper (**argv);
	if (housecode < 'A' || housecode > 'P') {
		fprintf (stderr, "%s: Invalid house code '%c'.\n", program_name,
			(char) housecode);
		exit (FAIL_PARSE);
	}

	char *error;
	int unit = 0;
	if (strlen (*argv) == 1) {
		if (!*(++argv)) {
			unit = 0;
		} else {
			unit = strtol (*argv, &error, 10);
			if (error && *error) {
				unit = 0;
			} else {
				argv++;
			}
		}
	} else {
		unit = strtol ((*argv) + 1, &error, 10);
		if (error && *error) {
			fprintf (stderr, "%s: Invalid unit code %s.\n",
				 program_name, (*argv) + 1);
			exit (FAIL_PARSE);
		}
		argv++;
	}

	if (unit < 0 || unit > 16) {
		fprintf (stderr, "%s: Invalid unit code %d.\n",
			 program_name, unit);
		exit (FAIL_PARSE);
	}

	int status;
	status = action (housecode, unit, argv);
	close_socket ();

	if (status == FAIL_PARSE) {
		usage ();
	} else if (status == FAIL_UNITNEEDED) {
		fprintf (stderr, "%s: Unit number must be specified.\n",
			program_name);
		status = FAIL_PARSE;
	}
	return (status);
}


