// SPDX-License-Identifier: GPL-2.0
#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 "divelist.h"
#include "file.h"
#include "git-access.h"
#include "qthelperfromc.h"

/* For SAMPLE_* */
#include <libdivecomputer/parser.h>

/* to check XSLT version number */
#include <libxslt/xsltconfig.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 == (int)mem->size) // converting to int loses a bit but size will never be that big
		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, 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;
	(void) parse_xml_buffer(filename, mem, read, &dive_table, NULL);
	free(mem);
}

int try_to_open_zip(const char *filename)
{
	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;
			/* skip parsing the divelogs.de pictures */
			if (strstr(zip_get_name(zip, index, 0), "pictures/"))
				continue;
			zip_read(file, filename);
			zip_fclose(file);
			success++;
		}
		subsurface_zip_close(zip);

		if (!success)
			return report_error(translate("gettextFromC", "No dives in the input file '%s'"), filename);
	}
	return success;
}

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

	if (mem->size == 0 && readfile(filename, mem) < 0)
		return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);

	/* Surround the CSV file content with XML tags to enable XSLT
	 * parsing
	 *
	 * Tag markers take: strlen("<></>") = 5
	 */
	buf = realloc(mem->buffer, mem->size + 7 + strlen(tag) * 2);
	if (buf != NULL) {
		char *starttag = NULL;
		char *endtag = NULL;

		starttag = malloc(3 + strlen(tag));
		endtag = malloc(5 + strlen(tag));

		if (starttag == NULL || endtag == NULL) {
			/* this is fairly silly - so the malloc fails, but we strdup the error?
			 * let's complete the silliness by freeing the two pointers in case one malloc succeeded
			 *  and the other one failed - this will make static analysis tools happy */
			free(starttag);
			free(endtag);
			free(buf);
			return report_error("Memory allocation failed in %s", __func__);
		}

		sprintf(starttag, "<%s>", tag);
		sprintf(endtag, "\n</%s>", tag);

		memmove(buf + 2 + strlen(tag), buf, mem->size);
		memcpy(buf, starttag, 2 + strlen(tag));
		memcpy(buf + mem->size + 2 + strlen(tag), endtag, 5 + strlen(tag));
		mem->size += (6 + 2 * strlen(tag));
		mem->buffer = buf;

		free(starttag);
		free(endtag);
	} else {
		free(mem->buffer);
		return report_error("realloc failed in %s", __func__);
	}

	return 0;
}

int db_test_func(void *param, int columns, char **data, char **column)
{
	(void) param;
	(void) columns;
	(void) column;
	return *data[0] == '0';
}


static int try_to_open_db(const char *filename, struct memblock *mem)
{
	sqlite3 *handle;
	char dm4_test[] = "select count(*) from sqlite_master where type='table' and name='Dive' and sql like '%ProfileBlob%'";
	char dm5_test[] = "select count(*) from sqlite_master where type='table' and name='Dive' and sql like '%SampleBlob%'";
	char shearwater_test[] = "select count(*) from sqlite_master where type='table' and name='system' and sql like '%dbVersion%'";
	char cobalt_test[] = "select count(*) from sqlite_master where type='table' and name='TrackPoints' and sql like '%DepthPressure%'";
	char divinglog_test[] = "select count(*) from sqlite_master where type='table' and name='DBInfo' and sql like '%PrgName%'";
	int retval;

	retval = sqlite3_open(filename, &handle);

	if (retval) {
		fprintf(stderr, "Database connection failed '%s'.\n", filename);
		return 1;
	}

	/* Testing if DB schema resembles Suunto DM5 database format */
	retval = sqlite3_exec(handle, dm5_test, &db_test_func, 0, NULL);
	if (!retval) {
		retval = parse_dm5_buffer(handle, filename, mem->buffer, mem->size, &dive_table);
		sqlite3_close(handle);
		return retval;
	}

	/* Testing if DB schema resembles Suunto DM4 database format */
	retval = sqlite3_exec(handle, dm4_test, &db_test_func, 0, NULL);
	if (!retval) {
		retval = parse_dm4_buffer(handle, filename, mem->buffer, mem->size, &dive_table);
		sqlite3_close(handle);
		return retval;
	}

	/* Testing if DB schema resembles Shearwater database format */
	retval = sqlite3_exec(handle, shearwater_test, &db_test_func, 0, NULL);
	if (!retval) {
		retval = parse_shearwater_buffer(handle, filename, mem->buffer, mem->size, &dive_table);
		sqlite3_close(handle);
		return retval;
	}

	/* Testing if DB schema resembles Atomic Cobalt database format */
	retval = sqlite3_exec(handle, cobalt_test, &db_test_func, 0, NULL);
	if (!retval) {
		retval = parse_cobalt_buffer(handle, filename, mem->buffer, mem->size, &dive_table);
		sqlite3_close(handle);
		return retval;
	}

	/* Testing if DB schema resembles Divinglog database format */
	retval = sqlite3_exec(handle, divinglog_test, &db_test_func, 0, NULL);
	if (!retval) {
		retval = parse_divinglog_buffer(handle, filename, mem->buffer, mem->size, &dive_table);
		sqlite3_close(handle);
		return retval;
	}

	sqlite3_close(handle);

	return retval;
}

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,
	POSEIDON_DEPTH,
	POSEIDON_TEMP,
	POSEIDON_SETPOINT,
	POSEIDON_SENSOR1,
	POSEIDON_SENSOR2,
	POSEIDON_NDL,
	POSEIDON_CEILING
};

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->pressure[0].mbar = psi_to_mbar(val * 4);
		break;
	case POSEIDON_DEPTH:
		sample->depth.mm = lrint(val * 0.5 * 1000);
		break;
	case POSEIDON_TEMP:
		sample->temperature.mkelvin = C_to_mkelvin(val * 0.2);
		break;
	case POSEIDON_SETPOINT:
		sample->setpoint.mbar = lrint(val * 10);
		break;
	case POSEIDON_SENSOR1:
		sample->o2sensor[0].mbar = lrint(val * 10);
		break;
	case POSEIDON_SENSOR2:
		sample->o2sensor[1].mbar = lrint(val * 10);
		break;
	case POSEIDON_NDL:
		sample->ndl.seconds = lrint(val * 60);
		break;
	case POSEIDON_CEILING:
		sample->stopdepth.mm = lrint(val * 1000);
		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(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)
{
	// hack to be able to provide a comment for the translated string
	static char *csv_warning = QT_TRANSLATE_NOOP3("gettextFromC",
						      "Cannot open CSV file %s; please use Import log file dialog",
						      "'Import log file' should be the same text as corresponding label in Import menu");

	/* 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);

	/* CSV files */
	if (!strcasecmp(fmt, "CSV"))
		return report_error(translate("gettextFromC", csv_warning), filename);
	/* Truly nasty intentionally obfuscated Cochran Anal software */
	if (!strcasecmp(fmt, "CAN"))
		return try_to_open_cochran(filename, mem);
	/* Cochran export comma-separated-value files */
	if (!strcasecmp(fmt, "DPT"))
		return try_to_open_csv(mem, CSV_DEPTH);
	if (!strcasecmp(fmt, "LVD"))
		return try_to_open_liquivision(filename, mem);
	if (!strcasecmp(fmt, "TMP"))
		return try_to_open_csv(mem, CSV_TEMP);
	if (!strcasecmp(fmt, "HP1"))
		return try_to_open_csv(mem, CSV_PRESSURE);

	return 0;
}

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

	if (!mem->size || !mem->buffer)
		return report_error("Out of memory parsing file %s\n", filename);

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

int check_git_sha(const char *filename, struct git_repository **git_p, const char **branch_p)
{
	struct git_repository *git;
	const char *branch = NULL;

	char *current_sha = strdup(saved_git_id);
	git = is_git_repository(filename, &branch, NULL, false);
	if (git_p)
		*git_p = git;
	if (branch_p)
		*branch_p = branch;
	if (prefs.cloud_git_url &&
	    strstr(filename, prefs.cloud_git_url)
	    && git == dummy_git_repository) {
		/* opening the cloud storage repository failed for some reason,
		 * so we don't know if there is additional data in the remote */
		free(current_sha);
		return 1;
	}
	/* if this is a git repository, do we already have this exact state loaded ?
	 * get the SHA and compare with what we currently have */
	if (git && git != dummy_git_repository) {
		const char *sha = get_sha(git, branch);
		if (!same_string(sha, "") &&
		    same_string(sha, current_sha)) {
			fprintf(stderr, "already have loaded SHA %s - don't load again\n", sha);
			free(current_sha);
			return 0;
		}
	}
	free(current_sha);
	return 1;
}

int parse_file(const char *filename)
{
	struct git_repository *git;
	const char *branch = NULL;
	char *current_sha = copy_string(saved_git_id);
	struct memblock mem;
	char *fmt;
	int ret;

	git = is_git_repository(filename, &branch, NULL, false);
	if (prefs.cloud_git_url &&
	    strstr(filename, prefs.cloud_git_url)
	    && git == dummy_git_repository) {
		/* opening the cloud storage repository failed for some reason
		 * give up here and don't send errors about git repositories */
		free(current_sha);
		return -1;
	}
	/* if this is a git repository, do we already have this exact state loaded ?
	 * get the SHA and compare with what we currently have */
	if (git && git != dummy_git_repository) {
		const char *sha = get_sha(git, branch);
		if (!same_string(sha, "") &&
		    same_string(sha, current_sha) &&
		    !unsaved_changes()) {
			fprintf(stderr, "already have loaded SHA %s - don't load again\n", sha);
			free(current_sha);
			return 0;
		}
	}
	free(current_sha);
	if (git)
		return git_load_dives(git, branch);

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

		return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
	} else if (ret == 0) {
		return report_error(translate("gettextFromC", "Empty file '%s'"), filename);
	}

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

	/* Divesoft Freedom */
	if (fmt && (!strcasecmp(fmt + 1, "DLF"))) {
		if (!parse_dlf_buffer(mem.buffer, mem.size)) {
			free(mem.buffer);
			return 0;
		}
		return -1;
	}

	/* DataTrak/Wlog */
	if (fmt && !strcasecmp(fmt + 1, "LOG")) {
		ret = datatrak_import(&mem, &dive_table);
		free(mem.buffer);
		return ret;
	}

	/* OSTCtools */
	if (fmt && (!strcasecmp(fmt + 1, "DIVE"))) {
		ostctools_import(filename, &dive_table);
		return 0;
	}

	ret = parse_file_buffer(filename, &mem);
	free(mem.buffer);
	return ret;
}

#define MATCH(buffer, pattern) \
	memcmp(buffer, pattern, strlen(pattern))

char *parse_mkvi_value(const char *haystack, const char *needle)
{
	char *lineptr, *valueptr, *endptr, *ret = NULL;

	if ((lineptr = strstr(haystack, needle)) != NULL) {
		if ((valueptr = strstr(lineptr, ": ")) != NULL) {
			valueptr += 2;
		}
		if ((endptr = strstr(lineptr, "\n")) != NULL) {
			char terminator = '\n';
			if (*(endptr - 1) == '\r') {
				--endptr;
				terminator = '\r';
			}
			*endptr = 0;
			ret = copy_string(valueptr);
			*endptr = terminator;

		}
	}
	return ret;
}

char *next_mkvi_key(const char *haystack)
{
	char *valueptr, *endptr, *ret = NULL;

	if ((valueptr = strstr(haystack, "\n")) != NULL) {
		valueptr += 1;
		if ((endptr = strstr(valueptr, ": ")) != NULL) {
			*endptr = 0;
			ret = strdup(valueptr);
			*endptr = ':';
		}
	}
	return ret;
}

int parse_txt_file(const char *filename, const char *csv)
{
	struct memblock memtxt, memcsv;

	if (readfile(filename, &memtxt) < 0) {
		return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
	}

	/*
	 * MkVI stores some information in .txt file but the whole profile and events are stored in .csv file. First
	 * make sure the input .txt looks like proper MkVI file, then start parsing the .csv.
	 */
	if (MATCH(memtxt.buffer, "MkVI_Config") == 0) {
		int d, m, y, he;
		int hh = 0, mm = 0, ss = 0;
		int prev_depth = 0, cur_sampletime = 0, prev_setpoint = -1, prev_ndl = -1;
		bool has_depth = false, has_setpoint = false, has_ndl = false;
		char *lineptr, *key, *value;
		int cur_cylinder_index = 0;
		unsigned int prev_time = 0;

		struct dive *dive;
		struct divecomputer *dc;
		struct tm cur_tm;

		value = parse_mkvi_value(memtxt.buffer, "Dive started at");
		if (sscanf(value, "%d-%d-%d %d:%d:%d", &y, &m, &d, &hh, &mm, &ss) != 6) {
			free(value);
			return -1;
		}
		free(value);
		cur_tm.tm_year = y;
		cur_tm.tm_mon = m - 1;
		cur_tm.tm_mday = d;
		cur_tm.tm_hour = hh;
		cur_tm.tm_min = mm;
		cur_tm.tm_sec = ss;

		dive = alloc_dive();
		dive->when = utc_mktime(&cur_tm);;
		dive->dc.model = strdup("Poseidon MkVI Discovery");
		value = parse_mkvi_value(memtxt.buffer, "Rig Serial number");
		dive->dc.deviceid = atoi(value);
		free(value);
		dive->dc.divemode = CCR;
		dive->dc.no_o2sensors = 2;

		dive->cylinder[cur_cylinder_index].cylinder_use = OXYGEN;
		dive->cylinder[cur_cylinder_index].type.size.mliter = 3000;
		dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000;
		dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6");
		dive->cylinder[cur_cylinder_index].gasmix.o2.permille = 1000;
		cur_cylinder_index++;

		dive->cylinder[cur_cylinder_index].cylinder_use = DILUENT;
		dive->cylinder[cur_cylinder_index].type.size.mliter = 3000;
		dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000;
		dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6");
		value = parse_mkvi_value(memtxt.buffer, "Helium percentage");
		he = atoi(value);
		free(value);
		value = parse_mkvi_value(memtxt.buffer, "Nitrogen percentage");
		dive->cylinder[cur_cylinder_index].gasmix.o2.permille = (100 - atoi(value) - he) * 10;
		free(value);
		dive->cylinder[cur_cylinder_index].gasmix.he.permille = he * 10;
		cur_cylinder_index++;

		lineptr = strstr(memtxt.buffer, "Dive started at");
		while (lineptr && *lineptr && (lineptr = strchr(lineptr, '\n')) && ++lineptr) {
			key = next_mkvi_key(lineptr);
			if (!key)
				break;
			value = parse_mkvi_value(lineptr, key);
			if (!value) {
				free(key);
				break;
			}
			add_extra_data(&dive->dc, key, value);
			free(key);
			free(value);
		}
		dc = &dive->dc;

		/*
		 * Read samples from the CSV file. A sample contains all the lines with same timestamp. The CSV file has
		 * the following format:
		 *
		 * timestamp, type, value
		 *
		 * And following fields are of interest to us:
		 *
		 * 	6	sensor1
		 * 	7	sensor2
		 * 	8	depth
		 *	13	o2 tank pressure
		 *	14	diluent tank pressure
		 *	20	o2 setpoint
		 *	39	water temp
		 */

		if (readfile(csv, &memcsv) < 0) {
			free(dive);
			return report_error(translate("gettextFromC", "Poseidon import failed: unable to read '%s'"), csv);
		}
		lineptr = memcsv.buffer;
		for (;;) {
			struct sample *sample;
			int type;
			int value;
			int sampletime;
			int gaschange = 0;

			/* Collect all the information for one sample */
			sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value);

			has_depth = false;
			has_setpoint = false;
			has_ndl = false;
			sample = prepare_sample(dc);

			/*
			 * There was a bug in MKVI download tool that resulted in erroneous sample
			 * times. This fix should work similarly as the vendor's own.
			 */

			sample->time.seconds = cur_sampletime < 0xFFFF * 3 / 4 ? cur_sampletime : prev_time;
			prev_time = sample->time.seconds;

			do {
				int i = sscanf(lineptr, "%d,%d,%d", &sampletime, &type, &value);
				switch (i) {
				case 3:
					switch (type) {
					case 0:
						//Mouth piece position event: 0=OC, 1=CC, 2=UN, 3=NC
						switch (value) {
						case 0:
							add_event(dc, cur_sampletime, 0, 0, 0,
									QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position OC"));
							break;
						case 1:
							add_event(dc, cur_sampletime, 0, 0, 0,
									QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position CC"));
							break;
						case 2:
							add_event(dc, cur_sampletime, 0, 0, 0,
									QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position unknown"));
							break;
						case 3:
							add_event(dc, cur_sampletime, 0, 0, 0,
									QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position not connected"));
							break;
						}
						break;
					case 3:
						//Power Off event
						add_event(dc, cur_sampletime, 0, 0, 0,
								QT_TRANSLATE_NOOP("gettextFromC", "Power off"));
						break;
					case 4:
						//Battery State of Charge in %
#ifdef SAMPLE_EVENT_BATTERY
						add_event(dc, cur_sampletime, SAMPLE_EVENT_BATTERY, 0,
								value, QT_TRANSLATE_NOOP("gettextFromC", "battery"));
#endif
						break;
					case 6:
						//PO2 Cell 1 Average
						add_sample_data(sample, POSEIDON_SENSOR1, value);
						break;
					case 7:
						//PO2 Cell 2 Average
						add_sample_data(sample, POSEIDON_SENSOR2, value);
						break;
					case 8:
						//Depth * 2
						has_depth = true;
						prev_depth = value;
						add_sample_data(sample, POSEIDON_DEPTH, value);
						break;
						//9 Max Depth * 2
						//10 Ascent/Descent Rate * 2
					case 11:
						//Ascent Rate Alert >10 m/s
						add_event(dc, cur_sampletime, SAMPLE_EVENT_ASCENT, 0, 0,
								QT_TRANSLATE_NOOP("gettextFromC", "ascent"));
						break;
					case 13:
						//O2 Tank Pressure
						add_sample_pressure(sample, 0, lrint(value * 1000));
						break;
					case 14:
						//Diluent Tank Pressure
						add_sample_pressure(sample, 1, lrint(value * 1000));
						break;
						//16 Remaining dive time #1?
						//17 related to O2 injection
					case 20:
						//PO2 Setpoint
						has_setpoint = true;
						prev_setpoint = value;
						add_sample_data(sample, POSEIDON_SETPOINT, value);
						break;
					case 22:
						//End of O2 calibration Event: 0 = OK, 2 = Failed, rest of dive setpoint 1.0
						if (value == 2)
							add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_END, 0,
									QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration failed"));
						add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_END, 0,
								QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration"));
						break;
					case 25:
						//25 Max Ascent depth
						add_sample_data(sample, POSEIDON_CEILING, value);
						break;
					case 31:
						//Start of O2 calibration Event
						add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_BEGIN, 0,
								QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration"));
						break;
					case 37:
						//Remaining dive time #2?
						has_ndl = true;
						prev_ndl = value;
						add_sample_data(sample, POSEIDON_NDL, value);
						break;
					case 39:
						// Water Temperature in Celcius
						add_sample_data(sample, POSEIDON_TEMP, value);
						break;
					case 85:
						//He diluent part in %
						gaschange += value << 16;
						break;
					case 86:
						//O2 diluent part in %
						gaschange += value;
						break;
						//239 Unknown, maybe PO2 at sensor validation?
						//240 Unknown, maybe PO2 at sensor validation?
						//247 Unknown, maybe PO2 Cell 1 during pressure test
						//248 Unknown, maybe PO2 Cell 2 during pressure test
						//250 PO2 Cell 1
						//251 PO2 Cell 2
					default:
						break;
					} /* sample types */
					break;
				case EOF:
					break;
				default:
					printf("Unable to parse input: %s\n", lineptr);
					break;
				}

				lineptr = strchr(lineptr, '\n');
				if (!lineptr || !*lineptr)
					break;
				lineptr++;

				/* Grabbing next sample time */
				sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value);
			} while (sampletime == cur_sampletime);

			if (gaschange)
				add_event(dc, cur_sampletime, SAMPLE_EVENT_GASCHANGE2, 0, gaschange,
						QT_TRANSLATE_NOOP("gettextFromC", "gaschange"));
			if (!has_depth)
				add_sample_data(sample, POSEIDON_DEPTH, prev_depth);
			if (!has_setpoint && prev_setpoint >= 0)
				add_sample_data(sample, POSEIDON_SETPOINT, prev_setpoint);
			if (!has_ndl && prev_ndl >= 0)
				add_sample_data(sample, POSEIDON_NDL, prev_ndl);
			finish_sample(dc);

			if (!lineptr || !*lineptr)
				break;
		}
		record_dive(dive);
		return 1;
	} else {
		return 0;
	}

	return 0;
}

#define MAXCOLDIGITS 10
#define DATESTR 9
#define TIMESTR 6

int parse_dan_format(const char *filename, char **params, int pnr)
{
	int ret = 0, i;
	size_t end_ptr = 0;
	struct memblock mem, mem_csv;
	char tmpbuf[MAXCOLDIGITS];

	char *ptr = NULL;
	char *NL = NULL;
	char *iter = NULL;

	if (readfile(filename, &mem) < 0)
		return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);

	/* Determine NL (new line) character and the start of CSV data */
	if ((ptr = strstr(mem.buffer, "\r\n")) != NULL) {
		NL = "\r\n";
	} else if ((ptr = strstr(mem.buffer, "\n")) != NULL) {
		NL = "\n";
	} else {
		fprintf(stderr, "DEBUG: failed to detect NL\n");
		return -1;
	}

	while ((end_ptr < mem.size) && (ptr = strstr(mem.buffer + end_ptr, "ZDH"))) {

		/*
		 * Process the dives, but let the last round be parsed
		 * from C++ code
		 */

		if (end_ptr)
			process_dives(true, false);

		mem_csv.buffer = malloc(mem.size + 1);
		mem_csv.size = mem.size;

		iter = ptr + 4;
		iter = strchr(iter, '|');
		if (iter) {
			memcpy(tmpbuf, ptr + 4, iter - ptr - 4);
			tmpbuf[iter - ptr - 4] = 0;
			params[pnr] = "diveNro";
			params[pnr + 1] = strdup(tmpbuf);
		}

		//fprintf(stderr, "DEBUG: BEGIN end_ptr %d round %d <%s>\n", end_ptr, j++, ptr);
		iter = ptr + 1;
		for (i = 0; i <= 4 && iter; ++i) {
			iter = strchr(iter, '|');
			if (iter)
				++iter;
		}

		/* Setting date */
		memcpy(tmpbuf, iter, 8);
		tmpbuf[8] = 0;
		params[pnr + 2] = "date";
		params[pnr + 3] = strdup(tmpbuf);

		/* Setting time, gotta prepend it with 1 to
		 * avoid octal parsing (this is stripped out in
		 * XSLT */
		tmpbuf[0] = '1';
		memcpy(tmpbuf + 1, iter + 8, 6);
		tmpbuf[7] = 0;
		params[pnr + 4] = "time";
		params[pnr + 5] = strdup(tmpbuf);
		params[pnr + 6] = NULL;

		ptr = strstr(ptr, "ZDP{");
		if (ptr && ptr[4] == '}') {
			end_ptr += ptr - (char *)mem_csv.buffer;
			return report_error(translate("gettextFromC", "No dive profile found from '%s'"), filename);
		}
		if (ptr)
			ptr = strstr(ptr, NL);
		if (ptr)
			ptr += strlen(NL);

		end_ptr = ptr - (char *)mem.buffer;

		/* Copy the current dive data to start of mem_csv buffer */
		memcpy(mem_csv.buffer, ptr, mem.size - (ptr - (char *)mem.buffer));
		ptr = strstr(mem_csv.buffer, "ZDP}");
		if (ptr) {
			*ptr = 0;
		} else {
			fprintf(stderr, "DEBUG: failed to find end ZDP\n");
			return -1;
		}
		mem_csv.size = ptr - (char*)mem_csv.buffer;

		if (try_to_xslt_open_csv(filename, &mem_csv, "csv"))
			return -1;

		ret |= parse_xml_buffer(filename, mem_csv.buffer, mem_csv.size, &dive_table, (const char **)params);
		end_ptr += ptr - (char *)mem_csv.buffer;
		free(mem_csv.buffer);
	}

	free(mem.buffer);
	for (i = 0; params[i]; i += 2)
		free(params[i + 1]);

	return ret;
}

int parse_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate)
{
	int ret, i;
	struct memblock mem;
	time_t now;
	struct tm *timep = NULL;
	char tmpbuf[MAXCOLDIGITS];

	/* Increase the limits for recursion and variables on XSLT
	 * parsing */
	xsltMaxDepth = 30000;
#if LIBXSLT_VERSION > 10126
	xsltMaxVars = 150000;
#endif

	if (filename == NULL)
		return report_error("No CSV filename");

	mem.size = 0;
	if (!strcmp("DL7", csvtemplate)) {
		return parse_dan_format(filename, params, pnr);
	} else if (strcmp(params[0], "date")) {
		time(&now);
		timep = localtime(&now);

		strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep);
		params[pnr++] = "date";
		params[pnr++] = strdup(tmpbuf);

		/* 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(tmpbuf, MAXCOLDIGITS, "1%H%M", timep);
		params[pnr++] = "time";
		params[pnr++] = strdup(tmpbuf);
		params[pnr++] = NULL;
	}

	if (try_to_xslt_open_csv(filename, &mem, csvtemplate))
		return -1;

	/*
	 * Lets print command line for manual testing with xsltproc if
	 * verbosity level is high enough. The printed line needs the
	 * input file added as last parameter.
	 */

#ifndef SUBSURFACE_MOBILE
	if (verbose >= 2) {
		fprintf(stderr, "(echo '<csv>'; cat %s;echo '</csv>') | xsltproc ", filename);
		for (i=0; params[i]; i+=2)
			fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]);
		fprintf(stderr, "%s/xslt/csv2xml.xslt -\n", SUBSURFACE_SOURCE);
	}
#endif
	ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params);

	free(mem.buffer);
	for (i = 0; params[i]; i += 2)
		free(params[i + 1]);

	return ret;
}

#define SBPARAMS 40
int parse_seabear_log(const char *filename)
{
	char *params[SBPARAMS];
	int pnr = 0;

	pnr = parse_seabear_header(filename, params, pnr);

	if (parse_seabear_csv_file(filename, params, pnr, "csv") < 0) {
		return -1;
	}

	return 0;
}

int parse_seabear_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate)
{
	int ret, i;
	struct memblock mem;
	time_t now;
	struct tm *timep = NULL;
	char *ptr, *ptr_old = NULL;
	char *NL = NULL;
	char tmpbuf[MAXCOLDIGITS];

	/* Increase the limits for recursion and variables on XSLT
	 * parsing */
	xsltMaxDepth = 30000;
#if LIBXSLT_VERSION > 10126
	xsltMaxVars = 150000;
#endif

	time(&now);
	timep = localtime(&now);

	strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep);
	params[pnr++] = "date";
	params[pnr++] = strdup(tmpbuf);

	/* 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(tmpbuf, MAXCOLDIGITS, "1%H%M", timep);
	params[pnr++] = "time";
	params[pnr++] = strdup(tmpbuf);


	if (filename == NULL)
		return report_error("No CSV filename");

	if (readfile(filename, &mem) < 0)
		return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);

	/* Determine NL (new line) character and the start of CSV data */
	ptr = mem.buffer;
	while ((ptr = strstr(ptr, "\r\n\r\n")) != NULL) {
		ptr_old = ptr;
		ptr += 1;
		NL = "\r\n";
	}

	if (!ptr_old) {
		ptr = mem.buffer;
		while ((ptr = strstr(ptr, "\n\n")) != NULL) {
			ptr_old = ptr;
			ptr += 1;
			NL = "\n";
		}
		ptr_old += 2;
	} else
		ptr_old += 4;

	/*
	 * If file does not contain empty lines, it is not a valid
	 * Seabear CSV file.
	 */
	if (NL == NULL)
		return -1;

	/*
	 * On my current sample of Seabear DC log file, the date is
	 * without any identifier. Thus we must search for the previous
	 * line and step through from there. That is the line after
	 * Serial number.
	 */
	ptr = strstr(mem.buffer, "Serial number:");
	if (ptr)
		ptr = strstr(ptr, NL);

	/*
	 * Write date and time values to params array, if available in
	 * the CSV header
	 */

	if (ptr) {
		ptr += strlen(NL) + 2;
		/*
		 * pnr is the index of NULL on the params as filled by
		 * the init function. The two last entries should be
		 * date and time. Here we overwrite them with the data
		 * from the CSV header.
		 */

		memcpy(params[pnr - 3], ptr, 4);
		memcpy(params[pnr - 3] + 4, ptr + 5, 2);
		memcpy(params[pnr - 3] + 6, ptr + 8, 2);
		params[pnr - 3][8] = 0;

		memcpy(params[pnr - 1] + 1, ptr + 11, 2);
		memcpy(params[pnr - 1] + 3, ptr + 14, 2);
		params[pnr - 1][5] = 0;
	}

	params[pnr++] = NULL;

	/* Move the CSV data to the start of mem buffer */
	memmove(mem.buffer, ptr_old, mem.size - (ptr_old - (char*)mem.buffer));
	mem.size = (int)mem.size - (ptr_old - (char*)mem.buffer);

	if (try_to_xslt_open_csv(filename, &mem, csvtemplate))
		return -1;

	/*
	 * Lets print command line for manual testing with xsltproc if
	 * verbosity level is high enough. The printed line needs the
	 * input file added as last parameter.
	 */

	if (verbose >= 2) {
		fprintf(stderr, "xsltproc ");
		for (i=0; params[i]; i+=2)
			fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]);
		fprintf(stderr, "xslt/csv2xml.xslt\n");
	}

	ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params);
	free(mem.buffer);
	for (i = 0; params[i]; i += 2)
		free(params[i + 1]);

	return ret;
}

int parse_manual_file(const char *filename, char **params, int pnr)
{
	struct memblock mem;
	time_t now;
	struct tm *timep;
	char curdate[9];
	char curtime[6];
	int ret, i;


	time(&now);
	timep = localtime(&now);
	strftime(curdate, DATESTR, "%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, TIMESTR, "1%H%M", timep);


	params[pnr++] = strdup("date");
	params[pnr++] = strdup(curdate);
	params[pnr++] = strdup("time");
	params[pnr++] = strdup(curtime);
	params[pnr++] = NULL;

	if (filename == NULL)
		return report_error("No manual CSV filename");

	mem.size = 0;
	if (try_to_xslt_open_csv(filename, &mem, "manualCSV"))
		return -1;

#ifndef SUBSURFACE_MOBILE
	if (verbose >= 2) {
		fprintf(stderr, "(echo '<manualCSV>'; cat %s;echo '</manualCSV>') | xsltproc ", filename);
		for (i=0; params[i]; i+=2)
			fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]);
		fprintf(stderr, "%s/xslt/manualcsv2xml.xslt -\n", SUBSURFACE_SOURCE);
	}
#endif
	ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params);

	free(mem.buffer);
	for (i = 0; i < pnr - 2; ++i)
		free(params[i]);
	return ret;
}
