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

pub use bots::BotConfig;

use std::{num::NonZeroU32, collections::BTreeMap, f32::consts::SQRT_2};

use glam::Vec2;
use serde::Deserialize;

use bots::{Id, Bot, BotConfigLayers};

use super::super::common::config::{AmmoCountIo, Effects, blocks::BlockConfig};

use crate::world::{MAX_AMMO_CRATES, MAX_POWERUPS, config::WorldConfig, powerup::PowerupType, special_area::SpecialArea, colour::ColourAlpha};
use crate::protocol::message::{StoryText, text_io::TextIoString};
use crate::utils::maths::RectCorners;

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
	pub lives: NonZeroU32,
	pub ammo: AmmoConfig,
	pub world: WorldConfig,
	pub blocks: BlockConfig,
	bot_configs: BTreeMap<Id, BotConfigLayers>,
	bots: Box<[Bot<Id>]>,
	#[serde(default)] pub effects: Effects,
	#[serde(default)] pub powerups: PowerupConfig,
	#[serde(default)] pub spawnpoint: Vec2,
	#[serde(default)] pub checkpoints: Box<[Vec2]>,
	#[serde(default)] pub flag: Option<Flag>,
	#[serde(default)] pub story_text: Vec<StoryText>,
	#[serde(default)] pub arrows: Vec<Arrow>,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AmmoConfig {
	pub init: AmmoCountIo,
	pub crate_supply: u32,
	#[serde(default)] pub seed: u64,
	#[serde(default)] pub crate_spawn_ranges: Vec<RectCorners>,
	#[serde(default = "crate_spawn_period_default")] pub crate_spawn_period: f32,
	#[serde(default)] pub crate_init_spawn: Vec<Vec2>,
	#[serde(default = "crate_limit_default")] pub crate_limit: usize,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PowerupConfig {
	#[serde(default)] pub seed: u64,
	#[serde(default)] pub init_spawn: Vec<(Vec2, PowerupType)>,
	#[serde(default = "powerup_spawn_period_default")] pub spawn_period: f32,
	#[serde(default = "powerup_limit_default")] pub limit: usize,
}

impl Default for PowerupConfig {
	fn default() -> PowerupConfig {
		PowerupConfig {
			seed: 0,
			init_spawn: Vec::new(),
			spawn_period: powerup_spawn_period_default(),
			limit: powerup_limit_default(),
		}
	}
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Flag {
	pub pos: Vec2,
	pub colour: ColourAlpha,
	pub pickup_message: TextIoString,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Arrow {
	pos: Vec2,
	angle: f32,
}

fn crate_spawn_period_default() -> f32 { 30.0 }
fn crate_limit_default() -> usize { MAX_AMMO_CRATES }
fn powerup_spawn_period_default() -> f32 { f32::INFINITY }
fn powerup_limit_default() -> usize { MAX_POWERUPS }

impl Config {
	pub fn validate(&self) -> Result<(), String> {
		if self.ammo.crate_limit > MAX_AMMO_CRATES {
			return Err(format!("ammo crate limit is too large, expected at most {MAX_AMMO_CRATES}, got {}", self.ammo.crate_limit));
		}

		if self.ammo.crate_init_spawn.len() > MAX_AMMO_CRATES {
			return Err(format!("too many initially spawned ammo crates, expected at most {MAX_AMMO_CRATES}, got {}", self.ammo.crate_init_spawn.len()));
		}

		if self.powerups.limit > MAX_POWERUPS {
			return Err(format!("powerup limit is too large, expected at most {MAX_POWERUPS}, got {}", self.powerups.limit));
		}

		if self.powerups.init_spawn.len() > MAX_POWERUPS {
			return Err(format!("too many initially spawned powerups, expected at most {MAX_POWERUPS}, got {}", self.powerups.init_spawn.len()));
		}

		Ok(())
	}

	pub fn get_special_areas(&self) -> Box<[SpecialArea]> {
		const SIZE: f32 = 0.1875;
		const WIDTH: f32 = 4.0;
		const HEIGHT: f32 = 1.5;

		static VERTICES: [Vec2; 9] = [
			Vec2::new(0.0, -SIZE / 2.0),
			Vec2::new(0.0, SIZE / 2.0),
			Vec2::new(WIDTH, 0.0),
			Vec2::new(WIDTH - HEIGHT / 2.0, -HEIGHT / 2.0),
			Vec2::new(WIDTH - HEIGHT / 2.0 - SIZE / SQRT_2, -HEIGHT / 2.0 + SIZE / SQRT_2),
			Vec2::new(WIDTH - SIZE * (SQRT_2 + 0.5), -SIZE / 2.0),
			Vec2::new(WIDTH - HEIGHT / 2.0, HEIGHT / 2.0),
			Vec2::new(WIDTH - HEIGHT / 2.0 - SIZE / SQRT_2, HEIGHT / 2.0 - SIZE / SQRT_2),
			Vec2::new(WIDTH - SIZE * (SQRT_2 + 0.5), SIZE / 2.0),
		];

		const AREAS_PER_ARROW: usize = 7;

		static INDICES: [[usize; 3]; AREAS_PER_ARROW] = [
			[0, 1, 5],
			[1, 5, 8],
			[3, 4, 5],
			[2, 3, 5],
			[2, 5, 8],
			[2, 6, 8],
			[6, 7, 8],
		];

		let mut areas = Vec::with_capacity(self.arrows.len() * AREAS_PER_ARROW);
		let colour = ColourAlpha::from([255, 255, 255, 127]);
		for arrow in &self.arrows {
			let (sin, cos) = arrow.angle.to_radians().sin_cos();
			let get = |i| {
				let pos: Vec2 = VERTICES[i];
				Vec2::new(pos.x * cos - pos.y * sin, pos.x * sin + pos.y * cos) + arrow.pos
			};
			for &[i0, i1, i2] in &INDICES {
				areas.push(SpecialArea::new(get(i0), get(i1), get(i2), colour));
			}
		}
		areas.into_boxed_slice()
	}
}
