// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
mod sampler;
mod resampler;
mod waves;
mod chirp;

mod shot;
mod hit;
mod kill;
mod block_bullet;
mod ammo_crate;
mod speed;
mod reload;
mod regen;
mod damage;
mod forcefield;
mod teleportation;
mod thrust;
mod block_player;
mod game_start;
mod game_end;
mod game_win;
mod flag;
mod checkpoint;

use std::fs;

use hound::{WavWriter, WavSpec, SampleFormat};

use crate::task::Task;
use crate::utils;

static OUTPUT_DIR: &str = "../data/sounds";
const SAMPLE_RATE: u32 = 48000;
const DELTA_TIME: f32 = 1.0 / SAMPLE_RATE as f32;

fn create_dir() {
	// Prevents erroring when already created
	let _ = fs::create_dir(OUTPUT_DIR);
}

struct Sound<F> where F: FnMut(f32) -> f32 {
	name: &'static str,
	time: f32,
	amplitude: f32,
	on_sample: F,
	loopable: bool,
}

impl<F> Sound<F> where F: FnMut(f32) -> f32 {
	fn loopable(mut self) -> Sound<F> {
		self.loopable = true;
		self
	}
}

// Hacky but it works
impl<F> Drop for Sound<F> where F: FnMut(f32) -> f32 {
	fn drop(&mut self) {
		assert!(self.amplitude.abs() <= 1.0);

		let path = format!("{OUTPUT_DIR}/{}.wav", self.name);

		if utils::exists(&path) { return; }

		let sample_count = (SAMPLE_RATE as f32 * self.time) as usize;
		let mut samples = Vec::with_capacity(sample_count);
		for i in 0..sample_count {
			let t = i as f32 / SAMPLE_RATE as f32;
			let sample = (self.on_sample)(t);
			samples.push(sample);
		}

		if self.loopable {
			/*
			 * Want to add a linear function f(t) = mt + c such that the sample
			 * that comes after the final sample equals the first sample.
			 */
			let dt = sample_count as f32 / SAMPLE_RATE as f32;
			let next = (self.on_sample)(dt);

			let da = next - samples[0];
			let m = -da / dt;
			let c = da / 2.0;

			for (i, sample) in samples.iter_mut().enumerate() {
				let t = i as f32 / SAMPLE_RATE as f32;
				*sample += m * t + c;
			}
		}

		let max = samples.iter().copied().map(f32::abs).max_by(f32::total_cmp).unwrap();

		if max > 0.0 {
			for sample in &mut samples {
				*sample *= self.amplitude / max;
			}
		}

		// Unwanted delay
		let start_zero_count = samples.iter().take_while(|&&x| x == 0.0).count();
		assert!((start_zero_count as f32 / SAMPLE_RATE as f32) < 0.001, "Starting silence is too long");

		// Unnecessary file size
		let end_zero_count = samples.iter().rev().take_while(|&&x| x == 0.0).count();
		assert!((end_zero_count as f32 / SAMPLE_RATE as f32) < 0.001, "Ending silence is too long");

		let mut writer = WavWriter::create(&path, WavSpec {
			channels: 1,
			sample_rate: SAMPLE_RATE,
			bits_per_sample: 16,
			sample_format: SampleFormat::Int,
		}).unwrap();
		let mut writer = writer.get_i16_writer(sample_count.try_into().unwrap());

		for sample in samples {
			assert!((-1.0..=1.0).contains(&sample), "Sample after normalisation out of range, got {sample}");
			writer.write_sample((sample * 32767.5).floor() as i16);
		}

		writer.flush().unwrap();
	}
}

/**
 * Convenience method for generating a sound effect.
 *
 * `name` is the sound's name, `time` is how long the sound effect lasts (in
 * seconds) and `on_sample` is a function that takes as input the current
 * position of time and returns as output the sample.
 *
 * It is guaranteed that `on_sample` is called sequentially on all samples in
 * the output.
 */
fn generate<F>(name: &'static str, time: f32, amplitude: f32, on_sample: F) -> Sound<F>
where
	F: FnMut(f32) -> f32,
{
	Sound { name, time, amplitude, on_sample, loopable: false }
}

pub fn get_tasks(tasks: &mut Vec<Task>) {
	tasks.extend_from_slice(&[
		Task::new("Creating sound directory", create_dir),
		Task::new("Generating shot sound", shot::generate),
		Task::new("Generating hit sound", hit::generate),
		Task::new("Generating kill sound", kill::generate),
		Task::new("Generating block bullet sound", block_bullet::generate),
		Task::new("Generating ammo crate sound", ammo_crate::generate),
		Task::new("Generating speed sound", speed::generate),
		Task::new("Generating reload sound", reload::generate),
		Task::new("Generating regeneration sound", regen::generate),
		Task::new("Generating damage sound", damage::generate),
		Task::new("Generating forcefield sound", forcefield::generate),
		Task::new("Generating teleportation sound", teleportation::generate),
		Task::new("Generating thrust sound", thrust::generate),
		Task::new("Generating block player sound", block_player::generate),
		Task::new("Generating game start sound", game_start::generate),
		Task::new("Generating game end sound", game_end::generate),
		Task::new("Generating game win sound", game_win::generate),
		Task::new("Generating flag capture sound", flag::generate_capture),
		Task::new("Generating flag drop sound", flag::generate_drop),
		Task::new("Generating checkpoint sound", checkpoint::generate),
	]);
}
