const d3a = require('d3');
const { encode } = require('@dougal/binary');

function bundle (json, opts = {}) {

	const { type, geometries, payload } = opts;

	const deltas = [];
	const values = [];


	if (type == 0) {
		/* Preplot information – sail line points
		 *
		 * elem 0: Float32Array Longitude
		 * elem 1: Float32Array Latitude
		 * elem 2: Uint8Array – Flags: 0x01 point ntba, 0x02 sailline ntba
		 */

		// Add preplot positions
		values.push({
			// longitude
			key: el => el.geometry.coordinates?.[0],
			type: Float32Array
		});

		values.push({
			// latitude
			key: el => el.geometry?.coordinates?.[1],
			type: Float32Array
		});

		values.push({
			// flags
			key: el => (el.sailline_ntba ? 0x02 : 0) | (el.ntba ? 0x01 : 0),
			type: Uint8Array
		});

		return encode.sequential(json, el => el.sailline, el => el.point, deltas, values, type)

	} else if (type == 1) {
		/* Preplot information – source line points
		 *
		 * elem 0: Float32Array Longitude
		 * elem 1: Float32Array Latitude
		 * elem 2: Uint8Array – Flags: 0x01 point ntba, 0x02 sailline ntba
		 * elem 3: Uint16Array Sailline
		 */

		// Add preplot positions
		values.push({
			// longitude
			key: el => el.geometry.coordinates?.[0],
			type: Float32Array
		});

		values.push({
			// latitude
			key: el => el.geometry?.coordinates?.[1],
			type: Float32Array
		});

		values.push({
			// flags
			key: el => (el.sailline_ntba ? 0x02 : 0) | (el.ntba ? 0x01 : 0),
			type: Uint8Array
		});

		values.push({
			// sailline
			key: el => el.sailline,
			type: Uint16Array
		});

		return encode.sequential(json, el => el.line, el => el.point, deltas, values, type)

	} else if (type == 2) {
		/* Raw positions and gun information:
		*
		* - Δelem    0: BigUint64Array + Int16Array – timestamps
		* - elem   0‒1: Float32Array, Float32Array – Raw positions (x, y)
		* - elem   2‒3: Int16Array, Int16Array – (×10) Raw position errors (i, j)
		* - elem   4‒6: Int8Array, Int8Array, Uint8Array – Gun deltas (μ, σ, R)
		* - elem   7‒9: Uint16Array, Uint8Array, Uint16Array – Gun pressures (μ, σ, R)
		* - elem 10‒12: Gun depths (μ, σ, R)
		* - elem 13‒15: Gun fill times (μ, σ, R)
		* - elem 16‒18: Gun delay (μ, σ, R)
		* - elem    19: No fire / autofire (in a single byte)
		*/

		// Add timestamps
		deltas.push({
			// Timestamp
			key: el => el.tstamp.valueOf(),
			baseType: BigUint64Array,
			incrType: Int16Array
		});

		// Add raw positions
		values.push({
			// longitude
			key: el => el.geometryraw?.coordinates?.[0] ?? el.geometry.preplot?.coordinates?.[0],
			type: Float32Array
		});
		values.push({
			// latitude
			key: el => el.geometryraw?.coordinates?.[1] ?? el.geometry.preplot?.coordinates?.[1],
			type: Float32Array
		});

		// Add raw inline, crossline errors
		values.push({
			// Crossline
			key: el => el.errorraw?.coordinates?.[0] * 100,
			type: Float32Array
		});
		values.push({
			// Inline
			key: el => el.errorraw?.coordinates?.[1] * 100,
			type: Float32Array
		});

		// Include delta errors info
		values.push({
			// Average
			key: el => (el.meta?.raw?.smsrc?.avg_delta ?? 0) * 10,
			type: Int8Array
		});
		values.push({
			// Standard deviation
			key: el => (el.meta?.raw?.smsrc?.std_delta ?? 0) * 10,
			type: Int8Array
		});
		values.push({
			// Spread
			key: el => (el.meta?.raw?.smsrc?.spread ?? 0), // Spread is already × 10
			type: Uint8Array
		});

		// Include pressure info
		const press = el => el.meta.raw?.smsrc?.guns?.map( i => i[11] ?? 0) ?? [];

		values.push({
			// Average
			key: el => d3a.mean(press(el)) ?? 0,
			type: Uint16Array
		});
		values.push({
			// Standard deviation
			key: el => d3a.deviation(press(el)) ?? 0,
			type: Uint8Array
		});
		values.push({
			// Spread
			key: el => (d3a.max(press(el)) ?? 0) - (d3a.min(press(el)) ?? 0),
			type: Uint16Array
		});

		// Include depth info (× 10)
		const depths = el => el.meta.raw?.smsrc?.guns?.map( i => i[10] ?? 0).filter(i => i) ?? [];

		values.push({
			// Average
			key: el => Math.min(d3a.mean(depths(el)) ?? 0, 25.5) * 10,
			type: Uint8Array
		});
		values.push({
			// Standard deviation
			key: el => (d3a.deviation(depths(el)) ?? 0) * 10,
			type: Uint8Array
		});
		values.push({
			// Spread
			key: el => ((d3a.max(depths(el)) ?? 0) - (d3a.min(depths(el)) ?? 0)) * 10,
			type: Uint8Array
		});

		// Include fill times
		const filltime = el => el.meta.raw?.smsrc?.guns?.map( i => i[13] ?? 0).filter(i => i) ?? [];

		values.push({
			// Average
			key: el => d3a.mean(filltime(el)) ?? 0,
			type: Uint16Array
		});
		values.push({
			// Standard deviation
			key: el => d3a.deviation(filltime(el)) ?? 0,
			type: Uint16Array
		});
		values.push({
			// Spread
			key: el => ((d3a.max(filltime(el)) ?? 0) - (d3a.min(filltime(el)) ?? 0)),
			type: Uint16Array
		});

		// Include fire delay
		const delay = el => el.meta.raw?.smsrc?.guns?.map( i => i[9] ?? 0).filter(i => i) ?? [];

		values.push({
			// Average
			key: el => (d3a.mean(delay(el)) ?? 0) * 10,
			type: Uint16Array
		});
		values.push({
			// Standard deviation
			key: el => (d3a.deviation(delay(el)) ?? 0) * 10,
			type: Uint16Array
		});
		values.push({
			// Spread
			key: el => ((d3a.max(delay(el)) ?? 0) - (d3a.min(delay(el)) ?? 0)) * 10,
			type: Uint16Array
		});

		// Include no fire and autofire (same byte)
		values.push({
			key: el => (el.meta.raw?.smsrc?.no_fire ?? 0) << 4 | (el.meta.raw?.smsrc?.num_auto ?? 0),
			type: Uint8Array
		});

		return encode.sequential(json, el => el.sequence, el => el.point, deltas, values, type)
	} else if (type == 3) {
		/* Final positions and raw vs final errors:
		*
		* - Δelem    0: BigUint64Array + Int16Array – timestamps
		* - elem   0‒1: Float32Array, Float32Array – Final positions (x, y)
		* - elem   2‒3: Int16Array, Int16Array – (×10) Final - raw position errors (i, j)
		*/

		// Add timestamps
		deltas.push({
			// Timestamp
			key: el => el.tstamp.valueOf(),
			baseType: BigUint64Array,
			incrType: Int16Array
		});

		// Add raw positions
		values.push({
			// longitude
			key: el => el.geometryfinal?.coordinates?.[0],
			type: Float32Array
		});
		values.push({
			// latitude
			key: el => el.geometryfinal?.coordinates?.[1],
			type: Float32Array
		});

		// Add raw inline, crossline errors relative to preplot
		values.push({
			// Crossline
			key: el => el.errorfinal?.coordinates?.[0] * 100,
			type: Int16Array
		});
		values.push({
			// Inline
			key: el => el.errorfinal?.coordinates?.[1] * 100,
			type: Int16Array
		});

		// Add raw inline, crossline errors relative to raw
		values.push({
			// Crossline
			key: el => (el.errorfinal?.coordinates?.[0] - el.errorraw?.coordinates?.[0]) * 100,
			type: Int16Array
		});
		values.push({
			// Inline
			key: el => (el.errorfinal?.coordinates?.[1] - el.errorraw?.coordinates?.[1]) * 100,
			type: Int16Array
		});

		return encode.sequential(json, el => el.sequence, el => el.point, deltas, values, type)
	} else if (type == 0xa) {
		/* 4D comparison data:
		 *
		 * elem0: i differences
		 * elem1: j differences
		 *
		 * Note that line/point may not be unique.
		 *
		 */

		/*
		deltas.push({
			key: el => el.baseTStamp,
			baseType: BigUint64Array,
			incrType: Int32Array
		});

		deltas.push({
			key: el => el.monTStamp,
			baseType: BigUint64Array,
			incrType: Int32Array
		})
		*/

		values.push({
			key: el => el[2],
			type: Float32Array
		});

		values.push({
			key: el => el[3],
			type: Float32Array
		});

		/*
		values.push({
			key: el => el.baseSeq,
			type: Uint16Array
		});

		values.push({
			key: el => el.monSeq,
			type: Uint16Array
		});
		*/

		return encode.sequential(json, el => el[0], el => el[1], deltas, values, type)
	} else if (type == 0xc) {
		/* 4D comparison data (reduced sample)
		 *
		 * Input is comparison records, i.e.:
		 * [ [ line, point, δi, δj ], … ]
		 *
		 * elem0: line
		 * elem1: point
		 * elem2: δi
		 * elem3: δj
		 *
		 * Note that the chunk's `i` and `j` values are not used
		 */

		values.push({
			key: el => el[0],
			type: Uint16Array
		});

		values.push({
			key: el => el[1],
			type: Uint16Array
		});

		values.push({
			key: el => el[2],
			type: Float32Array
		});

		values.push({
			key: el => el[3],
			type: Float32Array
		});

		return encode.sequential(json, el => 0, el => 0, deltas, values, type)
	}
}

module.exports = { bundle };
