/*
 * Copyright (C) 2013 - 2014 Andrew Duggan
 * Copyright (C) 2013 - 2014 Synaptics Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <getopt.h>

#include <linux/types.h>
#include <linux/input.h>
#include <linux/hidraw.h>
#include <signal.h>
#include <stdlib.h>

#include "hiddevice.h"

#define RMI4UPDATE_GETOPTS      "hp:ir:w:foambde"

 enum rmihidtool_cmd {
	RMIHIDTOOL_CMD_INTERACTIVE,
	RMIHIDTOOL_CMD_READ,
	RMIHIDTOOL_CMD_WRITE,
	RMIHIDTOOL_CMD_FW_ID,
	RMIHIDTOOL_CMD_PROPS,
	RMIHIDTOOL_CMD_ATTN,
	RMIHIDTOOL_CMD_PRINT_FUNCTIONS,
	RMIHIDTOOL_CMD_REBIND_DRIVER,
	RMIHIDTOOL_CMD_PRINT_DEVICE_INFO,
	RMIHIDTOOL_CMD_RESET_DEVICE,
};

static int report_attn = 0;
static RMIDevice * g_device = NULL;

void print_help(const char *prog_name)
{
	fprintf(stdout, "Usage: %s [OPTIONS] DEVICEFILE\n", prog_name);
	fprintf(stdout, "\t-h, --help\t\t\t\tPrint this message\n");
	fprintf(stdout, "\t-p, --protocol [protocol]\t\tSet which transport prototocl to use.\n");
	fprintf(stdout, "\t-i, --interactive\t\t\tRun in interactive mode.\n");
	fprintf(stdout, "\t-r, --read [address] [length]\t\tRead registers starting at the address.\n");
	fprintf(stdout, "\t-r, --write [address] [length] [data]\tWrite registers starting at the address.\n");
	fprintf(stdout, "\t-f, --firmware-id\t\t\tPrint the firmware id\n");
	fprintf(stdout, "\t-o, --props\t\t\t\tPrint device properties\n");
	fprintf(stdout, "\t-a, --attention\t\t\t\tPrint attention reports until control + c\n");
	fprintf(stdout, "\t-m, --print-functions\t\t\tPrint RMI4 functions for the device.\n");
	fprintf(stdout, "\t-b, --rebind-driver\t\t\tRebind the driver to force an update of device properties.\n");
	fprintf(stdout, "\t-d, --device-info\t\t\tPrint protocol specific information about the device.\n");
	fprintf(stdout, "\t-e, --reset-device\t\t\tReset the device.\n");
}

void print_cmd_usage()
{
	fprintf(stdout, "Commands:\n");
	fprintf(stdout, "s [0,1,2]: Set RMIMode\n");
	fprintf(stdout, "r address size: read size bytes from address\n");
	fprintf(stdout, "w address { values }: write bytes to address\n");
	fprintf(stdout, "a: Wait for attention\n");
	fprintf(stdout, "q: quit\n");
}

int find_token(char * input, char * result, size_t result_len, char ** endpp)
{
	int i = 0;
	char * start = input;
	char * end;

	while (input[i] == ' ') {
		++start;
		++i;
	}

	while (input[i] != '\0') {
		if (input[++i] == ' ')
			break;
	}
	end = &input[i];

	if (start == end)
		return 0;

	*endpp = end;
	if (static_cast<ssize_t>(result_len) < end - start + 1)
		return 0;
	strncpy(result, start, end - start);
	result[end - start] = '\0';

	return 1;
}

void interactive(RMIDevice * device, unsigned char *report)
{
	char token[256];
	char * start;
	char * end;
	int rc;

	for (;;) {
		fprintf(stdout, "\n");
		print_cmd_usage();
		char input[256];

		if (fgets(input, 256, stdin)) {
			memset(token, 0, 256);

			if (input[0] == 's') {
				start = input + 2;
				find_token(start, token, sizeof(token), &end);
				int mode = strtol(token, NULL, 0);
				if (mode >= 0 && mode <= 2) {
					if (device->SetMode(mode)) {
						fprintf(stderr, "Set RMI Mode to: %d\n", mode);
					} else {
						fprintf(stderr, "Set RMI Mode FAILED!\n");
						continue;
					}
				}
			} else if (input[0] == 'r') {
				start = input + 2;
				find_token(start, token, sizeof(token), &end);
				start = end + 1;
				unsigned int addr = strtol(token, NULL, 0);
				find_token(start, token, sizeof(token), &end);
				start = end + 1;
				unsigned int len = strtol(token, NULL, 0);
				fprintf(stdout, "Address = 0x%02x Length = %d\n", addr, len);

				memset(report, 0, 256);
				rc = device->Read(addr, report, len);
				if (rc < 0)
					fprintf(stderr, "Failed to read report: %d\n", rc);
				print_buffer(report, len);
			} else if (input[0] == 'w') {
				int index = 0;
				start = input + 2;
				find_token(start, token, sizeof(token), &end);
				start = end + 1;
				unsigned int addr = strtol(token, NULL, 0);
				unsigned int len = 0;

				memset(report, 0, 256);
				while (find_token(start, token, sizeof(token), &end)) {
					start = end;
					report[index++] = strtol(token, NULL, 0);
					++len;
				}

				if (device->Write(addr, report, len) < 0) {
					fprintf(stderr, "Failed to Write Report\n");
					continue;
				}
			} else if (input[0] == 'a') {
				unsigned int bytes = 256;
				device->GetAttentionReport(NULL,
						RMI_INTERUPT_SOURCES_ALL_MASK,
						report, &bytes);
				print_buffer(report, bytes);
			} else if (input[0] == 'q') {
				return;
			} else {
				print_cmd_usage();
			}
		}
	}
}

static void cleanup(int status)
{
	if (report_attn) {
		report_attn = 0;
		if (g_device)
			g_device->Cancel();
	} else {
		exit(0);
	}
}

int main(int argc, char ** argv)
{
	int rc;
	struct sigaction sig_cleanup_action;
	int opt;
	int index;
	RMIDevice *device;
	const char *protocol = "HID";
	unsigned char report[256];
	char token[256];
	static struct option long_options[] = {
		{"help", 0, NULL, 'h'},
		{"protocol", 1, NULL, 'p'},
		{"interactive", 0, NULL, 'i'},
		{"read", 1, NULL, 'r'},
		{"write", 1, NULL, 'w'},
		{"firmware-id", 0, NULL, 'f'},
		{"props", 0, NULL, 'o'},
		{"attention", 0, NULL, 'a'},
		{"print-functions", 0, NULL, 'm'},
		{"rebind-driver", 0, NULL, 'b'},
		{"device-info", 0, NULL, 'd'},
		{"reset-device", 0, NULL, 'e'},
		{0, 0, 0, 0},
	};
	enum rmihidtool_cmd cmd = RMIHIDTOOL_CMD_INTERACTIVE;
	unsigned int addr = 0;
	unsigned int len = 0;
	char * data = NULL;
	char * start;
	char * end;
	int i = 0;

	memset(&sig_cleanup_action, 0, sizeof(struct sigaction));
	sig_cleanup_action.sa_handler = cleanup;
	sig_cleanup_action.sa_flags = SA_RESTART;
	sigaction(SIGINT, &sig_cleanup_action, NULL);

	while ((opt = getopt_long(argc, argv, RMI4UPDATE_GETOPTS, long_options, &index)) != -1) {
		switch (opt) {
			case 'h':
				print_help(argv[0]);
				return 0;
			case 'p':
				protocol = optarg;
				break;
			case 'i':
				cmd = RMIHIDTOOL_CMD_INTERACTIVE;
				break;
			case 'r':
				cmd = RMIHIDTOOL_CMD_READ;
				addr = strtol(optarg, NULL, 0);
				len = strtol(argv[optind++], NULL, 0);
				break;
			case 'w':
				cmd = RMIHIDTOOL_CMD_WRITE;
				addr = strtol(optarg, NULL, 0);
				data = argv[optind++];
				break;
			case 'f':
				cmd = RMIHIDTOOL_CMD_FW_ID;
				break;
			case 'o':
				cmd = RMIHIDTOOL_CMD_PROPS;
				break;
			case 'a':
				cmd = RMIHIDTOOL_CMD_ATTN;
				break;
			case 'm':
				cmd = RMIHIDTOOL_CMD_PRINT_FUNCTIONS;
				break;
			case 'b':
				cmd = RMIHIDTOOL_CMD_REBIND_DRIVER;
				break;
			case 'd':
				cmd = RMIHIDTOOL_CMD_PRINT_DEVICE_INFO;
				break;
			case 'e':
				cmd = RMIHIDTOOL_CMD_RESET_DEVICE;
				break;
			default:
				print_help(argv[0]);
				return 0;
				break;

		}
	}

	if (!strncasecmp("hid", protocol, 3)) {
		device = new HIDDevice();
	} else {
		fprintf(stderr, "Invalid Protocol: %s\n", protocol);
		return -1;
	}

	if (optind >= argc) {
		print_help(argv[0]);
		return -1;
	}

	rc = device->Open(argv[optind++]);
	if (rc) {
		fprintf(stderr, "%s: failed to initialize rmi device (%d): %s\n", argv[0], errno,
			strerror(errno));
		return 1;
	}

	g_device = device;

	switch (cmd) {
		case RMIHIDTOOL_CMD_READ:
			memset(report, 0, sizeof(report));
			rc = device->Read(addr, report, len);
			if (rc < 0)
				fprintf(stderr, "Failed to read report: %d\n", rc);

			print_buffer(report, len);
			break;
		case RMIHIDTOOL_CMD_WRITE:
			i = 0;
			start = data;
			memset(report, 0, sizeof(report));
			while (find_token(start, token, sizeof(token), &end)) {
				start = end;
				report[i++] = (unsigned char)strtol(token, NULL, 0);
				++len;
			}

			if (device->Write(addr, report, len) < 0) {
				fprintf(stderr, "Failed to Write Report\n");
				return -1;
			}
			break;
		case RMIHIDTOOL_CMD_FW_ID:
			device->ScanPDT();
			device->QueryBasicProperties();
			fprintf(stdout, "firmware id: %lu\n", device->GetFirmwareID());
			break;
		case RMIHIDTOOL_CMD_PROPS:
			device->ScanPDT();
			device->QueryBasicProperties();
			device->PrintProperties();
			break;
		case RMIHIDTOOL_CMD_ATTN:
			report_attn = 1;
			while(report_attn) {
				unsigned int bytes = 256;
				rc = device->GetAttentionReport(NULL,
						RMI_INTERUPT_SOURCES_ALL_MASK,
						report, &bytes);
				if (rc > 0) {
					print_buffer(report, bytes);
					fprintf(stdout, "\n");
				}
			}
			break;
		case RMIHIDTOOL_CMD_PRINT_FUNCTIONS:
			device->ScanPDT();
			device->PrintFunctions();
			break;
		case RMIHIDTOOL_CMD_REBIND_DRIVER:
			device->RebindDriver();
			break;
		case RMIHIDTOOL_CMD_PRINT_DEVICE_INFO:
			device->PrintDeviceInfo();
			break;
		case RMIHIDTOOL_CMD_RESET_DEVICE:
			device->ScanPDT();
			device->Reset();
			break;
		case RMIHIDTOOL_CMD_INTERACTIVE:
		default:
			interactive(device, report);
			break;
	}

	device->Close();

	return 0;
}