/**
 * Instantaneous characteristics of a horse in motion.
 */
export interface Instant {
	/** Timestamp of the instant. */
	t: number;
	/** Forward position along the course. */
	x: number;
	/** Actual speed. */
	v: number;
}

/**
 * Forward travel characteristics under the effects of acceleration.
 */
export interface Travel {
	start: Instant;
	end: Instant;
	/** Actual acceleration value applied between start and end. */
	a: number;
}

/**
 * Advance a horse according to its forward motion characteristics.
 * @param start Motion at the beginning of the segment
 * @param targetSpeed Target speed to accelerate to
 * @param totalAccel Total (positive) acceleration including skill bonuses
 * @param maxDur Max duration of the acceleration, or infinite if not given
 * @param maxDist Max distance of the acceleration, or infinite if not given
 * @param decel Negative acceleration value for cases where target speed is below start speed
 * @returns Travel segment after reaching the first of the given limits
 */
export function travelSegment(
	start: Instant,
	targetSpeed: number,
	totalAccel: number,
	maxDur?: number,
	maxDist?: number,
	decel?: number,
): Travel {
	const a = start.v < targetSpeed ? totalAccel : (decel ?? -1);
	const dvt = (targetSpeed - start.v) / a;
	const dxt = maxDist != null ? (-start.v + Math.sqrt(start.v * start.v + 4 * a * maxDist)) / (2 * a) : dvt;
	const time = maxDur != null ? Math.min(dvt, dxt, maxDur) : Math.min(dvt, dxt);
	// To address numerical imprecision, if accel time comes from velocity or distance,
	// set the reached values directly.
	const v = time === dvt ? targetSpeed : start.v + time * a;
	const dist = maxDist != null && time === dxt ? maxDist : (start.v + 0.5 * a * time) * time;
	const end = { t: start.t + time, x: start.x + dist, v };
	return { start, end, a };
}

/**
 * Acceleration bonus from skills.
 */
export interface Skill {
	/**
	 * Acceleration modifier in m/s².
	 */
	accel: number;
	/**
	 * Remaining actual duration in seconds.
	 */
	dur: number;
}

/**
 * Accelerate up to a new target speed while under the effects of acceleration skills.
 * @param startSpeed Speed at the start of the acceleration in m/s
 * @param targetSpeed Speed at which the acceleration ends in m/s
 * @param baseAccel Base acceleration value to which skills add, in m/s²
 * @param skills Skills that are active for the acceleration period
 * @returns Acceleration segments and remainders of skills upon reaching the target speed
 */
export function up(start: Instant, targetSpeed: number, baseAccel: number, skills: Skill[]) {
	const segments: Travel[] = [];
	const sk = skills.map((s) => ({ ...s })).filter((s) => s.dur > 0);
	while (start.v < targetSpeed) {
		const active = sk.filter((s) => s.dur > 0);
		if (active.length === 0) {
			segments.push(travelSegment(start, targetSpeed, baseAccel));
			break;
		}
		const boost = active.reduce((a, s) => a + s.accel, 0);
		const time = Math.min(...active.map((s) => s.dur));
		const seg = travelSegment(start, targetSpeed, baseAccel + boost, time);
		segments.push(seg);
		start = seg.end;
		for (const s of active) {
			s.dur -= seg.end.t - seg.start.t;
			if (s.dur < 1e-5) {
				s.dur = 0;
			}
		}
	}
	return { segments, skills: sk };
}
