use std::collections::HashMap;

use anyhow::Result;

use ecs::*;
use engine::prelude::{
    cgmath::{Vector3, Vector4},
    *,
};

#[derive(Clone, Copy, Debug)]
struct PlayerEntity(Entity);

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum Input {
    Axis(u8),
    Button(Button),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum Button {
    One,
}

#[derive(Clone, Copy, Debug)]
enum Control {
    Throttle,
    StrafeHorizontal,
    StrafeVertical,
    Yaw,
    Pitch,
    Roll,

    PrimaryWeapon,
    SecondaryWeapon,
}

#[derive(Clone, Debug)]
struct InputSettings {
    mappings: HashMap<(String, Input), Control>,
}

impl Default for InputSettings {
    fn default() -> Self {
        Self {
            mappings: [
                (
                    ("Joystick 1".to_string(), Input::Axis(0)),
                    Control::Throttle,
                ),
                (
                    ("Joystick 1".to_string(), Input::Axis(1)),
                    Control::StrafeHorizontal,
                ),
                (
                    ("Joystick 1".to_string(), Input::Axis(2)),
                    Control::StrafeVertical,
                ),
                (("Joystick 1".to_string(), Input::Axis(3)), Control::Pitch),
                (
                    ("Joystick 1".to_string(), Input::Button(Button::One)),
                    Control::PrimaryWeapon,
                ),
                (("Joystick 2".to_string(), Input::Axis(1)), Control::Yaw),
                (("Joystick 2".to_string(), Input::Axis(0)), Control::Roll),
                (
                    ("Joystick 2".to_string(), Input::Button(Button::One)),
                    Control::SecondaryWeapon,
                ),
            ]
            .into_iter()
            .collect(),
        }
    }
}

impl InputSettings {
    pub fn map_axis(&self, device_name: impl ToString, axis: u8) -> Option<Control> {
        self.mappings
            .get(&(device_name.to_string(), Input::Axis(axis)))
            .map(|control| *control)
    }
}

pub struct Game;

impl Game {
    pub fn update(&mut self, _world: &mut World) -> Result<()> {
        Ok(())
    }

    pub fn event(&mut self, world: &mut World, event: EngineEvent<'_>) -> Result<()> {
        let player = world.resources.get::<PlayerEntity>();
        let (fighter_object, resources) = world.entity_resources(player.0)?;

        match event {
            EngineEvent::JoystickAxis(joystick, axis_index, value) => {
                let normalized = value as f32 * (i16::MAX as f32).recip();
                let input_settings = resources.get::<InputSettings>();
                let player_control = fighter_object.get_component_mut::<FreeSpaceControl>()?;

                if let Some(control) = input_settings.map_axis(joystick.name(), axis_index) {
                    match control {
                        Control::Throttle => player_control.set_throttle(normalized),
                        Control::StrafeHorizontal => {
                            player_control.set_left_right_strafe(normalized)
                        }
                        Control::StrafeVertical => player_control.set_up_down_strafe(normalized),
                        Control::Yaw => player_control.set_yaw(normalized),
                        Control::Pitch => player_control.set_pitch(normalized),
                        Control::Roll => player_control.set_roll(normalized),

                        Control::PrimaryWeapon => (),
                        Control::SecondaryWeapon => (),
                    }
                }
            }

            _ => (),
        }

        Ok(())
    }
}

impl Game {
    pub fn setup_updates(world_builder: &mut WorldBuilder) -> Result<()> {
        world_builder.add_update(
            "player_rotation",
            200,
            Self::player_orientation,
            EmptyFilter,
        )?;

        world_builder.add_update("camera_position", 1_000, Self::camera_update, EmptyFilter)?;

        Ok(())
    }

    pub fn setup_scene(world: &mut World) -> Result<()> {
        let mut fighter = AssetHandler::create(world).create_entity("fighter")?;
        fighter.insert_component(FreeSpaceControl::new(FreeSpaceControlSettings::default()));

        let player = PlayerEntity(world.add_entity(fighter)?);
        world.resources.insert(player);
        world.resources.insert(InputSettings::default());

        world.commit_entity_changes()
    }
}

// updates
impl Game {
    fn player_orientation(
        world: &mut World,
        _entity: Entity,
        draw: &mut Draw,
        control: &mut FreeSpaceControl,
    ) -> Result<()> {
        control.update(world.now());
        draw.set_transform(control.transform())
    }

    fn camera_update(
        world: &mut World,
        _entity: Entity,
        control: &mut FreeSpaceControl,
    ) -> Result<()> {
        let scene = world.resources.get_mut::<Scene>();
        let view = scene.view_mut();

        view.camera_mut()
            .set_center((control.translation() * Vector4::unit_w()).truncate());
        view.camera_mut()
            .set_eye_dir(control.rotation() * Vector3::unit_y());

        view.update_buffer()
    }
}