use bevy::prelude::*;

use crate::{bike::CyberBikeBody, input::InputState};

// 85 degrees in radians
const MAX_PITCH: f32 = 1.48353;

#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Component)]
enum CyberCameras {
    Hero,
    Debug,
}

#[derive(Debug, Resource)]
pub struct DebugCamOffset {
    pub rot: f32,
    pub dist: f32,
    pub alt: f32,
}

impl Default for DebugCamOffset {
    fn default() -> Self {
        DebugCamOffset {
            rot: 60.0,
            dist: 10.0,
            alt: 4.0,
        }
    }
}

impl CyberCameras {
    fn next(self) -> Self {
        match self {
            CyberCameras::Debug => CyberCameras::Hero,
            CyberCameras::Hero => CyberCameras::Debug,
        }
    }
}

fn setup_cybercams(mut commands: Commands) {
    let hero_projection = PerspectiveProjection {
        fov: std::f32::consts::FRAC_PI_3,
        ..Default::default()
    };
    commands
        .spawn(Camera3dBundle {
            projection: bevy::render::camera::Projection::Perspective(hero_projection),
            ..Default::default()
        })
        .insert(CyberCameras::Hero);

    commands
        .spawn(Camera3dBundle::default())
        .insert(CyberCameras::Debug);
}

fn follow_cyberbike(
    mut query: ParamSet<(
        // 0: the bike
        Query<&Transform, With<CyberBikeBody>>,
        // 1: the cameras
        Query<(&mut Transform, &CyberCameras)>,
    )>,
    input: Res<InputState>,
    offset: Res<DebugCamOffset>,
) {
    let bike_xform = *query.p0().single();
    let up = bike_xform.translation.normalize();

    for (mut cam_xform, cam_type) in query.p1().iter_mut() {
        match *cam_type {
            CyberCameras::Hero => {
                let look_at = bike_xform.translation + (bike_xform.forward() * 500.0);
                let cam_pos = bike_xform.translation + (bike_xform.back() * 0.1) + (up * 0.8);

                cam_xform.translation = cam_pos;
                cam_xform.look_at(look_at, up);

                // handle input pitch
                let angle = input.pitch.powi(3) * MAX_PITCH;
                let axis = cam_xform.right();
                cam_xform.rotate(Quat::from_axis_angle(axis, angle));
            }
            CyberCameras::Debug => {
                let mut ncx = bike_xform.to_owned();
                ncx.rotate(Quat::from_axis_angle(up, offset.rot.to_radians()));
                ncx.translation += ncx.forward() * offset.dist;
                ncx.translation += ncx.up() * offset.alt;
                *cam_xform = ncx;
                cam_xform.look_at(bike_xform.translation, up);
            }
        }
    }
}

fn update_active_camera(
    state: Res<State<CyberCameras>>,
    mut query: Query<(&mut Camera, &CyberCameras)>,
) {
    // find the camera with the current state, set it as the ActiveCamera
    query.iter_mut().for_each(|(mut cam, cyber)| {
        if cyber.eq(state.current()) {
            cam.is_active = true;
        } else {
            cam.is_active = false;
        }
    });
}

fn cycle_cam_state(mut state: ResMut<State<CyberCameras>>, mut keys: ResMut<Input<KeyCode>>) {
    if keys.just_pressed(KeyCode::D) {
        let new_state = state.current().next();
        info!("{:?}", new_state);
        state.set(new_state).unwrap();
        keys.reset(KeyCode::D);
    }
}

pub struct CyberCamPlugin;

impl Plugin for CyberCamPlugin {
    fn build(&self, app: &mut bevy::prelude::App) {
        common(app);
    }
}

fn common(app: &mut bevy::prelude::App) {
    app.insert_resource(DebugCamOffset::default())
        .add_startup_system(setup_cybercams)
        .add_state(CyberCameras::Hero)
        .add_system(cycle_cam_state)
        .add_system(update_active_camera)
        .add_system(follow_cyberbike);
}
