const YAML = require('yaml');
const fs = require('fs').promises;
const path = require('path');

const alert = require("../../../alerts");
const configuration = require('../../configuration');

let lineNameProperties;

async function getDistance (client, payload) {
	const text = `
		SELECT ST_Distance(pp0.geometry, pp1.geometry) distance
		FROM preplot_points pp0,
		preplot_points pp1
		WHERE
			pp0.line = $1 AND pp1.line = $1
			AND pp0.class = $4 AND pp1.class = $4
			AND pp0.point = $2 AND pp1.point = $3;
	`;

	const p = payload;
	const res = await client.query(text, [p.line, p.fsp, p.lsp, p.class || "V"]);
	if (res.rows.length) {
		return res.rows[0].distance;
	} // else undefined
}

async function getSequence (client) {
	// Get the next free sequence from planned data
	// if there is any planned lines, if not get the
	// next available from raw lines
	const text = `
		WITH p AS (
			SELECT max(sequence) AS sequence
			FROM planned_lines
		),
		r AS (
			SELECT max(sequence) AS sequence
			FROM raw_lines
		)
		SELECT COALESCE(p.sequence, r.sequence)+1 AS sequence
		FROM p, r;
	`;

	const res = await client.query(text);
	// If no sequences have been found, assume none have been shot
	// and return 1
	return (res.rows[0] && res.rows[0].sequence) || 1;
}

async function getTimestamps (client, projectId, payload) {

	const defaultLineChangeDuration = (await configuration.get(projectId, "planner/defaultLineChangeDuration") || 30) * 60*1000; // minutes to milliseconds

	const defaultAcquisitionSpeed = (await configuration.get(projectId, "planner/defaultAcquisitionSpeed") || 4.8) * 1.852 / 3.6; // Knots to m/s

	const distance = await getDistance(client, payload);

	const text = `
		SELECT * FROM planned_lines;
	`;

	const res = await client.query(text);

	const ts0 = new Date(
		(res.rows.length
			? res.rows.map(r => r.ts1).reduce( (a, b) => Math.max(a, b) )
			: Date.now()
		) + defaultLineChangeDuration
	);

	const ts1 = new Date(ts0.valueOf() + (distance / defaultAcquisitionSpeed)*1000);

	return {ts0, ts1};
}

async function getSequencesForLine (client, line) {
	const text  = `
		SELECT * from sequences_summary WHERE line = $1 ORDER BY sequence;
	`;

	const res = await client.query(text, [line]);
	return res.rows;
}

async function getPlanned (client) {
	const text = `
		SELECT * FROM planned_lines ORDER BY sequence;
	`;

	const res = await client.query(text);
	return res.rows;
}


async function getLineName (client, projectId, payload) {

	// This is to monitor #165
	// https://gitlab.com/wgp/dougal/software/-/issues/incident/165
	if (!payload?.line) {
		alert({function: "getLineName", client, projectId, payload});
	}

	const lineNameBuilder = await configuration.get(projectId, "online/line/lineNameBuilder");
	const fields = lineNameBuilder?.fields;

	if (fields) {
		const properties = await getLineNameProperties();
		const values = await getLineNameValues(client, projectId, payload, lineNameBuilder?.values);
		return buildLineName(properties, fields, values, payload?.name);
	} else {
		// TODO send a user notification via WS to let them know
		// they haven't configured the line name parameters
		throw { status: 400, message: `Line name format parameters missing or invalid. Please check ‘Line name format’ in this project's [configuration settings](./configuration)` };
	}

	// return undefined
}

/** Get line properties that go into making a line name.
 *
 * The properties are defined in a separate YAML file for
 * convenience.
 */
async function getLineNameProperties () {
	if (!lineNameProperties) {
		const buffer = await fs.readFile(path.join(__dirname, 'linename-properties.yaml'));
		lineNameProperties = YAML.parse(buffer.toString());
	}

	return lineNameProperties;
}

async function getLineNameValues (client, projectId, payload, otherValues = {}) {
	const planned = await getPlanned(client);
	const previous = await getSequencesForLine(client, payload.line);
	const attempt = planned.filter(r => r.line == payload.line).concat(previous).length;
	const p = payload;
	const incr = p.lsp > p.fsp;
	const sequence = p.sequence || 1;
	const line = p.line;

	return {
		...structuredClone(otherValues),
		line_number: payload.line,
		sequence_number: payload.sequence || 1,
		original_sequence: payload.meta?.original_sequence,
		pass_number: attempt,
		is_prime: attempt == 0,
		is_reshoot: payload.meta?.is_reshoot ?? (!payload.meta?.is_infill && attempt > 0),
		is_infill: payload.meta?.is_infill ?? false,
		direction: null, // TODO
		is_incrementing: incr
	};
}

/** Compute the string representation of a line name field
 */
function fieldValue (properties, field, values) {
	let value;

	if (field.item == "text") {
		value = field.value;
	} else if (properties[field.item]?.type == "boolean") {
		if (values[field.item] === field.when) {
			value = field.value;
		}
	} else {
		value = values[field.item];
	}

	if (value != null) {

		if (properties[field.item]?.type == "number") {
			if (field.scale_multiplier != null) {
				value *= field.scale_multiplier;
			}
			if (field.scale_offset != null) {
				value += field.scale_offset;
			}

			if (field.format == "integer") {
				value = Math.round(value);
			}
		}

		value = String(value);
		if (field.pad_side == "left") {
			value = value.padStart(field.length, field.pad_string ?? " ");
		} else if (field.pad_side == "right") {
			value = value.padEnd(field.length, field.pad_string ?? " ");
		}

		return value;
	}
}


/** Build a line name out of its component properties, fields and values.
 *
 * NOTE: This is the same function as available client-side on
 * `fixed-string-encoder.vue`. Consider merging them.
 */
function buildLineName (properties, fields, values, str = "") {
	const length = fields.reduce( (acc, cur) => (cur.offset + cur.length) > acc ? (cur.offset + cur.length) : acc, str.length )
	str = str.padEnd(length);
	for (const field of fields) {
		const value = fieldValue(properties, field, values);
		if (value != null) {
			str = str.slice(0, field.offset) + value + str.slice(field.offset + field.length);
		}
	}
	return str;
}

module.exports = {
	getDistance,
	getSequence,
	getTimestamps,
	getSequencesForLine,
	getPlanned,
	getLineNameProperties,
	getLineNameValues,
	getLineName,
	buildLineName
};
