const { setSurvey, transaction } = require('../connection');
const { getLineName } = require('./lib');

async function patch (projectId, sequence, payload, opts = {}) {
	const client = await setSurvey(projectId);

	sequence = Number(sequence);

	/*
	 * Takes a Date object and returns the epoch
	 * in seconds
	 */
	function epoch (ts) {
		return Number(ts)/1000;
	}

	/*
	 * Shift sequence ts0, ts1 by dt0, dt1 respectively
	 * for only one sequence
	 */
	async function shiftSequence (sequence, dt0, dt1) {
		const text = `
			UPDATE planned_lines
			SET ts0 = ts0 + make_interval(secs => $2), ts1 = ts1 + make_interval(secs => $3)
			WHERE sequence = $1
		`;

		return await client.query(text, [sequence, dt0, dt1]);
	}

	/*
	 * Shift sequence ts0, ts1 by dt0, dt1 respectively
	 * for all sequences >= sequence
	 */
	async function shiftSequences (sequence, dt0, dt1) {
		const text = `
			UPDATE planned_lines
			SET ts0 = ts0 + make_interval(secs => $2), ts1 = ts1 + make_interval(secs => $3)
			WHERE sequence >= $1
		`;

		return await client.query(text, [sequence, dt0, dt1]);
	}

	try {
		transaction.begin(client);

		let deltatime;

		const r0 = await client.query("SELECT * FROM planned_lines_summary WHERE sequence >= $1 ORDER BY sequence ASC LIMIT 2;", [sequence]);
		const seq = (r0?.rows || [])[0];
		if (!seq || seq?.sequence != sequence) {
			throw {status: 400, message: `Sequence ${sequence} does not exist`};
		}
		const seq1 = r0.rows[1];
		const speed = seq.length/(epoch(seq.ts1)-epoch(seq.ts0)); // m/s

		if ("ts0" in payload || "ts1" in payload) {
			/*
			 * Change in start or end times
			 */

			deltatime = "ts0" in payload
				? (epoch(new Date(payload.ts0)) - epoch(seq.ts0))
				: (epoch(new Date(payload.ts1)) - epoch(seq.ts1));
			// Now shift all sequences >= this one by deltatime
			await shiftSequences(sequence, deltatime, deltatime);

		} else if ("speed" in payload) {
			/*
			 * Change in acquisition speed (m/s)
			 */

			// Check that speed is sensible
			if (payload.speed < 0.1) {
				throw {status: 400, message: "Speed must be at least 0.1 m/s"};
			}

			deltatime = epoch(seq.ts0) + (seq.length/payload.speed) - epoch(seq.ts1);
			// Fix seq.ts0, shift set.ts1 += deltatime, plus all sequences > this one
			await shiftSequence(sequence, 0, deltatime);
			await shiftSequences(sequence+1, deltatime, deltatime);

		} else if ("fsp" in payload) {
			/*
			 * Change of FSP
			 */

			// Keep ts1, adjust fsp and ts0 according to speed
			// ts0' = (shot_distance * delta_shots / speed) + ts0
			const sign = Math.sign(seq.lsp-seq.fsp);
			const ts0 = (sign * (seq.length/seq.num_points) * (payload.fsp-seq.fsp) / speed) + epoch(seq.ts0);
			const text = `
				UPDATE planned_lines
				SET fsp = $2, ts0 = $3
				WHERE sequence = $1;
			`;
			await client.query(text, [sequence, payload.fsp, new Date(ts0*1000)]);

		} else if ("lsp" in payload) {
			/*
			 * Change of LSP
			 */

			// Keep ts0, adjust lsp and ts1 according to speed
			// Calculate deltatime from ts1'-ts1
			// Shift all sequences > this one by deltatime

			// deltatime = (shot_distance * delta_shots / speed)
			// ts1' = deltatime + ts1
			const sign = Math.sign(seq.lsp-seq.fsp);
			deltatime = (sign * (seq.length/seq.num_points) * (payload.lsp-seq.lsp) / speed);
			const ts1 = deltatime + epoch(seq.ts1);
			const text = `
				UPDATE planned_lines
				SET lsp = $2, ts1 = $3
				WHERE sequence = $1;
			`;
			await client.query(text, [sequence, payload.lsp, new Date(ts1*1000)]);
			shiftSequences(sequence+1, deltatime, deltatime);

		} else if ("lagAfter" in payload && seq1) {
			/*
			 * Change of line change time
			 */

			// Check that the value is sensible
			if (payload.lagAfter < 0) {
				throw {status: 400, message: "Line change time cannot be negative"};
			}

			// Calculate deltatime from next sequence's ts0'-ts0
			// Shift all sequences > this one by deltatime
			const ts0 = epoch(seq.ts1) + payload.lagAfter; // lagAfter is in seconds
			deltatime = ts0 - epoch(seq1.ts0);
			shiftSequences(sequence+1, deltatime, deltatime);

		} else if ("sequence" in payload) {
			/*
			 * Renumbering / reshuffling of sequences
			 */

			// NOTE: This does not enforce consecutive sequences, because sometimes
			// there is a need for those (don't ask).

			// Renumber or reorder sequences
			const r1 = await client.query("SELECT sequence FROM planned_lines ORDER BY sequence;");
			const sequences = (r1?.rows||[]).map(i => i.sequence);
			const index = sequences.indexOf(payload.sequence);
			if (index != -1) {
				// Make space by shifting all sequence numbers >= payload.sequence by 1
				const text = `
					UPDATE planned_lines
					SET sequence = sequence + 1
					WHERE sequence >= $1;
				`;
				await client.query("SET CONSTRAINTS planned_lines_pkey DEFERRED;");
				await client.query(text, [payload.sequence]);

				// And now we need to rename all affected lines
				const r2 = await client.query("SELECT * FROM planned_lines WHERE sequence > $1 ORDER BY sequence;", [payload.sequence]);
				for (let row in r2.rows) {
					const name = await getLineName(client, projectId, row);
					await client.query("UPDATE planned_lines SET name = $2 WHERE sequence = $1", [row.sequence, name]);
				}

			}
			// Now update just this sequence
			const text = `
				UPDATE planned_lines
				SET sequence = $2
				WHERE sequence = $1;
			`;
			await client.query(text, [sequence, payload.sequence]);

			// And rename
			const r3 = await client.query("SELECT * FROM planned_lines WHERE sequence = $1 ORDER BY sequence;", [payload.sequence]);
			const name = await getLineName(client, projectId, r3.rows[0]);
			await client.query("UPDATE planned_lines SET name = $2 WHERE sequence = $1", [payload.sequence, name]);


		} else if (["name", "remarks", "meta"].some(i => i in payload)) {
			/*
			 * Change in various other attributes that do not affect
			 * other sequences
			 */

			// NOTE Magic! If name is empty, we generate one.
			// Can be used for going back to a default name after it's been
			// changed manually.
			if (payload.name === "") {
				payload.name = await getLineName(client, projectId, r0.rows[0]);
			}

			// Change the relevant attribute
			const text = `
				UPDATE planned_lines
				SET
					name = COALESCE($2, name),
					remarks = COALESCE($3, remarks),
					meta = COALESCE($4, meta)
				WHERE sequence = $1;
			`;
			await client.query(text, [sequence, payload.name, payload.remarks, payload.meta]);

		} else {
			throw { status: 400, message: "Bad request"};
		}

		transaction.commit(client);
	} catch (err) {
		transaction.rollback(client);

		if (err.code == 23503) {
			if (err.constraint == "planned_lines_line_fsp_class_fkey" || err.constraint == "planned_lines_line_lsp_class_fkey") {
				throw {status: 400, message: "Attempt to shoot a non-existent shotpoint"};
			}
		} else {
			throw err;
		}
	} finally {
		client.release();
	}
}

module.exports = patch;
