/*
 * Copyright © 2017 Keith Packard <keithp@keithp.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.
 */

#include "ao-eeprom-read.h"
#include <json-c/json.h>
#include <string.h>
#include <errno.h>

static struct json_object *
ao_eeprom_read_config(FILE *file)
{
	char			line[1024];
	struct json_tokener	*tok;
	struct json_object	*obj = NULL;
	enum json_tokener_error	err;

	tok = json_tokener_new();
	if (!tok)
		goto fail_tok;

	for (;;) {
		if (fgets(line, sizeof(line), file) == NULL)
			goto fail_read;
		obj = json_tokener_parse_ex(tok, line, strlen(line));
		err = json_tokener_get_error(tok);
		if (err == json_tokener_success)
			break;
		if (err != json_tokener_continue)
			goto fail_read;
	}
	json_tokener_free(tok);
	return obj;
fail_read:
	json_tokener_free(tok);
fail_tok:
	return NULL;
}

static int
ao_eeprom_read_byte(FILE *file)
{
	int	byte;
	if (fscanf(file, "%x", &byte) != 1)
		return EOF;
	return byte;
}

static int
ao_eeprom_read_data(FILE *file, struct ao_eeprom *eeprom)
{
	uint8_t	*data = NULL, *ndata;
	int	len = 0;
	int	size = 0;
	int	byte;

	data = malloc(size = 64);
	if (!data)
		goto fail_alloc;
	while ((byte = ao_eeprom_read_byte(file)) != EOF) {
		if (len == size) {
			ndata = realloc(data, size *= 2);
			if (!ndata)
				goto fail_realloc;
			data = ndata;
		}
		data[len++] = (uint8_t) byte;
	}
	eeprom->data = data;
	eeprom->len = len;
	return 1;
fail_realloc:
	free(data);
fail_alloc:
	return 0;
}

static int
ao_json_get_int(struct json_object *obj, const char *key, int def)
{
	struct json_object *value;
	int i;

	if (!json_object_object_get_ex(obj, key, &value))
		return def;
	errno = 0;
	i = (int) json_object_get_int(value);
	if (errno != 0)
		return def;
	return i;
}

static const char *
ao_json_get_string(struct json_object *obj, const char *key, const char *def)
{
	struct json_object *value;
	const char *str;

	if (!json_object_object_get_ex(obj, key, &value))
		return def;
	errno = 0;
	str = json_object_get_string(value);
	if (errno)
		return def;
	if (!str)
		return def;
	return str;
}

static int
ao_eeprom_get_pyro(struct ao_config *config, struct json_object *obj)
{
	struct json_object	*pyros;
	struct json_object	*pyro;
	int			i, p;

	if (!json_object_object_get_ex(obj, "pyros", &pyros))
		return 1;

	if (json_object_get_type(pyros) != json_type_array)
		return 0;

	for (i = 0; i < json_object_array_length(pyros); i++) {
		pyro = json_object_array_get_idx(pyros, i);
		if (pyro) {
			p = ao_json_get_int(pyro, "channel", -1);
			if (0 <= p && p < AO_PYRO_NUM) {
				config->pyro[p].flags		= ao_json_get_int(pyro, "flags", 0);
				config->pyro[p].accel_less	= ao_json_get_int(pyro, "accel_less", 0);
				config->pyro[p].accel_greater	= ao_json_get_int(pyro, "accel_greater", 0);
				config->pyro[p].speed_less	= ao_json_get_int(pyro, "speed_less", 0);
				config->pyro[p].speed_greater	= ao_json_get_int(pyro, "speed_greater", 0);
				config->pyro[p].height_less	= ao_json_get_int(pyro, "height_less", 0);
				config->pyro[p].height_greater	= ao_json_get_int(pyro, "height_greater", 0);
				config->pyro[p].orient_less	= ao_json_get_int(pyro, "orient_less", 0);
				config->pyro[p].orient_greater	= ao_json_get_int(pyro, "orient_greater", 0);
				config->pyro[p].time_less	= ao_json_get_int(pyro, "time_less", 0);
				config->pyro[p].time_greater	= ao_json_get_int(pyro, "time_greater", 0);
				config->pyro[p].delay		= ao_json_get_int(pyro, "delay", 0);
				config->pyro[p].state_less	= ao_json_get_int(pyro, "state_less", 0);
				config->pyro[p].state_greater_or_equal	= ao_json_get_int(pyro, "state_greater_or_equal", 0);
				config->pyro[p].motor		= ao_json_get_int(pyro, "motor", 0);
			}
		}
	}
	return 1;
}

static int
ao_eeprom_get_ms5607(struct ao_ms5607_prom *ms5607_prom, struct json_object *obj)
{
	struct json_object	*ms5607;

	if (!json_object_object_get_ex(obj, "ms5607", &ms5607))
		return 1;

	if (json_object_get_type(ms5607) != json_type_object)
		return 0;

	ms5607_prom->reserved =	ao_json_get_int(ms5607, "reserved", 0);
	ms5607_prom->sens =	ao_json_get_int(ms5607, "sens", 0);
	ms5607_prom->off =	ao_json_get_int(ms5607, "off", 0);
	ms5607_prom->tcs =	ao_json_get_int(ms5607, "tcs", 0);
	ms5607_prom->tco =	ao_json_get_int(ms5607, "tco", 0);
	ms5607_prom->tref =	ao_json_get_int(ms5607, "tref", 0);
	ms5607_prom->tempsens =	ao_json_get_int(ms5607, "tempsens", 0);
	ms5607_prom->crc =	ao_json_get_int(ms5607, "crc", 0);
	return 1;
}

static int
ao_eeprom_get_config(struct ao_eeprom *ao_eeprom, struct json_object *obj)
{
	struct ao_config	*config = &ao_eeprom->config;
	const char		*s;

	if (json_object_get_type(obj) != json_type_object)
		return 0;

	ao_eeprom->log_format = 	ao_json_get_int(obj, "log_format", 0);
	ao_eeprom->serial_number =	ao_json_get_int(obj, "serial", 0);

	config->major =			ao_json_get_int(obj, "config_major", 0);
	config->minor = 		ao_json_get_int(obj, "config_minor", 0);
	if (config->major == 0 || config->minor == 0)
		return 0;

	config->main_deploy = 		ao_json_get_int(obj, "main_deploy", 250);
	config->accel_plus_g = 		ao_json_get_int(obj, "accel_cal_plus", 0);

	s = ao_json_get_string(obj, "callsign", "N0CALL");
	strncpy(config->callsign, s, sizeof(config->callsign)-1);

	config->apogee_delay =		ao_json_get_int(obj, "apogee_delay", 0);
	config->accel_minus_g =		ao_json_get_int(obj, "accel_cal_minus", 0);
	config->radio_cal =		ao_json_get_int(obj, "radio_calibration", 0);
	config->flight_log_max =	ao_json_get_int(obj, "flight_log_max", 0);
	config->ignite_mode = 		ao_json_get_int(obj, "ignite_mode", 0);
	config->pad_orientation = 	ao_json_get_int(obj, "pad_orientation", 0);
	config->radio_setting = 	ao_json_get_int(obj, "radio_setting", 0);
	config->radio_enable = 		ao_json_get_int(obj, "radio_enable", 1);
	config->frequency = 		ao_json_get_int(obj, "frequency", 434550);
	config->apogee_lockout = 	ao_json_get_int(obj, "apogee_lockout", 0);
	if (!ao_eeprom_get_pyro(config, obj))
		return 0;
	config->ignite_mode = 		ao_json_get_int(obj, "ignite_mode", 0);
	config->ignite_mode = 		ao_json_get_int(obj, "ignite_mode", 0);
	config->ignite_mode = 		ao_json_get_int(obj, "ignite_mode", 0);
	config->ignite_mode = 		ao_json_get_int(obj, "ignite_mode", 0);
	config->ignite_mode = 		ao_json_get_int(obj, "ignite_mode", 0);
	config->ignite_mode = 		ao_json_get_int(obj, "ignite_mode", 0);

	if (!ao_eeprom_get_ms5607(&ao_eeprom->ms5607_prom, obj))
		return 0;

	return 1;
}

struct ao_eeprom *
ao_eeprom_read(FILE *file)
{
	struct ao_eeprom	*ao_eeprom;
	struct json_object	*obj;
	int			ret;

	ao_eeprom = calloc(1, sizeof (struct ao_eeprom));
	if (!ao_eeprom)
		goto fail_ao_eeprom;

	obj = ao_eeprom_read_config(file);
	if (!obj)
		goto fail_config;

	ret = ao_eeprom_get_config(ao_eeprom, obj);
	json_object_put(obj);
	if (!ret)
		goto fail_config;

	if (!ao_eeprom_read_data(file, ao_eeprom))
		goto fail_data;

	return ao_eeprom;
fail_data:
fail_config:
	free(ao_eeprom);
fail_ao_eeprom:
	return NULL;
}
