
const NavHeaderError = require('./error');

/**
 * Detect if a Hydronav header is present in `buffer`.
 */
function detect (buffer) {
	const s = buffer.indexOf("@@@UHEAD");
	return s != -1 ? s : false;
};

function parse (buffer) {
	const s = buffer.indexOf("@@@UHEAD");

	if (s == -1) {
		return; // undefined
	}

	const parser = [
		// @@@UHEAD marker
		(buf, ctx) => {
			if (buf.subarray(s, s+8).toString('ascii') != "@@@UHEAD") {
				console.log(s, buf.subarray(s, s+8).toString('ascii'));
				throw new NavHeaderError("Expected @@@UHEAD marker not found at position "+s, buf);
			}
		},

		// Timestamp. We also need to pick the date from further down the message
		(buf, ctx) => {
			const t = buf.subarray(s+8, s+8+15).toString('ascii');
			const d = buf.subarray(s+105, s+105+10).toString('ascii');
			ctx.tstamp = new Date(d.replace(/(.{2})\/(.{2})\/(.{4})/, "$3-$2-$1") + t.replace(/(.{8}).(.*)/, "T$1.$2Z"));
			if (isNaN(ctx.tstamp)) {
				throw new NavHeaderError(`Invalid timestamp from '${d}' + '${t}'`, buf);
			}
		},

		// GPS clock, whatever that means
		(buf, ctx) => {
			ctx.gpsClock = Number(String.fromCharCode(buf[s+24]));
		},

		// Vessel name
		(buf, ctx) => {
			ctx.vesselName = buf.subarray(s+26, s+26+8).toString('ascii');
		},

		// Station number (aka shotpoint)
		// NOTE this waypoint will not be valid if mode is offline
		(buf, ctx) => {
			ctx.shotPoint = Number(buf.subarray(s+35, s+35+7).toString('ascii'))
		},

		// FSID
		// TODO Find out what this is!
		(buf, ctx) => {
			ctx.fsid = Number(buf.subarray(s+43, s+43+10).toString('ascii'))
		},

		// Northing
		// NOTE We have no clue which CRS
		(buf, ctx) => {
			ctx.northing = Number(buf.subarray(s+54, s+54+11).toString('ascii'))
			const hemisphere = String.fromCharCode(buf[s+65]);
			switch (hemisphere) {
				case "N":
				case "n":
					break;
				case "S":
				case "s":
					ctx.northing *= -1;
					break;
				default:
					throw new NavHeaderError("Unexpected hemisphere marker for northings: "+hemisphere, buf);
			}
		},

		// Easting
		// NOTE We have no clue which CRS
		(buf, ctx) => {
			ctx.easting = Number(buf.subarray(s+67, s+67+11).toString('ascii'))
			const hemisphere = String.fromCharCode(buf[s+78]);
			switch (hemisphere) {
				case "E":
				case "e":
					break;
				case "W":
				case "w":
					ctx.easting *= -1;
					break;
				default:
					throw new NavHeaderError("Unexpected hemisphere marker for eastings: "+hemisphere, buf);
			}
		},

		// Speed
		// (we convert to metres per second)
		(buf, ctx) => {
			ctx.speed = Number(buf.subarray(s+80, s+80+6).toString('ascii'));
			if (isNaN(ctx.speed)) {
				throw new NavHeaderError("Unparseable speed value: "+buf.subarray(s+80, s+80+6).toString('ascii'), buf);
			}
			const units = buf.subarray(s+86, s+86+2).toString('ascii');
			switch (units.toUpperCase()) {
				case "KT":
					ctx.speed *= (1.852/3.6);
					break;
				default:
					throw new NavHeaderError("Unexpected speed units: "+units+" for speed: "+ctx.speed, buf);
			}
		},

		// Bearing
		// (in degrees)
		(buf, ctx) => {
			ctx.bearing = Number(buf.subarray(s+89, s+89+6).toString('ascii'));
			const units = buf.subarray(s+95, s+95+2).toString('ascii');
			switch (units.toUpperCase()) {
				case "DG":
					break;
				default:
					throw new NavHeaderError("Unexpected angular units: "+units, buf);
			}
		},

		// Water depth
		// (in metres)
		(buf, ctx) => {
			ctx.waterDepth = Number(buf.subarray(s+98, s+98+5).toString('ascii'));
			const units = String.fromCharCode(buf[s+103]);
			switch (units.toUpperCase()) {
				case "M":
					break;
				default:
					throw new NavHeaderError("Unexpected depth units: "+units, buf);
			}
		},

		// Date
		// Already taken care of along with the time part

		// Distance offline
		// (meaning crossline?)
		(buf, ctx) => {
			ctx.crossline = Number(buf.subarray(s+116, s+116+5).toString('ascii'));
			const units = String.fromCharCode(buf[s+121]);
			switch (units.toUpperCase()) {
				case "M":
					break;
				default:
					// https://gitlab.com/wgp/dougal/software/-/issues/21
					// Triggerfish reports this even when offline and when
					// far from whatever it's using as reference, this field
					// will overflow. We assume this is the case and we do not
					// report an error.
					ctx.crossline = null;
// 					throw new NavHeaderError("Unexpected crossline units: "+units, buf);
			}
		},

		// Distance inline
		(buf, ctx) => {
			ctx.inline = Number(buf.subarray(s+123, s+123+8).toString('ascii'));
			const units = String.fromCharCode(buf[s+131]);
			switch (units.toUpperCase()) {
				case "M":
					break;
				default:
					// See previous comment.
					ctx.inline = null;
// 					throw new NavHeaderError("Unexpected inline units: "+units, buf);
			}
		},

		// Line status
		(buf, ctx) => {
			const status = String.fromCharCode(buf[s+133]);
			switch (status) {
				case "0":
					ctx.lineStatus = "offline";
					break;
				case "1":
					ctx.lineStatus = "approach";
					break;
				case "2":
					ctx.lineStatus = "online";
					break;
				case "3":
					ctx.lineStatus = "runout";
					break;
				default:
					throw new NavHeaderError("Unrecognised line status: "+status, buf);
			}
		},

		// GPS height
		(buf, ctx) => {
			ctx.gpsHeight = Number(buf.subarray(s+135, s+135+5).toString('ascii'));
			const units = String.fromCharCode(buf[s+140]);
			switch (units.toUpperCase()) {
				case "M":
					break;
				default:
					throw new NavHeaderError("Unexpected GPS height units: "+units, buf);
			}
		},

		// Line name
		(buf, ctx) => {
			ctx.lineName = buf.subarray(s+0x8e, s+0x8e+16).toString('ascii');
		},

		// End of Hydronav data
		(buf, ctx) => {
			if (String.fromCharCode(buf[s+0x9e]) != "*") {
				throw new NavHeaderError("Missing end of HYDRONAV data marker '*'", buf);
			}
		}
	];

	const hydronav = {};
	hydronav._type = "hydronav";
	hydronav._received = new Date();
	for (const fn of parser) {
		fn(buffer, hydronav);
	}

	return hydronav;
}


module.exports = {
	name: "Hydronav",
	detect,
	parse
};
