/*
 * Create an image file for the MTCDP ID EEPROM
 *
 * Copyright (C) 2010 by Multi-Tech Systems
 *
 * Author: James Maki <jmaki@multitech.com>
 *
 * 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
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <linux/mts_io.h>

#include "log.h"
#include "eeprom.h"

static int hwaddr_aton(const char *str, uint8_t *buf, size_t len)
{
	size_t count = 0;

	for (;;) {
		unsigned acc;
		char ch;

		acc = 0;

		while ((ch = *str) != ':' && ch != 0) {
			if (ch >= '0' && ch <= '9') {
				ch -= '0';
			} else if (ch >= 'a' && ch <= 'f') {
				ch -= 'a' - 10;
			} else if (ch >= 'A' && ch <= 'F') {
				ch -= 'A' - 10;
			} else {
				return 0;
			}
			acc = (acc << 4) + ch;
			str++;
		}

		if (acc > 255) {
			return 0;
		}

		if (count >= len) {
			return 0;
		}

		buf[count] = acc;
		count++;

		if (ch == 0) {
			break;
		}
		str++;
	}

	if (count < len) {
		return 0;
	}

	return 1;
}

static int yaml_out(const char *name, struct mts_id_eeprom_layout *id_eeprom)
{
	FILE *file;

	if (!strcmp(name, "-")) {
		file = stdout;
	} else {
		file = fopen(name, "w+");
		if (!file) {
			log_error("could not open %s: %m", name);
			return -1;
		}
	}

	fprintf(file, "---\n");
	fprintf(file, "\n");

	fprintf(file, "vendor-id: \"%.32s\"\n", id_eeprom->vendor_id);
	fprintf(file, "product-id: \"%.32s\"\n",  id_eeprom->product_id);
	fprintf(file, "device-id: \"%.32s\"\n",  id_eeprom->device_id);
	fprintf(file, "hw-version: \"%.32s\"\n",  id_eeprom->hw_version);
	fprintf(file, "mac-addr: \"%02X:%02X:%02X:%02X:%02X:%02X\"\n",
            id_eeprom->mac_addr[0],
            id_eeprom->mac_addr[1],
            id_eeprom->mac_addr[2],
            id_eeprom->mac_addr[3],
            id_eeprom->mac_addr[4],
            id_eeprom->mac_addr[5]);
	fprintf(file, "imei: \"%.32s\"\n",  id_eeprom->imei);
	fprintf(file, "capa-gps: %s\n", DEVICE_CAPA(id_eeprom->capa, CAPA_GPS) ? "true" : "false");
	fprintf(file, "capa-din: %s\n", DEVICE_CAPA(id_eeprom->capa, CAPA_DIN) ? "true" : "false");
	fprintf(file, "capa-dout: %s\n", DEVICE_CAPA(id_eeprom->capa, CAPA_DOUT) ? "true" : "false");
	fprintf(file, "capa-adc: %s\n", DEVICE_CAPA(id_eeprom->capa, CAPA_ADC) ? "true" : "false");

	fprintf(file, "capa: \"");
	int i;
	for (i = 0; i < sizeof(id_eeprom->capa); i++) {
		fprintf(file, "\\x%02X", id_eeprom->capa[i]);
	}
	fprintf(file, "\"\n");

	fprintf(file, "...\n");

	fclose(file);

	return 0;
}

static int bin_out(const char *name, struct mts_id_eeprom_layout *id_eeprom)
{
	int fd;
	int tmp;

	if (!strcmp(name, "-")) {
		fd = fileno(stdout);
	} else {
		fd = open(name, O_CREAT | O_WRONLY, 0644);
		if (fd < 0) {
			log_error("could not open %s: %m", name);
			return fd;
		}
	}

	tmp = write(fd, (char *) id_eeprom, sizeof(*id_eeprom));
	if (tmp < 0) {
		log_error("writing %s failed: %m", name);
		return fd;
	} else if (tmp != sizeof(*id_eeprom)) {
		log_error("only wrote %d bytes to %s", tmp, name);
		return -1;
	}

	close(fd);

	return tmp;
}

static void mts_id_eeprom_inspect(struct mts_id_eeprom_layout *id_eeprom)
{
	log_info("sizeof: %lu", sizeof(struct mts_id_eeprom_layout));
	log_info("vendor-id: %.32s", id_eeprom->vendor_id);
	log_info("product-id: %.32s",  id_eeprom->product_id);
	log_info("device-id: %.32s",  id_eeprom->device_id);
	log_info("hw-version: %.32s",  id_eeprom->hw_version);
	log_info("mac-addr: %02X:%02X:%02X:%02X:%02X:%02X",
            id_eeprom->mac_addr[0],
            id_eeprom->mac_addr[1],
            id_eeprom->mac_addr[2],
            id_eeprom->mac_addr[3],
            id_eeprom->mac_addr[4],
            id_eeprom->mac_addr[5]);
	log_info("imei: %.32s",  id_eeprom->imei);
	log_info("capa-gps: %s", DEVICE_CAPA(id_eeprom->capa, CAPA_GPS) ? "yes" : "no");
	log_info("capa-din: %s", DEVICE_CAPA(id_eeprom->capa, CAPA_DIN) ? "yes" : "no");
	log_info("capa-dout: %s", DEVICE_CAPA(id_eeprom->capa, CAPA_DOUT) ? "yes" : "no");
	log_info("capa-adc: %s", DEVICE_CAPA(id_eeprom->capa, CAPA_ADC) ? "yes" : "no");
}

static void print_version(const char *name) {
	printf("%s (" PACKAGE ") " VERSION " (" __DATE__ " " __TIME__ ")\n", name);
	printf("Copyright (C) 2010 by Multi-Tech Systems\n");
	printf(
"This program is free software; you may redistribute it under the terms of\n"
"the GNU General Public License version 2 or (at your option) any later version.\n"
"This program has absolutely no warranty.\n");
}

static void usage(FILE *out) {
	fprintf(out, "usage: mts-id-eeprom [ OPTIONS ... ]\n");
	fprintf(out, "where  OPTIONS := { \n");
	fprintf(out, "         --in-file <in-file> |\n");
	fprintf(out, "         --out-file <out-file> |\n");
	fprintf(out, "         --vendor-id <vendor-id> |\n");
	fprintf(out, "         --product-id <product-id> |\n");
	fprintf(out, "         --device-id <device-id> |\n");
	fprintf(out, "         --hw-version <hw-version> |\n");
	fprintf(out, "         --mac-addr <mac-addr> |\n");
	fprintf(out, "         --imei <imei> |\n");
	fprintf(out, "         --capa-gps |\n");
	fprintf(out, "         --capa-din |\n");
	fprintf(out, "         --capa-dout |\n");
	fprintf(out, "         --capa-adc |\n");
	fprintf(out, "         --capa <capa> |\n");
	fprintf(out, "         --out-format { bin | yaml (default) } |\n");
	fprintf(out, "       }\n");
	fprintf(out, "\n");
}

enum {
	CMD_OPT_IN_FILE = 128,
	CMD_OPT_OUT_FILE,
	CMD_OPT_VENDOR_ID,
	CMD_OPT_PRODUCT_ID,
	CMD_OPT_DEVICE_ID,
	CMD_OPT_HW_VERSION,
	CMD_OPT_MAC_ADDR,
	CMD_OPT_IMEI,
	CMD_OPT_CAPA_GPS,
	CMD_OPT_CAPA_DIN,
	CMD_OPT_CAPA_DOUT,
	CMD_OPT_CAPA_ADC,
	CMD_OPT_CAPA,
	CMD_OPT_OUT_FORMAT,
	CMD_OPT_VERSION,
	CMD_OPT_HELP,
};

static char *short_options = "f:";
static struct option long_options[] = {
	{"in-file", 1, NULL, CMD_OPT_IN_FILE},
	{"out-file", 1, NULL, CMD_OPT_OUT_FILE},
	{"vendor-id", 1, NULL, CMD_OPT_VENDOR_ID},
	{"product-id", 1, NULL, CMD_OPT_PRODUCT_ID},
	{"device-id", 1, NULL, CMD_OPT_DEVICE_ID},
	{"hw-version", 1, NULL, CMD_OPT_HW_VERSION},
	{"mac-addr", 1, NULL, CMD_OPT_MAC_ADDR},
	{"imei", 1, NULL, CMD_OPT_IMEI},
	{"capa-gps", 1, NULL, CMD_OPT_CAPA_GPS},
	{"capa-din", 1, NULL, CMD_OPT_CAPA_DIN},
	{"capa-dout", 1, NULL, CMD_OPT_CAPA_DOUT},
	{"capa-adc", 1, NULL, CMD_OPT_CAPA_ADC},
	{"capa", 1, NULL, CMD_OPT_CAPA},
	{"out-format", 1, NULL, CMD_OPT_OUT_FORMAT},
	{"version", 0, NULL, CMD_OPT_VERSION},
	{"help", 0, NULL, CMD_OPT_HELP},
	{0, 0, 0, 0},
};

int main(int argc, char *argv[]) {
	int i;
	int option_index;
	int tmp;
	int fd;

	char *in_file = NULL;
	char *out_file = "-";
	char *out_format = "yaml";

	struct mts_id_eeprom_layout id_eeprom;

	memset(&id_eeprom, 0, sizeof(id_eeprom));

#if 0
	strncpy(id_eeprom.vendor_id, VENDOR_ID_MULTITECH, sizeof(id_eeprom.vendor_id) - 1);
	strncpy(id_eeprom.product_id, PRODUCT_ID_MTCDP_E1_DK, sizeof(id_eeprom.product_id) - 1);
	strncpy(id_eeprom.device_id, "12345678", sizeof(id_eeprom.device_id) - 1);
	strncpy(id_eeprom.hw_version, HW_VERSION_MTCDP_1_0, sizeof(id_eeprom.hw_version) - 1);

	hwaddr_aton("00:d0:a0:02:0d:e1", id_eeprom.mac_addr, sizeof(id_eeprom.mac_addr));
	strncpy(id_eeprom.imei, "353626020834450", sizeof(id_eeprom.imei) - 1);

	DEVICE_CAPA_SET(id_eeprom.capa, CAPA_GPS);
	DEVICE_CAPA_SET(id_eeprom.capa, CAPA_DIN);
	DEVICE_CAPA_SET(id_eeprom.capa, CAPA_DOUT);
	DEVICE_CAPA_SET(id_eeprom.capa, CAPA_ADC);
#endif

	while((i = getopt_long(argc, argv, short_options, long_options, &option_index)) >= 0) {
		switch(i) {
		case 0:
			break;

		case CMD_OPT_IN_FILE:
			in_file = optarg;
			fd = open(in_file, O_RDONLY);
			if (fd < 0) {
				log_error("could not open in-file %s: %m", in_file);
				exit(1);
			}

			tmp = read(fd, (char *) &id_eeprom, sizeof(id_eeprom));
			if (tmp < 0) {
				log_error("reading %s failed: %m", in_file);
				exit(1);
			} else if (tmp != sizeof(id_eeprom)) {
				log_error("only read %d bytes from %s", tmp, in_file);
				exit(1);
			}

			close(fd);

			log_info("loaded eeprom from %s successfully", in_file);
			mts_id_eeprom_inspect(&id_eeprom);

			break;

		case CMD_OPT_OUT_FILE:
			out_file = optarg;
			break;

		case CMD_OPT_VENDOR_ID:
			strncpy(id_eeprom.vendor_id, optarg, sizeof(id_eeprom.vendor_id) - 1);
			break;

		case CMD_OPT_PRODUCT_ID:
			strncpy(id_eeprom.product_id, optarg, sizeof(id_eeprom.product_id) - 1);
			break;

		case CMD_OPT_DEVICE_ID:
			strncpy(id_eeprom.device_id, optarg, sizeof(id_eeprom.device_id) - 1);
			break;

		case CMD_OPT_HW_VERSION:
			strncpy(id_eeprom.hw_version, optarg, sizeof(id_eeprom.hw_version) - 1);
			break;

		case CMD_OPT_MAC_ADDR:
			tmp = hwaddr_aton(optarg, id_eeprom.mac_addr, sizeof(id_eeprom.mac_addr));
			if (!tmp) {
				log_error("invalid mac-addr %s", optarg);
				usage(stderr);
				exit(1);
			}
			break;

		case CMD_OPT_IMEI:
			strncpy(id_eeprom.imei, optarg, sizeof(id_eeprom.imei) - 1);
			break;

		case CMD_OPT_CAPA_GPS:
			DEVICE_CAPA_SET(id_eeprom.capa, CAPA_GPS);
			break;

		case CMD_OPT_CAPA_DIN:
			DEVICE_CAPA_SET(id_eeprom.capa, CAPA_DIN);
			break;

		case CMD_OPT_CAPA_DOUT:
			DEVICE_CAPA_SET(id_eeprom.capa, CAPA_DOUT);
			break;

		case CMD_OPT_CAPA_ADC:
			DEVICE_CAPA_SET(id_eeprom.capa, CAPA_ADC);
			break;

		case CMD_OPT_CAPA:
			log_error("capa option not implemented");
			exit(1);
			break;

		case CMD_OPT_OUT_FORMAT:
			if (strcmp(optarg, "bin") && strcmp(optarg, "yaml")) {
				log_error("invalid out-format %s", optarg);
				usage(stderr);
				exit(1);
			}
			out_format = optarg;

			break;

		case CMD_OPT_VERSION:
			print_version("mts-id-eeprom");
			exit(0);
			break;

		case CMD_OPT_HELP:
			usage(stdout);
			exit(0);
			break;

		default:
			usage(stderr);
			exit(1);
		}
	}

	if (out_file) {
		if (!strcmp(out_format, "bin")) {
			bin_out(out_file, &id_eeprom);
		} else if (!strcmp(out_format, "yaml")) {
			yaml_out(out_file, &id_eeprom);
		} else {
			log_error("un-handled out-format");
			exit(1);
		}
	}

	return 0;
}