#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <libdivecomputer/parser.h>

#include "dive.h"
#include "errorhelper.h"
#include "subsurface-string.h"
#include "divelist.h"
#include "divelog.h"
#include "file.h"
#include "format.h"
#include "parse.h"
#include "sample.h"
#include "divelist.h"
#include "gettext.h"
#include "import-csv.h"
#include "qthelper.h"
#include "xmlparams.h"

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

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);
}

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;
	}
}

static char *parse_dan_new_line(char *buf, const char *NL)
{
	char *iter = buf;

	if (!iter)
		return NULL;

	iter = strstr(iter, NL);
	if (iter) {
		iter += strlen(NL);
	} else {
		report_info("DEBUG: No new line found");
		return NULL;
	}
	return iter;
}

static int try_to_xslt_open_csv(const char *filename, std::string &mem, const char *tag);
static int parse_dan_format(const char *filename, struct xml_params *params, struct divelog *log)
{
	int ret = 0, i;
	size_t end_ptr = 0;
	char tmpbuf[MAXCOLDIGITS];
	int params_orig_size = xml_params_count(params);

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

	auto [mem, err] = readfile(filename);
	if (err < 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.data(), "\r\n")) != NULL) {
		NL = "\r\n";
	} else if ((ptr = strstr(mem.data(), "\n")) != NULL) {
		NL = "\n";
	} else {
		report_info("DEBUG: failed to detect NL");
		return -1;
	}

	while ((end_ptr < mem.size()) && (ptr = strstr(mem.data() + end_ptr, "ZDH"))) {
		xml_params_resize(params, params_orig_size); // restart with original parameter block
		char *iter_end = NULL;

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

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

		if (!iter) {
			report_info("DEBUG: Data corrupt");
			return -1;
		}

		/* Setting date */
		memcpy(tmpbuf, iter, 8);
		tmpbuf[8] = 0;
		xml_params_add(params, "date", 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;
		xml_params_add(params, "time", tmpbuf);

		/* Air temperature */
		memset(tmpbuf, 0, sizeof(tmpbuf));
		iter = strchr(iter, '|');

		if (iter) {
			iter = iter + 1;
			iter_end = strchr(iter, '|');

			if (iter_end) {
				memcpy(tmpbuf, iter, iter_end - iter);
				xml_params_add(params, "airTemp", tmpbuf);
			}
		}

		/* Search for the next line */
		if (iter)
			iter = parse_dan_new_line(iter, NL);
		if (!iter)
			return -1;

		/* We got a trailer, no samples on this dive */
		if (strncmp(iter, "ZDT", 3) == 0) {
			end_ptr = iter - mem.data();

			/* Water temperature */
			memset(tmpbuf, 0, sizeof(tmpbuf));
			for (i = 0; i < 5 && iter; ++i)
				iter = strchr(iter + 1, '|');

			if (iter) {
				iter = iter + 1;
				iter_end = strchr(iter, '|');

				if (iter_end) {
					memcpy(tmpbuf, iter, iter_end - iter);
					xml_params_add(params, "waterTemp", tmpbuf);
				}
			}
			ret |= parse_xml_buffer(filename, "<csv></csv>", 11, log, params);
			continue;
		}

		/* After ZDH we should get either ZDT (above) or ZDP */
		if (strncmp(iter, "ZDP{", 4) != 0) {
			report_info("DEBUG: Input appears to violate DL7 specification");
			end_ptr = iter - mem.data();
			continue;
		}

		if (ptr && ptr[4] == '}')
			return report_error(translate("gettextFromC", "No dive profile found from '%s'"), filename);

		if (ptr)
			ptr = parse_dan_new_line(ptr, NL);
		if (!ptr)
			return -1;

		end_ptr = ptr - mem.data();

		/* Copy the current dive data to start of mem_csv buffer */
		std::string mem_csv(ptr,  mem.size() - (ptr - mem.data()));

		ptr = strstr(mem_csv.data(), "ZDP}");
		if (ptr) {
			*ptr = 0;
		} else {
			report_info("DEBUG: failed to find end ZDP");
			return -1;
		}
		mem_csv.resize(ptr - mem_csv.data());
		end_ptr += ptr - mem_csv.data();

		iter = parse_dan_new_line(ptr + 1, NL);
		if (iter && strncmp(iter, "ZDT", 3) == 0) {
			/* Water temperature */
			memset(tmpbuf, 0, sizeof(tmpbuf));
			for (i = 0; i < 5 && iter; ++i)
				iter = strchr(iter + 1, '|');

			if (iter) {
				iter = iter + 1;
				iter_end = strchr(iter, '|');

				if (iter_end) {
					memcpy(tmpbuf, iter, iter_end - iter);
					xml_params_add(params, "waterTemp", tmpbuf);
				}
			}
		}

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

		ret |= parse_xml_buffer(filename, mem_csv.data(), mem_csv.size(), log, params);
	}

	return ret;
}

int parse_csv_file(const char *filename, struct xml_params *params, const char *csvtemplate, struct divelog *log)
{
	int ret;
	std::string 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");

	if (!strcmp("DL7", csvtemplate)) {
		return parse_dan_format(filename, params, log);
	} else if (strcmp(xml_params_get_key(params, 0), "date")) {
		time(&now);
		timep = localtime(&now);

		strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep);
		xml_params_add(params, "date", 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);
		xml_params_add(params, "time", tmpbuf);
	}

	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) {
		std::string info = format_string_std("(echo '<csv>'; cat %s;echo '</csv>') | xsltproc ", filename);
		for (int i = 0; i < xml_params_count(params); i++)
			info += format_string_std("--stringparam %s %s ", xml_params_get_key(params, i), xml_params_get_value(params, i));
		info += format_string_std("%s/xslt/%s -", SUBSURFACE_SOURCE, csvtemplate);
		report_info("%s", info.c_str());
	}
#endif
	ret = parse_xml_buffer(filename, mem.data(), mem.size(), log, params);

	return ret;
}


static int try_to_xslt_open_csv(const char *filename, std::string &mem, const char *tag)
{
	size_t amp = 0;

	if (mem.empty()) {
		auto [mem2, err] = readfile(filename);
		if (err < 0)
			return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
		if (mem2.empty())
			return 0; // Empty file - nothing to do. Guess that's a "success".
		mem = std::move(mem2);
	}

	/* Count ampersand characters */
	for (size_t i = 0; i < mem.size(); ++i) {
		if (mem[i] == '&') {
			++amp;
		}
	}


	/* Surround the CSV file content with XML tags to enable XSLT
	 * parsing
	 *
	 * Tag markers take: strlen("<></>") = 5
	 * Reserve also room for encoding ampersands "&" => "&amp;"
	 *
	 * Attention: This code is quite subtle, because we reserve one
	 * more byte than we use and put a '\0' there.
	 */

	size_t tag_name_size = strlen(tag);
	size_t old_size = mem.size();
	mem.resize(mem.size() + tag_name_size * 2 + 5 + amp * 4);
	const char *ptr_in = mem.data() + old_size;
	char *ptr_out = mem.data() + mem.size();

	/* Add end tag */
	*--ptr_out = '>';
	ptr_out -= tag_name_size;
	memcpy(ptr_out, tag, tag_name_size);
	*--ptr_out = '/';
	*--ptr_out = '<';

	while (--ptr_in >= mem.data()) {
		if (*ptr_in == '&') {
			*--ptr_out = ';';
			*--ptr_out = 'p';
			*--ptr_out = 'm';
			*--ptr_out = 'a';
		}
		*--ptr_out = *ptr_in;
	}

	/* Add start tag */
	*--ptr_out = '>';
	ptr_out -= tag_name_size;
	memcpy(ptr_out, tag, tag_name_size);
	*--ptr_out = '<';

	// On Windows, ptrdiff_t is long long int, on Linux it is long int.
	// Windows doesn't support the ptrdiff_t format specifier "%td", so
	// let's cast to long int.
	if (ptr_out != mem.data())
		report_info("try_to_xslt_open_csv(): ptr_out off by %ld. This shouldn't happen", static_cast<long int>(ptr_out - mem.data()));

	return 0;
}

int try_to_open_csv(std::string &mem, enum csv_format type, struct divelog *log)
{
	char *p = mem.data();
	char *header[8];
	int i, time;
	timestamp_t date;
	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;

	auto dive = std::make_unique<struct dive>();
	dive->when = date;
	dive->number = atoi(header[1]);
	dc = &dive->dcs[0];

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

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

static std::string parse_mkvi_value(const char *haystack, const char *needle)
{
	const char *lineptr, *valueptr, *endptr;

	if ((lineptr = strstr(haystack, needle)) != NULL) {
		if ((valueptr = strstr(lineptr, ": ")) != NULL) {
			valueptr += 2;

			if ((endptr = strstr(lineptr, "\n")) != NULL) {
				if (*(endptr - 1) == '\r')
					--endptr;
				return std::string(valueptr, endptr - valueptr);
			}
		}
	}
	return std::string();
}

static std::string next_mkvi_key(const char *haystack)
{
	const char *valueptr, *endptr;

	if ((valueptr = strstr(haystack, "\n")) != NULL) {
		valueptr += 1;
		if ((endptr = strstr(valueptr, ": ")) != NULL)
			return std::string(valueptr, endptr - valueptr);
	}
	return std::string();
}

int parse_txt_file(const char *filename, const char *csv, struct divelog *log)
{
	auto [memtxt, err] = readfile(filename);
	if (err < 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.data(), "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;
		int prev_time = 0;

		struct divecomputer *dc;
		struct tm cur_tm;

		std::string value = parse_mkvi_value(memtxt.data(), "Dive started at");
		if (sscanf(value.c_str(), "%d-%d-%d %d:%d:%d", &y, &m, &d, &hh, &mm, &ss) != 6)
			return -1;
		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;

		auto dive = std::make_unique<struct dive>();
		dive->when = utc_mktime(&cur_tm);;
		dive->dcs[0].model = "Poseidon MkVI Discovery";
		value = parse_mkvi_value(memtxt.data(), "Rig Serial number");
		dive->dcs[0].deviceid = atoi(value.c_str());
		dive->dcs[0].divemode = CCR;
		dive->dcs[0].no_o2sensors = 2;

		{
			cylinder_t cyl;
			cyl.cylinder_use = OXYGEN;
			cyl.type.size.mliter = 3000;
			cyl.type.workingpressure.mbar = 200000;
			cyl.type.description = "3l Mk6";
			cyl.gasmix.o2.permille = 1000;
			cyl.manually_added = true;
			cyl.bestmix_o2 = 0;
			cyl.bestmix_he = 0;
			dive->cylinders.push_back(std::move(cyl));
		}

		{
			cylinder_t cyl;
			cyl.cylinder_use = DILUENT;
			cyl.type.size.mliter = 3000;
			cyl.type.workingpressure.mbar = 200000;
			cyl.type.description = "3l Mk6";
			value = parse_mkvi_value(memtxt.data(), "Helium percentage");
			he = atoi(value.c_str());
			value = parse_mkvi_value(memtxt.data(), "Nitrogen percentage");
			cyl.gasmix.o2.permille = (100 - atoi(value.c_str()) - he) * 10;
			cyl.gasmix.he.permille = he * 10;
			dive->cylinders.push_back(std::move(cyl));
		}

		lineptr = strstr(memtxt.data(), "Dive started at");
		while (!empty_string(lineptr) && (lineptr = strchr(lineptr, '\n'))) {
			++lineptr;	// Skip over '\n'
			std::string key = next_mkvi_key(lineptr);
			if (key.empty())
				break;
			std::string value = parse_mkvi_value(lineptr, key.c_str());
			if (value.empty())
				break;
			add_extra_data(&dive->dcs[0], key, value);
		}
		dc = &dive->dcs[0];

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

		auto [memcsv, err] = readfile(csv);
		if (err < 0)
			return report_error(translate("gettextFromC", "Poseidon import failed: unable to read '%s'"), csv);
		lineptr = memcsv.data();
		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 Celsius
						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:
					report_info("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);

			if (!lineptr || !*lineptr)
				break;
		}
		log->dives.record_dive(std::move(dive));
		return 1;
	} else {
		return 0;
	}

	return 0;
}

#define DATESTR 9
#define TIMESTR 6

#define SBPARAMS 40
static int parse_seabear_csv_file(const char *filename, struct xml_params *params, const char *csvtemplate, struct divelog *log);
int parse_seabear_log(const char *filename, struct divelog *log)
{
	struct xml_params *params = alloc_xml_params();
	int ret;

	parse_seabear_header(filename, params);
	ret = parse_seabear_csv_file(filename, params, "csv", log) < 0 ? -1 : 0;

	free_xml_params(params);

	return ret;
}


static int parse_seabear_csv_file(const char *filename, struct xml_params *params, const char *csvtemplate, struct divelog *log)
{
	int ret, i;
	time_t now;
	struct tm *timep = NULL;
	char *ptr, *ptr_old = NULL;
	const 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);
	xml_params_add(params, "date", 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);
	xml_params_add(params, "time", tmpbuf);

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

	auto [mem, err] = readfile(filename);
	if (err < 0)
		return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);

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

	if (!ptr_old) {
		ptr = (char *)mem.data();
		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((char *)mem.data(), "Serial number:");
	if (ptr)
		ptr = strstr(ptr, NL);

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

	if (ptr) {
		/*
		 * The two last entries should be date and time.
		 * Here we overwrite them with the data from the
		 * CSV header.
		 */
		char buf[10];

		ptr += strlen(NL) + 2;
		memcpy(buf, ptr, 4);
		memcpy(buf + 4, ptr + 5, 2);
		memcpy(buf + 6, ptr + 8, 2);
		buf[8] = 0;
		xml_params_set_value(params, xml_params_count(params) - 2, buf);

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

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

	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) {
		std::string info = "xsltproc ";
		for (i = 0; i < xml_params_count(params); i++)
			info += format_string_std("--stringparam %s %s ", xml_params_get_key(params, i), xml_params_get_value(params, i));
		info += "xslt/csv2xml.xslt";
		report_info("%s", info.c_str());
	}

	ret = parse_xml_buffer(filename, mem.data(), mem.size(), log, params);

	return ret;
}

int parse_manual_file(const char *filename, struct xml_params *params, struct divelog *log)
{
	std::string mem;
	time_t now;
	struct tm *timep;
	char curdate[9];
	char curtime[6];
	int ret;


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

	xml_params_add(params, "date", curdate);
	xml_params_add(params, "time", curtime);

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

	if (try_to_xslt_open_csv(filename, mem, "manualCSV"))
		return -1;

#ifndef SUBSURFACE_MOBILE
	if (verbose >= 2) {
		std::string info = format_string_std("(echo '<manualCSV>'; cat %s;echo '</manualCSV>') | xsltproc ", filename);
		for (int i = 0; i < xml_params_count(params); i++)
			info += format_string_std("--stringparam %s %s ", xml_params_get_key(params, i), xml_params_get_value(params, i));
		info += format_string_std("%s/xslt/manualcsv2xml.xslt -", SUBSURFACE_SOURCE);
		report_info("%s", info.c_str());
	}
#endif
	ret = parse_xml_buffer(filename, mem.data(), mem.size(), log, params);

	return ret;
}