use babalcore::*;
use gdnative::api::GlobalConstants;
use gdnative::api::Input as GodotInput;
use gdnative::api::InputEventKey;
use gdnative::api::InputEventMouseButton;
use gdnative::api::InputEventMouseMotion;
use gdnative::api::InputEventScreenDrag;
use gdnative::api::InputEventScreenTouch;
use gdnative::api::OS;
use gdnative::prelude::*;

const NORMALIZED_TOUCHSCREEN_WIDTH: f32 = 1920.0;

#[derive(NativeClass)]
#[inherit(Node)]
pub struct Input {
    disabled: bool,
    mouse_captured: bool,
    has_touchscreen: bool,
    has_keyboard: bool,
    escape_mode: bool,
    input_jump: InputJump,
    input_steer: InputSteer,
    jump_button: Option<JumpButton>,
    viewport_rect: Option<Vector2>,
}

#[derive(Debug, Clone, Copy)]
struct JumpButton {
    top_left: Vector2,
    bottom_right: Vector2,
}

#[methods]
impl Input {
    fn new(_owner: &Node) -> Self {
        let os = OS::godot_singleton();
        let has_touchscreen = os.has_touchscreen_ui_hint();
        godot_print!("Touch mode hint: {}", has_touchscreen);

        Input {
            disabled: false,
            mouse_captured: false,
            has_touchscreen,
            has_keyboard: false,
            escape_mode: false,
            input_jump: InputJump::new(),
            input_steer: InputSteer::new(),
            jump_button: None,
            viewport_rect: None,
        }
    }

    #[export]
    fn flush(&mut self, _owner: &Node) {
        self.input_jump = InputJump::new();
        self.input_steer = InputSteer::new();
    }

    fn normalize_mouse_x(&self, x: f32) -> f64 {
        x as f64
    }

    fn normalize_touchscreen_x(&self, x: f32) -> f64 {
        match self.viewport_rect {
            Some(viewport_rect) => (x * NORMALIZED_TOUCHSCREEN_WIDTH / viewport_rect.x) as f64,
            None => x as f64,
        }
    }

    #[export]
    fn _process(&mut self, _owner: &Node, _delta: f64) {
        if !self.has_keyboard {
            return;
        }

        let input = GodotInput::godot_singleton();

        for scancode in vec![
            GlobalConstants::KEY_LEFT,
            GlobalConstants::KEY_KP_4,
            GlobalConstants::KEY_A,
            GlobalConstants::KEY_H,
        ] {
            self.input_steer
                .left_key(scancode, 1.0, input.is_key_pressed(scancode), None);
        }
        for scancode in vec![
            GlobalConstants::KEY_RIGHT,
            GlobalConstants::KEY_KP_6,
            GlobalConstants::KEY_D,
            GlobalConstants::KEY_L,
        ] {
            self.input_steer
                .right_key(scancode, 1.0, input.is_key_pressed(scancode), None);
        }
    }

    #[export]
    fn _input(&mut self, _owner: &Node, event: Variant) {
        if self.disabled {
            return;
        }
        if let Some(screen_drag) = event.try_to_object::<InputEventScreenDrag>() {
            if self.mouse_captured {
                let screen_drag = unsafe { screen_drag.assume_safe() };
                godot_print!("{:?}", screen_drag.relative());
                self.input_steer
                    .mouse_move(self.normalize_touchscreen_x(screen_drag.relative().x));
            }
        } else if let Some(mouse_motion) = event.try_to_object::<InputEventMouseMotion>() {
            if self.has_touchscreen {
                return;
            }
            if self.mouse_captured {
                let mouse_motion = unsafe { mouse_motion.assume_safe() };
                self.input_steer
                    .mouse_move(self.normalize_mouse_x(mouse_motion.relative().x));
            }
        } else if let Some(screen_touch) = event.try_to_object::<InputEventScreenTouch>() {
            let screen_touch = unsafe { screen_touch.assume_safe() };
            if let Some(jump_button) = self.jump_button {
                let global_position = screen_touch.position();
                if global_position.x >= jump_button.top_left.x
                    && global_position.y >= jump_button.top_left.y
                    && global_position.x <= jump_button.bottom_right.x
                    && global_position.y <= jump_button.bottom_right.y
                {
                    self.request_jump();
                }
            }
        } else if let Some(mouse_button) = event.try_to_object::<InputEventMouseButton>() {
            if self.has_touchscreen {
                return;
            }
            let mouse_button = unsafe { mouse_button.assume_safe() };
            if mouse_button.is_pressed() {
                match mouse_button.button_index() {
                    GlobalConstants::BUTTON_LEFT => {
                        if self.mouse_captured {
                            self.request_jump()
                        }
                    }
                    GlobalConstants::BUTTON_RIGHT => self.escape(),
                    _ => {}
                }
            }
        } else if let Some(key) = event.try_to_object::<InputEventKey>() {
            self.has_keyboard = true;
            let key = unsafe { key.assume_safe() };
            match key.scancode() {
                GlobalConstants::KEY_ESCAPE => self.escape(),
                GlobalConstants::KEY_BACKSPACE => self.escape(),
                GlobalConstants::KEY_DELETE => self.escape(),
                GlobalConstants::KEY_F10 => self.escape(),
                GlobalConstants::KEY_SPACE => {
                    if key.is_pressed() {
                        self.request_jump();
                    }
                }
                GlobalConstants::KEY_ENTER => {
                    if key.is_pressed() {
                        self.request_jump();
                    }
                }
                _ => {}
            }
        }
    }

    #[export]
    fn _ready(&mut self, _owner: &Node) {
        godot_print!("input start");
        self.capture(true);
    }

    #[export]
    fn _tree_exiting(&mut self, _owner: &Node) {
        godot_print!("input stop");
        self.capture(false);
    }

    #[export]
    pub fn pop_jump(&mut self, _owner: &Node) -> bool {
        self.input_jump.pop_jump(None)
    }

    #[export]
    pub fn pop_steer(&mut self, _owner: &Node) -> f64 {
        self.input_steer.pop_steer(None)
    }

    #[export]
    fn set_jump_stickiness_factor(&mut self, _owner: &Node, jump_stickiness_factor: f64) {
        self.input_jump
            .set_stickiness_secs_f64(Some(jump_stickiness_factor * DEFAULT_STICKINESS_SECS_F64));
    }

    #[export]
    fn set_mouse_sensibility_factor(&mut self, _owner: &Node, mouse_sensibility_factor: f64) {
        self.input_steer.mouse_sensibility = mouse_sensibility_factor * DEFAULT_MOUSE_SENSIBILITY;
    }

    #[export]
    fn set_keyboard_sensibility_factor(&mut self, _owner: &Node, keyboard_sensibility_factor: f64) {
        self.input_steer.keyboard_sensibility =
            keyboard_sensibility_factor * DEFAULT_KEYBOARD_SENSIBILITY;
    }

    #[export]
    fn set_jump_button(&mut self, _owner: &Node, position: Vector2, size: Vector2, scale: Vector2) {
        if size.x <= 0.0 || size.y <= 0.0 || scale.x <= 0.0 || scale.y <= 0.0 {
            return;
        }
        let top_left = position;
        let bottom_right = position + Vector2::new(size.x * scale.x, size.y * scale.y);
        self.jump_button = Some(JumpButton {
            top_left,
            bottom_right,
        });
    }

    #[export]
    fn set_viewport_rect(&mut self, _owner: &Node, rect: Vector2) {
        if rect.x <= 0.0 || rect.y <= 0.0 {
            return;
        }
        self.viewport_rect = Some(rect);
    }

    #[export]
    fn get_escape_mode(&self, _owner: &Node) -> bool {
        return self.escape_mode;
    }

    #[export]
    fn set_escape_mode(&mut self, _owner: &Node, mode: bool) {
        self.escape_mode = mode;
        self.capture(!mode);
    }

    #[export]
    fn disable(&mut self, owner: &Node) {
        self.set_escape_mode(owner, true);
        self.disabled = true;
    }

    fn request_jump(&mut self) {
        self.input_jump.push_jump(None);
    }

    fn escape(&mut self) {
        self.escape_mode = true;
        self.capture(false);
    }

    fn capture(&mut self, state: bool) {
        if state != self.mouse_captured {
            let mode = if state {
                gdnative::api::Input::MOUSE_MODE_CAPTURED
            } else {
                gdnative::api::Input::MOUSE_MODE_VISIBLE
            };
            self.mouse_captured = state;
            if !self.has_touchscreen {
                let input = gdnative::api::Input::godot_singleton();
                input.set_mouse_mode(mode);
            }
        }
    }
}
