use fyrox::{
    animation::spritesheet::SpriteSheetAnimation,
    core::{
        algebra::{Vector2, Vector3},
        pool::Handle,
        reflect::prelude::*,
        uuid::{uuid, Uuid},
        visitor::prelude::*,
        TypeUuidProvider,
    },
    keyboard::KeyCode,
    scene::dim2::{rectangle::Rectangle, rigidbody::RigidBody},
    scene::node::Node,
    script::ScriptContext,
};

#[derive(Visit, Reflect, Debug, Clone, Default)]
pub struct Player {
    sprite: Handle<Node>,
    move_left: bool,
    move_right: bool,
    jump: bool,
    animations: Vec<SpriteSheetAnimation>,
    current_animation: u32,
    current_frame: u32,

    start_y_position: f32,
    prev_y_velosity: f32,

    pub window_height: u32, // TODO: need change place
    pub window_width: u32,
}

impl TypeUuidProvider for Player {
    // Returns unique script id for serialization needs.
    fn type_uuid() -> Uuid {
        uuid!("c5671d19-9f1a-4286-8486-add4ebaadaec")
    }
}

impl Player {
    pub fn init(&mut self, context: &mut ScriptContext) {
        let rigid_body = context.scene.graph[context.handle].cast_mut::<RigidBody>();
        match rigid_body {
            Some(rigid_data) => {
                self.start_y_position = rigid_data.global_position().y;
            }
            None => {}
        }
    }

    pub fn loop_over(&mut self, context: &mut ScriptContext) {
        let rigid_body = context.scene.graph[context.handle].cast_mut::<RigidBody>();
        match rigid_body {
            Some(rigid_data) => {
                self.prev_y_velosity = rigid_data.lin_vel().y;
            }
            None => {}
        }
    }

    pub fn key_checker(&mut self, keycode: KeyCode, is_pressed: bool) {
        match keycode {
            KeyCode::ArrowLeft => self.move_left = is_pressed,
            KeyCode::ArrowRight => self.move_right = is_pressed,
            KeyCode::ArrowUp => self.jump = is_pressed,
            _ => (),
        }
    }

    fn get_speed(&self) -> f32 {
        return if self.move_left {
            3.0
        } else if self.move_right {
            -3.0
        } else {
            0.0
        };
    }

    pub fn move_do(&mut self, context: &mut ScriptContext) {
        let rigid_body = context.scene.graph[context.handle].cast_mut::<RigidBody>();
        match rigid_body {
            Some(rigid_data) => {
                let x_speed = self.get_speed();
                rigid_data.set_lin_vel(Vector2::new(x_speed, rigid_data.lin_vel().y));
            }
            None => {}
        }
    }

    pub fn is_in_fly(&self, rigid_data: &RigidBody) -> bool {
        let dif_y = self.prev_y_velosity - rigid_data.lin_vel().y;
        if dif_y > 0.0 && dif_y > 0.1 {
            return true;
        }
        if dif_y < 0.0 && dif_y < -0.1 {
            return true;
        }
        return false;
    }

    pub fn jump_do(&mut self, context: &mut ScriptContext) {
        let rigid_body = context.scene.graph[context.handle].cast_mut::<RigidBody>();
        match rigid_body {
            Some(rigid_data) => {
                if self.jump && !self.is_in_fly(rigid_data) {
                    self.start_y_position = rigid_data.global_position().y;
                    rigid_data.set_lin_vel(Vector2::new(rigid_data.lin_vel().x, 6.0));
                    return;
                }
                if rigid_data.lin_vel().y == 0.0 && !self.is_in_fly(rigid_data) {
                    self.start_y_position = rigid_data.global_position().y;
                }

                // Log::info(format!(
                //     "pos {}, dif_height: {}, lin_vel: {}, jump: {}, is_jumping: {}",
                //     self.prev_y_velosity - rigid_data.lin_vel().y,
                //     self.dif_height(rigid_data),
                //     rigid_data.lin_vel().y,
                //     self.prev_y_velosity - rigid_data.lin_vel().y,
                //     self.is_in_fly(rigid_data),
                // ));
            }
            None => {}
        }
    }

    pub fn change_orientation(&self, context: &mut ScriptContext) {
        let sprite = context.scene.graph.try_get_mut(self.sprite);
        match sprite {
            Some(sprite_data) => {
                let x_speed = self.get_speed();
                if x_speed == 0.0 {
                    return;
                }
                // It is always a good practice to check whether the handles are valid, at this point we don't know
                // for sure what's the value of the `sprite` field. It can be unassigned and the following code won't
                // execute. A simple `context.scene.graph[self.sprite]` would just panicked in this case.
                let local_transform: &mut fyrox::scene::transform::Transform =
                    sprite_data.local_transform_mut();
                let current_scale = **local_transform.scale();
                local_transform.set_scale(Vector3::new(
                    // Just change X scaling to mirror player's sprite.
                    current_scale.x.copysign(-x_speed),
                    current_scale.y,
                    current_scale.z,
                ));
            }
            None => {}
        }
    }

    fn animate_choose(&mut self) {
        let x_speed = self.get_speed();

        if x_speed != 0.0 {
            self.current_animation = 0;
        } else {
            self.current_animation = 1;
        }
    }

    pub fn animation_do(&mut self, context: &mut ScriptContext) {
        self.animate_choose();
        let current_animation = self.animations.get_mut(self.current_animation as usize);
        match current_animation {
            Some(animation_data) => {
                animation_data.update(context.dt);
                if let Some(sprite) = context
                    .scene
                    .graph
                    .try_get_mut(self.sprite)
                    .and_then(|n| n.cast_mut::<Rectangle>())
                {
                    sprite.set_texture(animation_data.texture());
                    sprite.set_uv_rect(animation_data.current_frame_uv_rect().unwrap_or_default());
                }
            }
            None => {}
        }
    }
}
