/*
usbrelay: Control USB HID connected electrical relay modules
Copyright (C) 2014  Darryl Bond
Library version
Copyright (C) 2019  Sean Mollet

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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
#include <wchar.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <hidapi/hidapi.h>
#include <argp.h>
#include "libusbrelay.h"
#include "usbrelay.h"
#include "gitversion.h"

#define MIN(a, b) ((a) < (b) ? (a) : (b))

static void argp_print_version(FILE *stream, struct argp_state *state)
{
	fprintf(stream, "libusbrelay: %s\nusbrelay: %s\n", libusbrelay_version(), GITVERSION);
}

void (*argp_program_version_hook)(FILE *stream, struct argp_state *state) = argp_print_version;

const char *argp_program_bug_address =
	"https://github.com/darrylb123/usbrelay/issues";

/* Program documentation. */
static char doc[] =
	"Control or query USB HID relays."

	/* This part of the documentation comes after the options */
	"\vWithout ACTION, the actual state of all relays is printed to stdout.\n"
	"ACTION can be one of:\n"
	"RELID_N=[0|1] to switch the N-th relay off or on\n"
	"RELID_0=NEWID to change relay ID\n"
	;

/* A description of the arguments we accept. */
static char args_doc[] = "[ACTION...]";

/* The options we understand. */
static struct argp_option options[] = {
	{"debug",    'd', 0,       0, "Produce debugging output" },
	{"quiet",    'q', 0,       0, "Be quiet" },
	{"export-id",'e', "DEV",   0, "Print relay ID of the given DEV (/dev/hidrawXX) in udev-compatible format" },
	{ 0 }
};

/* Used by ‘main’ to communicate with ‘parse_opt’. */
struct arguments
{
	int debug;
	int verbose;
	char *export_id;	/* "/dev/hidrawXX" or NULL */
};

/* Parse a single option. */
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
	/* Get the ‘input’ argument from ‘argp_parse’, which we
	   know is a pointer to our arguments structure. */
	struct arguments *args = state->input;

	switch (key) {
	case 'd':
		args->debug = 1;
		break;
	case 'e':
		args->export_id = arg;
		break;
	case 'q':
		args->verbose = 0;
		break;

	case ARGP_KEY_NO_ARGS:
		break;

	default:
		return ARGP_ERR_UNKNOWN;
	}
	return 0;
}

/* Our argp parser. */
static struct argp argp = { options, parse_opt, args_doc, doc };



int main(int argc, char *argv[])
{
	struct relay *relays = 0;
	int i;
	int exit_code = 0;
	struct arguments args = {
		.debug = 0,
		.export_id = NULL,
		.verbose = 1,
	};

       /* Parse our arguments; every option seen by ‘parse_opt’ will
          be reflected in ‘args’. */
       argp_parse (&argp, argc, argv, 0, &optind, &args);

	/* allocate the memory for all the relays */
	if ((argc - optind) >= 1) {
		relays = calloc(argc - optind + 1, sizeof(struct relay));	/* Yeah, I know. Not using the first member */
		/* relays is zero-initialized */
	} 
	/* loop through the command line and grab the relay details */
	for (i = 0 ; i < (argc - optind); i++) {
		char *arg = argv[i + optind];
		struct relay *relay = &relays[i];

		char *underscore = strchr(arg, '_');
		char *second_underscore = strrchr(arg, '_'); /* Search from end of string too. Should be only one _ or = */
		char *equal_sign = strchr(arg, '=');
		char *second_equals = strrchr(arg, '=');
		if (!underscore || !equal_sign || (underscore > equal_sign) || (equal_sign != second_equals) || (underscore != second_underscore)){	/* e.g. ASDFG=QWERT_1 */
			fprintf(stderr, "Invalid relay specification: %s\n",
				argv[i + optind]);
			exit(1);
		}

		size_t size;
		if (underscore)
			size = underscore - arg;
		else if (equal_sign)
			size = equal_sign - arg;
		else
			size = strlen(arg);

		relay->this_serial = malloc(size + 1);
		strncpy(relay->this_serial, arg, size);
		relay->this_serial[size] = 0;

		/* Parse relay number */
		if (underscore)
			relay->relay_num = atoi(underscore + 1);

		if (equal_sign) {
			if (relay->relay_num == 0) {	/* command to change the serial - remaining token is the new serial */
				strncpy(relay->new_serial, equal_sign + 1,
					sizeof(relay->new_serial) - 1);
				relay->new_serial[sizeof(relay->new_serial) -1] = 0;
			} else {
				if (atoi(equal_sign + 1)) {
					relays[i].state = CMD_ON;
				} else {
					relays[i].state = CMD_OFF;
				}
			}
		}
		if (args.debug) {
			fprintf(stderr, "Orig: %s, Serial: %s, Relay: %d State: %x\n",
				arg, relay->this_serial, 
				relay->relay_num,
				relay->state);
			relay->found = 0;
		}
	}
	// Warn if library is different version
	if( strcmp(libusbrelay_version(),GITVERSION)) {
		fprintf(stderr, "Warning: Version difference\nlibusbrelay: %s\nusbrelay: %s\n", libusbrelay_version(), GITVERSION);
	}
	
	//Locate and identify attached relay boards
	if(args.debug) 
		fprintf(stderr, "libusbrelay: %s\nusbrelay: %s\n", libusbrelay_version(), GITVERSION);
	enumerate_relay_boards(getenv("USBID"), args.verbose, args.debug);

	if (args.export_id) {
		relay_board *relay = find_board(args.export_id, args.debug);
		if (!relay) {
			fprintf(stderr, "relay '%s' not found\n", args.export_id);
			exit(1);
		}
		printf("ID_SERIAL=%s\n", relay->serial);
		return 0;
	}

	/* loop through the supplied command line and try to match the serial */
	for (i = 0; i < (argc - optind); i++) {
		if (args.debug ) {
			fprintf(stderr, "main() arg %d Serial: %s, Relay: %d State: %x \n",
				i,relays[i].this_serial, 
				relays[i].relay_num,
				relays[i].state);
		}
		relay_board *board = find_board(relays[i].this_serial, args.debug );

		if (board) {
			if (args.debug == 2)
				fprintf(stderr, "%d HID Serial: %s ", i, board->serial);
			if (relays[i].relay_num == 0) {
				if (!relays[i].new_serial[0]) {
					fprintf(stderr, "\n \n New serial can't be empty!\n");
				} else {
					fprintf(stderr, "Setting new serial\n");
					set_serial(board->serial,
						   relays[i].new_serial,args.debug);
				}
			} else {
				if (args.debug)
					fprintf(stderr,
						"main() operate: %s, Relay: %d State: %x\n",
						relays[i].this_serial,
						relays[i].relay_num,
						relays[i].state);
				if (operate_relay(relays[i].this_serial, relays[i].relay_num,relays[i].state,args.debug) < 0)
					exit_code++;
				relays[i].found = 1;
			}
		}
	}

	shutdown();

	for (i = 1; i < (argc - optind); i++) {
		if (args.debug)
			fprintf(stderr,"Serial: %s, Relay: %d State: %x ",
				relays[i].this_serial, relays[i].relay_num,
				relays[i].state);
		if (relays[i].found) {
			if (args.debug)
				fprintf(stderr, "--- Found\n");
		} else {
			if (args.debug)
				fprintf(stderr, "--- Not Found\n");
			exit_code++;
		}
	}
	if (relays) {
		for (i = 0; i < (argc - optind) ; i++) {
			free(relays[i].this_serial);
		}
		free(relays);
	}

	exit(exit_code);
}
