#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gettext.h"
#include <zip.h>
#include <time.h>

#include "dive.h"
#include "file.h"

/* Crazy windows sh*t */
#ifndef O_BINARY
#define O_BINARY 0
#endif

int readfile(const char *filename, struct memblock *mem)
{
	int ret, fd;
	struct stat st;
	char *buf;

	mem->buffer = NULL;
	mem->size = 0;

	fd = subsurface_open(filename, O_RDONLY | O_BINARY, 0);
	if (fd < 0)
		return fd;
	ret = fstat(fd, &st);
	if (ret < 0)
		goto out;
	ret = -EINVAL;
	if (!S_ISREG(st.st_mode))
		goto out;
	ret = 0;
	if (!st.st_size)
		goto out;
	buf = malloc(st.st_size+1);
	ret = -1;
	errno = ENOMEM;
	if (!buf)
		goto out;
	mem->buffer = buf;
	mem->size = st.st_size;
	ret = read(fd, buf, mem->size);
	if (ret < 0)
		goto free;
	buf[ret] = 0;
	if (ret == mem->size)
		goto out;
	errno = EIO;
	ret = -1;
free:
	free(mem->buffer);
	mem->buffer = NULL;
	mem->size = 0;
out:
	close(fd);
	return ret;
}


static void zip_read(struct zip_file *file, char **error, const char *filename)
{
	int size = 1024, n, read = 0;
	char *mem = malloc(size);

	while ((n = zip_fread(file, mem+read, size-read)) > 0) {
		read += n;
		size = read * 3 / 2;
		mem = realloc(mem, size);
	}
	mem[read] = 0;
	parse_xml_buffer(filename, mem, read, &dive_table, NULL, error);
	free(mem);
}

static int try_to_open_zip(const char *filename, struct memblock *mem, char **error)
{
	int success = 0;
	/* Grr. libzip needs to re-open the file, it can't take a buffer */
	struct zip *zip = subsurface_zip_open_readonly(filename, ZIP_CHECKCONS, NULL);

	if (zip) {
		int index;
		for (index = 0; ;index++) {
			struct zip_file *file = zip_fopen_index(zip, index, 0);
			if (!file)
				break;
			zip_read(file, error, filename);
			zip_fclose(file);
			success++;
		}
		subsurface_zip_close(zip);
	}
	return success;
}

static int try_to_xslt_open_csv(const char *filename, struct memblock *mem, char **error)
{
	char *buf;

	if (readfile(filename, mem) < 0) {
		if (error) {
			int len = strlen(translate("gettextFromC","Failed to read '%s'")) + strlen(filename);
			*error = malloc(len);
			snprintf(*error, len, translate("gettextFromC","Failed to read '%s'"), filename);
		}

		return 1;
	}

	/* Surround the CSV file content with XML tags to enable XSLT
	 * parsing
	 */
	buf = realloc(mem->buffer, mem->size + strlen("<csv></csv>"));
	if (buf != NULL) {
		memmove(buf + 5, buf, mem->size);
		memcpy(buf, "<csv>", 5);
		memcpy(buf + mem->size + 5, "</csv>", 7);
		mem->size += strlen("<csv></csv>");
		mem->buffer = buf;
	} else {
		/* we can atleast try to strdup a error... */
		*error = strdup("realloc failed in __func__\n");
		free(mem->buffer);
		return 1;
	}

	return 0;
}

static int try_to_open_db(const char *filename, struct memblock *mem, char **error)
{
	return parse_dm4_buffer(filename, mem->buffer, mem->size, &dive_table, error);
}

static timestamp_t parse_date(const char *date)
{
	int hour, min, sec;
	struct tm tm;
	char *p;

	memset(&tm, 0, sizeof(tm));
	tm.tm_mday = strtol(date, &p, 10);
	if (tm.tm_mday < 1 || tm.tm_mday > 31)
		return 0;
	for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
		if (!memcmp(p, monthname(tm.tm_mon), 3))
			break;
	}
	if (tm.tm_mon > 11)
		return 0;
	date = p+3;
	tm.tm_year = strtol(date, &p, 10);
	if (date == p)
		return 0;
	if (tm.tm_year < 70)
		tm.tm_year += 2000;
	if (tm.tm_year < 100)
		tm.tm_year += 1900;
	if (sscanf(p, "%d:%d:%d", &hour, &min, &sec) != 3)
		return 0;
	tm.tm_hour = hour;
	tm.tm_min = min;
	tm.tm_sec = sec;
	return utc_mktime(&tm);
}

enum csv_format {
	CSV_DEPTH, CSV_TEMP, CSV_PRESSURE
};

static void add_sample_data(struct sample *sample, enum csv_format type, double val)
{
	switch (type) {
	case CSV_DEPTH:
		sample->depth.mm = feet_to_mm(val);
		break;
	case CSV_TEMP:
		sample->temperature.mkelvin = F_to_mkelvin(val);
		break;
	case CSV_PRESSURE:
		sample->cylinderpressure.mbar = psi_to_mbar(val*4);
		break;
	}
}

/*
 * Cochran comma-separated values: depth in feet, temperature in F, pressure in psi.
 *
 * They start with eight comma-separated fields like:
 *
 *   filename: {C:\Analyst4\can\T036785.can},{C:\Analyst4\can\K031892.can}
 *   divenr: %d
 *   datetime: {03Sep11 16:37:22},{15Dec11 18:27:02}
 *   ??: 1
 *   serialnr??: {CCI134},{CCI207}
 *   computer??: {GeminiII},{CommanderIII}
 *   computer??: {GeminiII},{CommanderIII}
 *   ??: 1
 *
 * Followed by the data values (all comma-separated, all one long line).
 */
static int try_to_open_csv(const char *filename, struct memblock *mem, enum csv_format type)
{
	char *p = mem->buffer;
	char *header[8];
	int i, time;
	timestamp_t date;
	struct dive *dive;
	struct divecomputer *dc;

	for (i = 0; i < 8; i++) {
		header[i] = p;
		p = strchr(p, ',');
		if (!p)
			return 0;
		p++;
	}

	date = parse_date(header[2]);
	if (!date)
		return 0;

	dive = alloc_dive();
	dive->when = date;
	dive->number = atoi(header[1]);
	dc = &dive->dc;

	time = 0;
	for (;;) {
		char *end;
		double val;
		struct sample *sample;

		errno = 0;
		val = strtod(p,&end); // FIXME == localization issue
		if (end == p)
			break;
		if (errno)
			break;

		sample = prepare_sample(dc);
		sample->time.seconds = time;
		add_sample_data(sample, type, val);
		finish_sample(dc);

		time++;
		dc->duration.seconds = time;
		if (*end != ',')
			break;
		p = end+1;
	}
	record_dive(dive);
	return 1;
}

static int open_by_filename(const char *filename, const char *fmt, struct memblock *mem, char **error)
{
	/* Suunto Dive Manager files: SDE, ZIP; divelogs.de files: DLD */
	if (!strcasecmp(fmt, "SDE") || !strcasecmp(fmt, "ZIP") || !strcasecmp(fmt, "DLD"))
		return try_to_open_zip(filename, mem, error);

	/* CSV files */
	if (!strcasecmp(fmt, "CSV")) {
		int len = strlen(translate("gettextFromC","Failed to read '%s'. Use import for CSV files.")) + strlen(filename);
		*error = malloc(len);
		snprintf(*error, len, translate("gettextFromC","Failed to read '%s'. Use import for CSV files."), filename);
		return 1;
	}

#if ONCE_COCHRAN_IS_SUPPORTED
	/* Truly nasty intentionally obfuscated Cochran Anal software */
	if (!strcasecmp(fmt, "CAN"))
		return try_to_open_cochran(filename, mem, error);
#endif

	/* Cochran export comma-separated-value files */
	if (!strcasecmp(fmt, "DPT"))
		return try_to_open_csv(filename, mem, CSV_DEPTH);
	if (!strcasecmp(fmt, "TMP"))
		return try_to_open_csv(filename, mem, CSV_TEMP);
	if (!strcasecmp(fmt, "HP1"))
		return try_to_open_csv(filename, mem, CSV_PRESSURE);

	return 0;
}

static void parse_file_buffer(const char *filename, struct memblock *mem, char **error)
{
	char *fmt = strrchr(filename, '.');
	if (fmt && open_by_filename(filename, fmt+1, mem, error))
		return;

	if (!mem->size || !mem->buffer)
		return;

	parse_xml_buffer(filename, mem->buffer, mem->size, &dive_table, NULL, error);
}

void parse_file(const char *filename, char **error)
{
	struct memblock mem;
	char *fmt;

	if (readfile(filename, &mem) < 0) {
		/* we don't want to display an error if this was the default file */
		if (prefs.default_filename && ! strcmp(filename, prefs.default_filename))
			return;

		if (error) {
			int len = strlen(translate("gettextFromC","Failed to read '%s'")) + strlen(filename);
			*error = malloc(len);
			snprintf(*error, len, translate("gettextFromC","Failed to read '%s'"), filename);
		}

		return;
	}

	fmt = strrchr(filename, '.');
	if (fmt && (!strcasecmp(fmt + 1, "DB") || !strcasecmp(fmt + 1, "BAK"))) {
		if (!try_to_open_db(filename, &mem, error)) {
			free(mem.buffer);
			return;
		}
	}

	parse_file_buffer(filename, &mem, error);
	free(mem.buffer);
}

#define MAXCOLDIGITS 3
#define MAXCOLS 100
void parse_csv_file(const char *filename, int timef, int depthf, int tempf, int po2f, int cnsf, int stopdepthf, int sepidx, char **error)
{
	struct memblock mem;
	int pnr=0;
	char *params[19];
	char timebuf[MAXCOLDIGITS];
	char depthbuf[MAXCOLDIGITS];
	char tempbuf[MAXCOLDIGITS];
	char po2buf[MAXCOLDIGITS];
	char cnsbuf[MAXCOLDIGITS];
	char stopdepthbuf[MAXCOLDIGITS];
	char separator_index[MAXCOLDIGITS];
	time_t now;
	struct tm *timep;
	char curdate[9];
	char curtime[6];

	if (timef >= MAXCOLS || depthf >= MAXCOLS || tempf >= MAXCOLS || po2f >= MAXCOLS || cnsf >= MAXCOLS || stopdepthf >= MAXCOLS ) {
		int len = strlen(translate("gettextFromC", "Maximum number of supported columns on CSV import is %d")) + MAXCOLDIGITS;
		*error = malloc(len);
		snprintf(*error, len, translate("gettextFromC", "Maximum number of supported columns on CSV import is %d"), MAXCOLS);

		return;
	}
	snprintf(timebuf, MAXCOLDIGITS, "%d", timef);
	snprintf(depthbuf, MAXCOLDIGITS, "%d", depthf);
	snprintf(tempbuf, MAXCOLDIGITS, "%d", tempf);
	snprintf(po2buf, MAXCOLDIGITS, "%d", po2f);
	snprintf(cnsbuf, MAXCOLDIGITS, "%d", cnsf);
	snprintf(stopdepthbuf, MAXCOLDIGITS, "%d", stopdepthf);
	snprintf(separator_index, MAXCOLDIGITS, "%d", sepidx);
	time(&now);
	timep = localtime(&now);
	strftime(curdate, sizeof(curdate), "%Y%m%d", timep);

	/* As the parameter is numeric, we need to ensure that the leading zero
	* is not discarded during the transform, thus prepend time with 1 */
	strftime(curtime, sizeof(curtime), "1%H%M", timep);

	params[pnr++] = "timeField";
	params[pnr++] = timebuf;
	params[pnr++] = "depthField";
	params[pnr++] = depthbuf;
	params[pnr++] = "tempField";
	params[pnr++] = tempbuf;
	params[pnr++] = "po2Field";
	params[pnr++] = po2buf;
	params[pnr++] = "cnsField";
	params[pnr++] = cnsbuf;
	params[pnr++] = "stopdepthField";
	params[pnr++] = stopdepthbuf;
	params[pnr++] = "date";
	params[pnr++] = curdate;
	params[pnr++] = "time";
	params[pnr++] = curtime;
	params[pnr++] = "separatorIndex";
	params[pnr++] = separator_index;
	params[pnr++] = NULL;

	if (filename == NULL)
		return;

	if (try_to_xslt_open_csv(filename, &mem, error))
		return;

	parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params, error);
	free(mem.buffer);
}
