use crate::common::Rect;
use crate::components::draw_common::{Alignment, draw_number};
use crate::components::hud::HUD;
use crate::entity::GameEntity;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::game::frame::Frame;
use crate::game::inventory::Inventory;
use crate::game::shared_game_state::SharedGameState;
use crate::input::touch_controls::TouchControlType;
use crate::game::player::Player;
use crate::game::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState};
use crate::game::weapon::{WeaponLevel, WeaponType};

#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
enum InventoryFocus {
    None,
    Weapons,
    Items,
}

#[derive(Copy, Clone)]
struct InvWeaponData {
    wtype: WeaponType,
    level: WeaponLevel,
    ammo: u16,
    max_ammo: u16,
}

pub struct InventoryUI {
    tick: usize,
    text_y_pos: u16,
    selected_weapon: u16,
    selected_item: u16,
    weapon_count: u16,
    item_count: u16,
    weapon_data: [InvWeaponData; 8],
    item_data: [(u16, u16); 32],
    focus: InventoryFocus,
}

impl InventoryUI {
    pub fn new() -> InventoryUI {
        InventoryUI {
            text_y_pos: 16,
            tick: 0,
            selected_weapon: 0,
            selected_item: 0,
            weapon_count: 0,
            item_count: 0,
            weapon_data: [InvWeaponData { wtype: WeaponType::None, level: WeaponLevel::None, ammo: 0, max_ammo: 0 }; 8],
            item_data: [(0u16, 0u16); 32],
            focus: InventoryFocus::None,
        }
    }

    fn get_item_event_number(&self, inventory: &Inventory) -> u16 {
        inventory.get_item_idx(self.selected_item as usize).map(|i| i.0 + 5000).unwrap_or(5000)
    }

    fn get_item_event_number_action(&self, inventory: &Inventory) -> u16 {
        inventory.get_item_idx(self.selected_item as usize).map(|i| i.0 + 6000).unwrap_or(6000)
    }

    fn get_weapon_event_number(&self, inventory: &Inventory) -> u16 {
        inventory.get_current_weapon().map(|w| w.wtype as u16 + 1000).unwrap_or(1000)
    }

    fn exit(&mut self, state: &mut SharedGameState, _player: &mut Player, inventory: &mut Inventory, hud: &mut HUD) {
        self.focus = InventoryFocus::None;
        inventory.current_item = 0;
        self.text_y_pos = 16;
        hud.weapon_x_pos = 32;
        state.textscript_vm.reset();
        state.textscript_vm.set_mode(ScriptMode::Map);
    }
}

impl GameEntity<(&mut Context, &mut Player, &mut Inventory, &mut HUD)> for InventoryUI {
    fn tick(
        &mut self,
        state: &mut SharedGameState,
        (ctx, player, inventory, hud): (&mut Context, &mut Player, &mut Inventory, &mut HUD),
    ) -> GameResult<()> {
        let (off_left, off_top, off_right, _) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
        let mut slot_rect =
            Rect::new_size(state.canvas_size.0 as isize - 34 - off_right as isize, 8 + off_top as isize, 26, 26);

        state.touch_controls.control_type = TouchControlType::None;

        if state.control_flags.control_enabled()
            && (player.controller.trigger_inventory()
            || player.controller.trigger_menu_back()
            || (player.controller.trigger_menu_ok() && self.focus == InventoryFocus::Weapons)
            || (state.settings.touch_controls && state.touch_controls.consume_click_in(slot_rect)))
        {
            state.control_flags.set_ok_button_disabled(false);
            self.exit(state, player, inventory, hud);
            return Ok(());
        }

        if self.text_y_pos > 8 {
            self.text_y_pos -= 1;
        }

        self.weapon_count = 0;
        for (idx, weapon) in self.weapon_data.iter_mut().enumerate() {
            if let Some(weapon_data) = inventory.get_weapon(idx) {
                weapon.wtype = weapon_data.wtype;
                weapon.level = weapon_data.level;
                weapon.ammo = weapon_data.ammo;
                weapon.max_ammo = weapon_data.max_ammo;

                self.weapon_count += 1;
            } else {
                weapon.wtype = WeaponType::None;
                break;
            }
        }

        self.item_count = 0;
        for (idx, (item_id, amount)) in self.item_data.iter_mut().enumerate() {
            if let Some(item_data) = inventory.get_item_idx(idx) {
                *item_id = item_data.0;
                *amount = item_data.1;
                self.item_count += 1;
            } else {
                *item_id = 0;
                break;
            }
        }

        self.selected_item = inventory.current_item;
        self.selected_weapon = inventory.current_weapon;

        let count_x = state.constants.textscript.inventory_item_count_x as u16;

        match self.focus {
            InventoryFocus::None => {
                self.focus = InventoryFocus::Weapons;
                state.control_flags.set_ok_button_disabled(false);
                // check weapon count (0 count means we run item script)
                let event = if self.weapon_count > 0 {
                    self.get_weapon_event_number(inventory)
                } else {
                    self.get_item_event_number(inventory)
                };
                state.textscript_vm.start_script(event);
            }
            InventoryFocus::Weapons if state.control_flags.control_enabled() => {

                // if we have no weapons, the TSC should not be refreshed with L/R keystrokes
                if self.weapon_count > 0
                {
                    if player.controller.trigger_left() {
                        state.sound_manager.play_sfx(4);
                        inventory.prev_weapon();
                        state.control_flags.set_ok_button_disabled(false);
                        state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
                    }
    
                    if player.controller.trigger_right() {
                        state.sound_manager.play_sfx(4);
                        inventory.next_weapon();
                        state.control_flags.set_ok_button_disabled(false);
                        state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
                    }
                }

                // we should not move from the weapon row if there are no items
                if (player.controller.trigger_up() || player.controller.trigger_down()) && self.item_count > 0 {
                    self.focus = InventoryFocus::Items;
                    state.control_flags.set_ok_button_disabled(false);
                    state.textscript_vm.start_script(self.get_item_event_number(inventory));
                }
            }
            InventoryFocus::Items if self.item_count != 0 && state.control_flags.control_enabled() => {
                let mut moved_cursor = false;

                if player.controller.trigger_left() {
                    state.sound_manager.play_sfx(1);

                    if (self.selected_item % count_x) != 0 {
                        self.selected_item -= 1;
                    } else {
                        self.selected_item += count_x - 1;
                    }

                    state.control_flags.set_ok_button_disabled(false);
                    moved_cursor = true;
                }

                if player.controller.trigger_right() {
                    match () {
                        _ if self.selected_item + 1 == self.item_count => {
                            self.selected_item = count_x * (self.selected_item / count_x);
                        }
                        _ if (self.selected_item % count_x) + 1 == count_x => {
                            self.selected_item = self.selected_item.saturating_sub(count_x - 1);
                        }
                        _ => self.selected_item += 1,
                    }

                    state.sound_manager.play_sfx(1);
                    state.control_flags.set_ok_button_disabled(false);
                    moved_cursor = true;
                }

                if player.controller.trigger_up() {
                    if self.selected_item < count_x {
                        self.focus = InventoryFocus::Weapons;

                        state.sound_manager.play_sfx(4);
                        state.control_flags.set_ok_button_disabled(false);
                        state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
                    } else {
                        self.selected_item -= count_x;

                        state.sound_manager.play_sfx(1);
                        state.control_flags.set_ok_button_disabled(false);
                        moved_cursor = true;
                    }
                }

                if player.controller.trigger_down() {
                    if self.selected_item / 6 == self.item_count.saturating_sub(1) / 6 {
                        self.focus = InventoryFocus::Weapons;

                        state.sound_manager.play_sfx(4);
                        state.control_flags.set_ok_button_disabled(false);
                        moved_cursor = true;
                    } else {
                        self.selected_item += count_x;

                        state.sound_manager.play_sfx(1);
                        state.control_flags.set_ok_button_disabled(false);
                        moved_cursor = true;
                    }
                }

                self.selected_item = self.selected_item.min(self.item_count - 1);

                if moved_cursor {
                    state.textscript_vm.start_script(self.get_item_event_number(inventory));
                }

                if !state.control_flags.ok_button_disabled() && player.controller.trigger_menu_ok() {
                    state.textscript_vm.start_script(self.get_item_event_number_action(inventory));
                }

                inventory.current_item = self.selected_item;
            }
            _ => {}
        }

        if state.settings.touch_controls && state.control_flags.control_enabled() {
            let x = ((((state.canvas_size.0 - off_left - off_right) - 244.0) / 2.0).floor() + off_left) as isize;
            let y = 8 + off_top as isize;

            for i in 0..self.weapon_count {
                slot_rect = Rect::new_size(x + 12 + i as isize * 40, y + 16, 40, 40);

                if state.touch_controls.consume_click_in(slot_rect) {
                    self.focus = InventoryFocus::Weapons;
                    state.sound_manager.play_sfx(4);
                    self.selected_weapon = i;
                    inventory.current_weapon = i;
                    state.textscript_vm.start_script(self.get_weapon_event_number(inventory));
                    self.exit(state, player, inventory, hud);
                }
            }

            for i in 0..self.item_count {
                slot_rect =
                    Rect::new_size(x + 12 + (i % count_x) as isize * 32, y + 68 + (i / count_x) as isize * 16, 32, 16);

                if state.touch_controls.consume_click_in(slot_rect) {
                    state.sound_manager.play_sfx(1);

                    if self.focus == InventoryFocus::Items && inventory.current_item == i {
                        state.textscript_vm.start_script(self.get_item_event_number_action(inventory));
                    } else {
                        self.selected_item = i;
                        inventory.current_item = self.selected_item;
                        self.focus = InventoryFocus::Items;
                        state.textscript_vm.start_script(self.get_item_event_number(inventory));
                    }
                }
            }
        }

        self.tick = self.tick.wrapping_add(1);

        Ok(())
    }

    fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult<()> {
        if state.textscript_vm.state == TextScriptExecutionState::MapSystem {
            return Ok(());
        }

        let mut tmp_rect = Rect { left: 0, top: 0, right: 0, bottom: 0 };
        let (off_left, off_top, off_right, _) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale);
        let x = (((state.canvas_size.0 - off_left - off_right) - 244.0) / 2.0).floor() + off_left;
        let y = 8.0 + off_top;

        let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
        for i in 0..=18 {
            let rect = match i {
                0 => &state.constants.textscript.inventory_rect_top,
                18 => &state.constants.textscript.inventory_rect_bottom,
                _ => &state.constants.textscript.inventory_rect_middle,
            };

            batch.add_rect(x, y + i as f32 * 8.0, rect);
        }

        batch.add_rect(x + 12.0, y + self.text_y_pos as f32, &state.constants.textscript.inventory_text_arms);

        batch.add_rect(x + 12.0, y + 52.0 + self.text_y_pos as f32, &state.constants.textscript.inventory_text_item);

        let (item_cursor_frame, weapon_cursor_frame) = match self.focus {
            InventoryFocus::None => (1, 1),
            InventoryFocus::Weapons => (1, (self.tick / 2) % 2), //every-other frame (& 1): this is not what we want, we want every 2 frames.
            InventoryFocus::Items => ((self.tick / 2) % 2, 1),
        };

        batch.add_rect(
            x + 12.0 + self.selected_weapon as f32 * 40.0,
            y + 16.0,
            &state.constants.textscript.cursor_inventory_weapon[weapon_cursor_frame],
        );

        let count_x = state.constants.textscript.inventory_item_count_x as usize;
        batch.add_rect(
            x + 12.0 + (self.selected_item as usize % count_x) as f32 * 32.0,
            y + 68.0 + (self.selected_item as usize / count_x) as f32 * 16.0,
            &state.constants.textscript.cursor_inventory_item[item_cursor_frame],
        );

        for (idx, weapon) in self.weapon_data.iter().enumerate() {
            if weapon.wtype == WeaponType::None {
                break;
            }

            // lv
            batch.add_rect(x + 12.0 + idx as f32 * 40.0, y + 32.0, &Rect::new_size(80, 80, 16, 8));
            // per
            batch.add_rect(x + 12.0 + idx as f32 * 40.0, y + 48.0, &Rect::new_size(72, 48, 8, 8));

            if weapon.max_ammo == 0 {
                batch.add_rect(x + 28.0 + idx as f32 * 40.0, y + 40.0, &Rect::new_size(80, 48, 16, 8));
                batch.add_rect(x + 28.0 + idx as f32 * 40.0, y + 48.0, &Rect::new_size(80, 48, 16, 8));
            }
        }

        batch.draw(ctx)?;

        let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ArmsImage")?;
        for (idx, weapon) in self.weapon_data.iter().enumerate() {
            if weapon.wtype == WeaponType::None {
                break;
            }

            tmp_rect.left = (weapon.wtype as u16 % 16) * 16;
            tmp_rect.top = (weapon.wtype as u16 / 16) * 16;
            tmp_rect.right = tmp_rect.left + 16;
            tmp_rect.bottom = tmp_rect.top + 16;

            batch.add_rect(x + 12.0 + idx as f32 * 40.0, y + 16.0, &tmp_rect);
        }

        batch.draw(ctx)?;

        let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ItemImage")?;

        for (idx, (item_id, _amount)) in self.item_data.iter().enumerate() {
            if *item_id == 0 {
                break;
            }

            tmp_rect.left = (*item_id % 8) * 32;
            tmp_rect.top = (*item_id / 8) * 16;
            tmp_rect.right = tmp_rect.left + 32;
            tmp_rect.bottom = tmp_rect.top + 16;

            batch.add_rect(
                x + 12.0 + (idx % count_x) as f32 * 32.0,
                y + 68.0 + (idx / count_x) as f32 * 16.0,
                &tmp_rect,
            );
        }

        batch.draw(ctx)?;

        for (idx, (item_id, amount)) in self.item_data.iter().enumerate() {
            if *item_id == 0 || *amount == 0 {
                break;
            }

            if *amount > 1 {
                draw_number(
                    x + 12.0 + (idx % count_x) as f32 * 32.0 + 32.0,
                    y + 68.0 + (idx / count_x) as f32 * 16.0,
                    *amount as usize,
                    Alignment::Right,
                    state,
                    ctx,
                )?;
            }
        }

        for (idx, weapon) in self.weapon_data.iter().enumerate() {
            if weapon.wtype == WeaponType::None {
                break;
            }

            draw_number(x + 44.0 + idx as f32 * 40.0, y + 32.0, weapon.level as usize, Alignment::Right, state, ctx)?;

            if weapon.max_ammo != 0 {
                draw_number(
                    x + 44.0 + idx as f32 * 40.0,
                    y + 40.0,
                    weapon.ammo as usize,
                    Alignment::Right,
                    state,
                    ctx,
                )?;
                draw_number(
                    x + 44.0 + idx as f32 * 40.0,
                    y + 48.0,
                    weapon.max_ammo as usize,
                    Alignment::Right,
                    state,
                    ctx,
                )?;
            }
        }

        if state.settings.touch_controls {
            let close_rect = Rect { left: 110, top: 110, right: 128, bottom: 128 };
            let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/touch")?;

            batch.add_rect(state.canvas_size.0 - off_right - 30.0, 12.0 + off_top, &close_rect);
            batch.draw(ctx)?;
        }

        Ok(())
    }
}
