use std::collections::HashMap;

use anyhow::Result;

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

use crate::{FREE_CAMERA_CONTROL, celestial_object::CelestialObject};

#[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, u32, Input), (Control, bool)>,
}

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

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

pub struct Game;

impl Game {
    pub fn update(&mut self, world: &mut World) -> Result<()> {
        if FREE_CAMERA_CONTROL {
            let now = world.now();
            let mut resources = world.resources.multi_mut();
            let scene = resources.get::<Scene>();
            let camera_control = resources.get::<FreeCameraControl>();

            camera_control.update(now, scene.view_mut())?;
        }

        Ok(())
    }

    pub fn event(&mut self, world: &mut World, event: EngineEvent<'_>) -> Result<()> {
        if let Some(event) = Self::motion_concepts(world, event)? {
            match event {
                EngineEvent::JoystickAdded(joystick) => {
                    println!("joystick {} added", joystick.name());
                }

                EngineEvent::JoystickRemoved(joystick) => {
                    println!("joystick {} removed", joystick.name());
                }

                _ => (),
            }
        }

        Ok(())
    }

    fn motion_concepts<'a>(
        world: &mut World,
        event: EngineEvent<'a>,
    ) -> Result<Option<EngineEvent<'a>>> {
        if FREE_CAMERA_CONTROL {
            Self::free_camera(world, event)
        } else {
            Self::joystick_movement(world, event)
        }
    }

    fn free_camera<'a>(
        world: &mut World,
        event: EngineEvent<'a>,
    ) -> Result<Option<EngineEvent<'a>>> {
        match event {
            EngineEvent::MouseButtonDown(MouseButton::Middle) => {
                let camera_control = world.resources.get_mut::<FreeCameraControl>();
                camera_control.mouse_down();
            }
            EngineEvent::MouseButtonUp(MouseButton::Middle) => {
                let camera_control = world.resources.get_mut::<FreeCameraControl>();
                camera_control.mouse_release();
            }
            EngineEvent::MouseMotion(x, y) => {
                let mut resources = world.resources.multi_mut();
                let scene = resources.get::<Scene>();
                let camera_control = resources.get::<FreeCameraControl>();

                camera_control.mouse_move(x, y, scene.view_mut())?;

                return Ok(Some(event));
            }
            EngineEvent::KeyDown(key) => {
                let camera_control = world.resources.get_mut::<FreeCameraControl>();

                match key {
                    Keycode::W => camera_control.forward_back(1.0),
                    Keycode::A => camera_control.left_right(-1.0),
                    Keycode::S => camera_control.forward_back(-1.0),
                    Keycode::D => camera_control.left_right(1.0),
                    Keycode::Space => camera_control.up_down(1.0),
                    Keycode::LCtrl => camera_control.up_down(-1.0),

                    _ => return Ok(Some(event)),
                }
            }
            EngineEvent::KeyUp(key) => {
                let camera_control = world.resources.get_mut::<FreeCameraControl>();

                match key {
                    Keycode::W => camera_control.forward_back(-1.0),
                    Keycode::A => camera_control.left_right(1.0),
                    Keycode::S => camera_control.forward_back(1.0),
                    Keycode::D => camera_control.left_right(-1.0),
                    Keycode::Space => camera_control.up_down(-1.0),
                    Keycode::LCtrl => camera_control.up_down(1.0),

                    _ => return Ok(Some(event)),
                }
            }

            _ => return Ok(Some(event)),
        }

        Ok(None)
    }

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

        match event {
            EngineEvent::JoystickAxis(joystick, axis_index, value) => {
                let mut 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, inverted)) =
                    input_settings.map_axis(joystick.name(), joystick.id(), axis_index)
                {
                    if inverted {
                        normalized = -normalized
                    };

                    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 => (),
                    }
                }
            }

            _ => return Ok(Some(event)),
        }

        Ok(None)
    }
}

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

        if !FREE_CAMERA_CONTROL {
            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_edited")?;
        // fighter.insert_component(FreeSpaceControl::new(
        //     0.02,
        //     FreeSpaceControlSettings::default(),
        // ));

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

        let example_planet = CelestialObject::new(world, 5, None)?;
        world.add_entity(example_planet)?;

        let context = world.resources.get::<Context>();
        let mut light = Light::point_light(context.device())?;
        light.set_position(vec3(10_000.0, 10_000.0, 10_000.0))?;
        light.set_power(50_000_000_000.0)?;
        light.set_color(vec3(1.0, 1.0, 1.0))?;

        let scene = world.resources.get_mut::<Scene>();
        scene.add_light(light)?;

        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<()> {
        const DEFAULT_CENTER: Vector4<f32> = vec4(0.0, 0.0, 0.0, 1.0);

        let scene = world.resources.get_mut::<Scene>();
        let view = scene.view_mut();

        view.camera_mut()
            .set_center((control.translation() * DEFAULT_CENTER).truncate());
        view.camera_mut()
            .set_eye_dir(control.rotation() * Vector3::unit_y());
        view.camera_mut()
            .set_up(control.rotation() * Vector3::unit_z());

        view.update_buffer()
    }
}