#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <linux/limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#include "global.h"

#if HAVE_LIBYAML
#include <yaml.h>
#endif

#include "atcmd.h"
#include "utils.h"
#include "log.h"

#if HAVE_LIBYAML

struct config_info {
	yaml_parser_t parser;
	char *filename;
	FILE *file;

	yaml_event_t event;

	int map_level;
	char *section;
	char *key;
	char *value;

	int complete;
};

static int boolean_value(const char *str)
{
	return !strcmp(str, "true") ? true : false;
}

static int config_close(struct config_info *config)
{
	yaml_parser_delete(&config->parser);
	fclose(config->file);
	free(config->filename);
	free(config->section);
	free(config->key);
	free(config->value);

	return 0;
}

static int config_open(struct config_info *config, const char *filename)
{
	int err;

	memset(config, 0, sizeof(*config));

	config->filename = strdup(filename);
	if (!config->filename) {
		log_error("out of memory");
	}

	config->file = fopen(config->filename, "r");
	if (!config->file) {
		log_error("open config %s: %m", config->filename);
		return -1;
	}

	err = yaml_parser_initialize(&config->parser);
	if (!err) {
		log_error("yaml_parser_initialize");
		fclose(config->file);
		return -1;
	}

	yaml_parser_set_input_file(&config->parser, config->file);

	config->complete = false;

	return 0;
}

static int config_next_event(struct config_info *config)
{
	int err;

	err = yaml_parser_parse(&config->parser, &config->event);
	if (!err) {
		log_error("yaml parse error");
		return -1;
	}

	return 0;
}

static void config_delete_event(struct config_info *config)
{
	yaml_event_delete(&config->event);
}

static int config_set_core_value(const char *key, const char *value)
{
	log_debug("try setting core.%s to %s", key, value);

	if (!strcmp("verbose", key)) {
		Global.core.verbose = boolean_value(value);
	} else if (!strcmp("interactive", key)) {
		Global.core.interactive = boolean_value(value);
	} else if (!strcmp("sms-init", key)) {
		Global.core.sms_init = boolean_value(value);
	} else if (!strcmp("baud-rate", key)) {
		Global.core.baud_rate = atoi(value);
		Global.core.baud_rate = value_to_baud(Global.core.baud_rate);
	} else if (!strcmp("read-timeout", key)) {
		Global.core.read_timeout = atoi(value);
	} else if (!strcmp("device", key)) {
		free(Global.core.device);
		Global.core.device = strdup(value);
	} else if (!strcmp("msg-store-read", key)) {
		free(Global.core.msg_store_read);
		Global.core.msg_store_read = strdup(value);
	} else if (!strcmp("msg-store-send", key)) {
		free(Global.core.msg_store_send);
		Global.core.msg_store_send = strdup(value);
	} else if (!strcmp("msg-store-new", key)) {
		free(Global.core.msg_store_new);
		Global.core.msg_store_new = strdup(value);
	} else if (!strcmp("pb-store", key)) {
		free(Global.core.pb_store);
		Global.core.pb_store = strdup(value);
	} else if (!strcmp("editor", key)) {
		free(Global.core.editor);
		Global.core.editor = strdup(value);
	} else if (!strcmp("edit-file", key)) {
		free(Global.core.edit_file);
		Global.core.edit_file = strdup(value);
	}

	return 0;
}

static int config_set_smtp_value(const char *key, const char *value)
{
	log_debug("try setting smtp.%s to '%s'", key, value);

	if (!strcmp("server", key)) {
		free(Global.smtp.server);
		Global.smtp.server = strdup(value);
	} else if (!strcmp("port", key)) {
		Global.smtp.port = atoi(value);
	} else if (!strcmp("user", key)) {
		free(Global.smtp.user);
		Global.smtp.user = strdup(value);
	} else if (!strcmp("passwd", key)) {
		free(Global.smtp.passwd);
		Global.smtp.passwd = strdup(value);
	} else if (!strcmp("encryption", key)) {
		free(Global.smtp.encryption);
		Global.smtp.encryption = strdup(value);
	}

	return 0;
}

static int config_set_send_email_value(const char *key, const char *value)
{
	log_debug("try setting send-email.%s to %s", key, value);

	if (!strcmp("domain", key)) {
		free(Global.send_email.domain);
		Global.send_email.domain = strdup(value);
	}

	return 0;
}

static int config_set_user_value(const char *key, const char *value)
{
	log_debug("try setting user.%s to %s", key, value);

	if (!strcmp("name", key)) {
		free(Global.user.name);
		Global.user.name = strdup(value);
	} else if (!strcmp("email", key)) {
		free(Global.user.email);
		Global.user.email = strdup(value);
	}

	return 0;
}

static int config_handle_event(struct config_info *config)
{
	switch (config->event.type) {
	case YAML_STREAM_START_EVENT:
		return 0;
	case YAML_STREAM_END_EVENT:
		return 0;
	case YAML_DOCUMENT_START_EVENT:
		return 0;
	case YAML_DOCUMENT_END_EVENT:
		config->complete = true;
		return 0;

	case YAML_SCALAR_EVENT:
		if (config->map_level == 1) {
			config->section = strdup((char *) config->event.data.scalar.value);
			if (!config->section) {
				log_error("out of memory");
				return -1;
			}
			log_debug("section: %s", config->section);

			return 0;
		} else if (config->map_level != 2) {
			log_debug("bad map level: %d", config->map_level);
			return -1;
		}

		if (!config->key) {
			config->key = strdup((char *) config->event.data.scalar.value);
			if (!config->key) {
				log_error("out of memory");
				return -1;
			}

			return 0;
		} else if (!config->value) {
			config->value = strdup((char *) config->event.data.scalar.value);
			if (!config->value) {
				log_error("out of memory");
				return -1;
			}
		}

		if (config->key && config->value) {
			if (!strcmp(config->section, "core")) {
				config_set_core_value(config->key, config->value);
			} else if (!strcmp(config->section, "smtp")) {
				config_set_smtp_value(config->key, config->value);
			} else if (!strcmp(config->section, "send-email")) {
				config_set_send_email_value(config->key, config->value);
			} else if (!strcmp(config->section, "user")) {
				config_set_user_value(config->key, config->value);
			}

			free(config->key);
			config->key = NULL;

			free(config->value);
			config->value = NULL;
		}

		return 0;

	case YAML_MAPPING_START_EVENT:
		config->map_level++;

		return 0;

	case YAML_MAPPING_END_EVENT:
		free(config->section);
		config->section = NULL;

		free(config->key);
		config->key = NULL;

		free(config->value);
		config->value = NULL;

		config->map_level--;

		return 0;

	default:
		log_error("event type: %d", config->event.type);
		return -1;
	}
}

static int _sms_config_load(const char *filename)
{
	int err;
	struct config_info config;
	struct stat sbuf;

	err = stat(filename, &sbuf);
	if (err < 0) {
		if (errno == ENOENT) {
			log_debug("sms config file missing");
			return 0;
		}

		return -1;
	}

	err = config_open(&config, filename);
	if (err < 0) {
		log_error("open config failed");
		return -1;
	}

	while (1) {
		err = config_next_event(&config);
		if (err < 0) {
			log_error("handle event failed");
			break;
		}

		err = config_handle_event(&config);
		if (err < 0) {
			log_error("handle event failed");
			config_delete_event(&config);
			break;
		}
		config_delete_event(&config);

		if (config.complete) {
			break;
		}
	}

	int complete = config.complete;

	err = config_close(&config);
	if (err < 0) {
		log_error("close config failed");
		return -1;
	}

	if (!complete) {
		log_error("config load did not complete");
		return -1;
	}

	log_debug("config loaded");

	return 0;
}

#define SMS_CONFIG_FILE		".smsconfig"

int sms_config_load(void)
{
	int err;
	char *cp;
	char prev[PATH_MAX];
	char *home;
	char *config;

	config = getenv("SMS_CONFIG");
	if (config) {
		return _sms_config_load(config);
	}

	home = getenv("HOME");
	if (!home) {
		log_error("HOME is not set");
		return -1;
	}

	cp = getcwd(prev, sizeof(prev));
	if (!cp) {
		log_error("getcwd %m");
		return -1;
	}

	chdir(home);

	err = _sms_config_load(SMS_CONFIG_FILE);

	chdir(prev);

	return err;
}
#else
int sms_config_load(void)
{
	return 0;
}
#endif