

function transform (events, sequences, opts = {}) {

	const dgl = !!opts.projectId;
	const exportQC = opts.exportQC !== false;
	const exportGeneral = opts.exportGeneral !== false;
	const exportMissing = opts.exportMissing !== false;
	const missingAsEvent = opts.missingAsEvent;

	const output = {
		DglProjectId: opts.projectId,
		Sequences: [],
		DglCreatedOn: dgl && new Date()
	};

	// NOTE: Events come in descending chronological
	// order from the server.
	for (const event of events.reverse()) {
		const SequenceNumber = event.sequence;
		if (SequenceNumber) {
			const sequence = sequences.find(s => s.sequence == SequenceNumber);
			if (!sequence) {
				throw Error(`Sequence ${SequenceNumber} not found in sequence list`);
			}

			let SequenceObject = output.Sequences.find(s => s.SequenceNumber == SequenceNumber);
			if (!SequenceObject) {
				SequenceObject = {
					SequenceNumber,
					Entries: [],
					DglSailline: sequence.line,
					DglNumPoints: sequence.num_points,
					DglNumMissing: sequence.missing_shots,
					// NOTE: Distance & azimuth refer to raw data if the sequence
					// status is 'raw' and to final data if status is 'final'. In
					// the event of it being NTBP it depends on whether final data
					// exists or not.
					DglLength: sequence.length,
					DglAzimuth: sequence.azimuth,
					DglDuration: sequence.duration_final || sequence.duration,
					DglEquipmentInfo: sequence.equipment
				};
				[sequence.remarks, sequence.remarks_final].filter(i => !!i).forEach(i => {
					if (!SequenceObject.DglSequenceComments) {
						SequenceObject.DglSequenceComments = []
					}
					SequenceObject.DglSequenceComments.push(i);
				});

				output.Sequences.push(SequenceObject);
			}

			if (event.labels.includes("FSP")) {
				const entry = {
					"EntryType": "Start of line recording",
					"EntryTypeId": 3000,
					"Comment": event.remarks.toString(),
					"Heading": Number(sequence.azimuth.toFixed(2)),
					"IsReshoot": sequence.reshoot, // FIXME Add this property to sequence object
					"IsUndershoot": false,
					// NOTE https://gitlab.com/wgp/dougal/software/-/issues/12#note_419162674
					"LineNumber": sequence.line.toString(),
					"LineType": "Prime", // FIXME
					"ShotPointId": event.point,
					"Time": event.tstamp
				}

				SequenceObject.Entries.push(entry);
			}

			if (event.labels.includes("FGSP")) {
				const entry = {
					"EntryType": "Start good",
					"EntryTypeId": 3001,
					"Comment": event.remarks.toString(),
					"ShotPointId": event.point,
					"Time": event.tstamp
				}

				SequenceObject.Entries.push(entry);
			}

			if (event.labels.includes("FCSP")) {
				const entry = {
					"EntryType": "Start charged",
					"EntryTypeId": 3002,
					"Comment": event.remarks.toString(),
					"ShotPointId": event.point,
					"Time": event.tstamp
				}

				SequenceObject.Entries.push(entry);
			}

			if (event.labels.includes("LGFFSP")) {
				const entry = {
					"EntryType": "Last good Full Fold",
					"EntryTypeId": 3008,
					"Comment": event.remarks.toString(),
					"ShotPointId": event.point,
					"Time": event.tstamp
				}

				SequenceObject.Entries.push(entry);
			}

			if (event.labels.includes("LCFFSP")) {
				const entry = {
					"EntryType": "Last charged Full Fold",
					"EntryTypeId": 3009,
					"Comment": event.remarks.toString(),
					"ShotPointId": event.point,
					"Time": event.tstamp
				}

				SequenceObject.Entries.push(entry);
			}

			if (event.labels.includes("FDSP")) {
				const entry = {
					"EntryType": "Midnight",
					"EntryTypeId": 3003,
					"Comment": event.remarks.toString(),
					"ShotPointId": event.point,
					"Time": event.tstamp
				}

				SequenceObject.Entries.push(entry);
			}

			if (event.labels.includes("LCSP")) {
				const entry = {
					"EntryType": "End charged",
					"EntryTypeId": 3005,
					"Comment": event.remarks.toString(),
					"ShotPointId": event.point,
					"Time": event.tstamp
				}

				SequenceObject.Entries.push(entry);
			}

			if (event.labels.includes("LGSP")) {
				const entry = {
					"EntryType": "End good",
					"EntryTypeId": 3006,
					"Comment": event.remarks.toString(),
					"ShotPointId": event.point,
					"Time": event.tstamp
				}

				SequenceObject.Entries.push(entry);
			}

			if (event.labels.includes("LSP")) {
				const entry = {
					"EntryType": "End of line recording",
					"EntryTypeId": 3007,
					"Comment": event.remarks.toString(),
					"IsNTBP": sequence.status == "ntbp",
					// NOTE: Always pending, see
					// https://gitlab.com/wgp/dougal/software/-/issues/12
					"LineStatus": "Pending",
					"ShotPointId": event.point,
					"Time": event.tstamp
				}

				SequenceObject.Entries.push(entry);
			}

			// Dougal QC data

			if (exportQC) {
				if (event.labels.includes("QC")) {
					if (!event.labels.includes("QCAccepted")) {
						const entry = {
							"EntryType": "QC",
							"Comment": event.remarks.toString(),
							"ShotPointId": event.point,
							"Time": event.tstamp
						}
						SequenceObject.Entries.push(entry);
					}
				}
			}

			if (exportGeneral) {
				// These are labels that we have already (potentially) exported
				const excluded = [
					"FSP", "FGSP", "FCSP", "LGFFSP",
					"LCFFSP", "LCSP", "LGSP", "LSP",
					"LDSP", "FDSP", "QC", "QCAccepted",
					"Internal", "Private", "Confidential",
					"NoExport"
				];
				if (!event.labels || !event.labels.some( l => excluded.includes(l) )) {
					const entry = {
						"Comment": event.remarks.toString(),
						"ShotPointId": event.point,
						"Time": event.tstamp
					}
					SequenceObject.Entries.push(entry);
				}
			}

		}
	}

	for (const SequenceObject of output.Sequences) {
		const sequence = sequences.find(s => s.sequence == SequenceObject.SequenceNumber);

		// If no explicit FSP but there is a FGSP, clone it as FSP.
		if (!SequenceObject.Entries.find(i => i.EntryTypeId == 3000)) {
			const fgspIndex = SequenceObject.Entries.findIndex(i => i.EntryTypeId == 3001);
			if (fgspIndex != -1) {
				const fsp = Object.assign({}, SequenceObject.Entries[fgspIndex], {
					"EntryType": "Start of line recording",
					"EntryTypeId": 3000,
					"Heading": Number(sequence.azimuth.toFixed(2)),
					"IsReshoot": sequence.reshoot,
					"IsUndershoot": false,
					"LineNumber": sequence.line.toString(),
					"LineType": "Prime", // FIXME
				});
				SequenceObject.Entries.splice(fgspIndex, 0, fsp);
			} else {
				// We have neither an FSP nor an FGSP, let us take the first
				// preplot shot from the sequence data as save that as FSP
				// (we intentionally omit adding an FGSP as we know nothing
				// about the user's intent).
				const fsp = {
					"EntryType": "Start of line recording",
					"EntryTypeId": 3000,
					"Comment": sequence.fsp_final
						? "(First preplot shot found in P1)"
						: "(First shot found in P1)",
					"Heading": Number(sequence.azimuth.toFixed(2)),
					"IsReshoot": sequence.reshoot, // FIXME Add this property to sequence object
					"IsUndershoot": false,
					// NOTE https://gitlab.com/wgp/dougal/software/-/issues/12#note_419162674
					"LineNumber": sequence.line.toString(),
					"LineType": "Prime", // FIXME
					"ShotPointId": sequence.fsp_final || sequence.fsp,
					"Time": sequence.ts0_final || sequence.ts0
				}
				SequenceObject.Entries.unshift(fsp);
			}
		}

		// If no explicit LSP but there is a LGSP, clone it as LSP.
		if (!SequenceObject.Entries.find(i => i.EntryTypeId == 3007)) {
			const lgspIndex = SequenceObject.Entries.findIndex(i => i.EntryTypeId == 3006);
			if (lgspIndex != -1) {
				const lsp = Object.assign({}, SequenceObject.Entries[lgspIndex], {
					"EntryType": "End of line recording",
					"EntryTypeId": 3007,
					"IsNTBP": sequence.status == "ntbp",
					"LineStatus": "Pending",
				});
				SequenceObject.Entries.splice(lgspIndex+1, 0, lsp);
			} else {
				// See comment above concerning FSP
				const lsp = {
					"EntryType": "End of line recording",
					"EntryTypeId": 3007,
					"Comment": sequence.lsp_final
						? "(Last preplot shot found in P1)"
						: "(Last shot found in P1)",
					"IsNTBP": sequence.status == "ntbp",
					// NOTE: Always pending, see
					// https://gitlab.com/wgp/dougal/software/-/issues/12
					"LineStatus": "Pending",
					"ShotPointId": sequence.lsp_final || sequence.lsp,
					"Time": sequence.ts1_final || sequence.ts1
				}
				SequenceObject.Entries.push(lsp);
			}
		}

		// Set the missing shots object if not inhibited by the user.
		if (exportMissing) {
			// The user also needs to request missing shots from the sequences
			// endpoint; these are not returned by default.
			if ("missing_final" in sequence) {
				SequenceObject.MissingShots = sequence.missing_final.map(s => s.point).sort();

				// Add (pseudo-)events for missing shots
				if (missingAsEvent) {

					// Create the ‘dummy’ missing shot events
					const pseudoevents = SequenceObject.MissingShots.map( point => {
						return {
							ShotPointId: point,
							EntryType: "Missing shot"
						}
					});

					const isAscending = SequenceObject.Entries[0].ShotPointId <= SequenceObject.Entries[SequenceObject.Entries.length-1].ShotPointId;

					pseudoevents.forEach( pseudoevent => {
						for (const index in SequenceObject.Entries) {
							const ShotPointId = SequenceObject.Entries[index].ShotPointId;
							const slotFound = isAscending
								? ShotPointId > pseudoevent.ShotPointId
								: ShotPointId < pseudoevent.ShotPointId;

							if (slotFound) {
								SequenceObject.Entries.splice(index, 0, pseudoevent);
								break;
							}
						}
					});

				}
			}
		}
	}

	return output;
}

module.exports = transform;
