use std::cell::RefCell;
use std::rc::Rc;

use downcast::Downcast;
use imgui::{Condition, MenuItem, TabItem, TabItemFlags, Window};

use crate::editor::{CurrentTool, EditorInstance};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::keyboard;
use crate::framework::keyboard::ScanCode;
use crate::framework::ui::Components;
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::graphics::font::Font;
use crate::scene::Scene;
use crate::scene::game_scene::GameScene;
use crate::scene::title_scene::TitleScene;

struct ErrorList {
    errors: Vec<String>,
}

impl ErrorList {
    fn new() -> ErrorList {
        Self { errors: Vec::new() }
    }

    fn try_or_push_error(&mut self, func: impl FnOnce() -> GameResult<()>) {
        if let Err(err) = func() {
            self.errors.push(err.to_string());
        }
    }
}

fn catch(error_list: Rc<RefCell<ErrorList>>, func: impl FnOnce() -> GameResult<()>) {
    error_list.borrow_mut().try_or_push_error(func);
}

pub struct EditorScene {
    stage_list: StageListWindow,
    error_list: Rc<RefCell<ErrorList>>,
    instances: Vec<EditorInstance>,
    subscene: Option<Box<GameScene>>,
    current_tool: CurrentTool,
    selected_instance: usize,
    switch_tab: bool,
}

impl EditorScene {
    pub fn new() -> Self {
        EditorScene {
            stage_list: StageListWindow::new(),
            error_list: Rc::new(RefCell::new(ErrorList::new())),
            instances: Vec::new(),
            subscene: None,
            current_tool: CurrentTool::Move,
            selected_instance: 0,
            switch_tab: false,
        }
    }

    fn exit_editor(&mut self, state: &mut SharedGameState) {
        state.next_scene = Some(Box::new(TitleScene::new()));
    }

    fn open_stage(&mut self, state: &mut SharedGameState, ctx: &mut Context, stage_id: usize) {
        catch(self.error_list.clone(), || {
            for (idx, instance) in self.instances.iter().enumerate() {
                if instance.stage_id == stage_id {
                    self.selected_instance = idx;
                    self.switch_tab = true;
                    return Ok(());
                }
            }

            if let Some(stage) = state.stages.get(stage_id) {
                let stage = Stage::load(&state.constants.base_paths, stage, ctx)?;

                let new_instance = EditorInstance::new(stage_id, stage);
                self.instances.push(new_instance);
                self.selected_instance = self.instances.len() - 1;
                self.switch_tab = true;
            }

            Ok(())
        });
    }

    fn test_stage(&mut self, state: &mut SharedGameState, ctx: &mut Context) {
        catch(self.error_list.clone(), || {
            if let Some(instance) = self.instances.get(self.selected_instance) {
                state.reset();
                state.textscript_vm.start_script(94);
                let mut game_scene = GameScene::from_stage(state, ctx, instance.stage.clone(), instance.stage_id)?;
                game_scene.init(state, ctx)?;
                game_scene.player1.cond.set_alive(true);
                game_scene.player1.x = instance.frame.x + (state.canvas_size.0 * 256.0) as i32;
                game_scene.player1.y = instance.frame.y + (state.canvas_size.1 * 256.0) as i32;
                state.control_flags.set_control_enabled(true);
                state.control_flags.set_tick_world(true);
                state.textscript_vm.suspend = false;
                self.subscene = Some(Box::new(game_scene));
            }

            Ok(())
        });
    }

    fn perform_actions(&mut self, state: &mut SharedGameState, ctx: &mut Context) {
        let actions = std::mem::take(&mut self.stage_list.actions);
        for action in actions.iter() {
            match action {
                StageListAction::OpenStage(idx) => self.open_stage(state, ctx, *idx),
            }
        }
    }
}

trait ExtraWidgetsExt {
    fn tool_button(&self, label: impl AsRef<str>, active: bool) -> bool;
}

impl ExtraWidgetsExt for imgui::Ui<'_> {
    fn tool_button(&self, label: impl AsRef<str>, active: bool) -> bool {
        if active {
            let color = self.style_color(imgui::StyleColor::ButtonActive);
            let _token1 = self.push_style_color(imgui::StyleColor::Button, color);
            let _token2 = self.push_style_color(imgui::StyleColor::ButtonHovered, color);
            let ret = self.button(label);
            ret
        } else {
            self.button(label)
        }
    }
}

impl Scene for EditorScene {
    fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
        state.sound_manager.play_song(0, &state.constants, &state.settings, ctx, false)?;

        Ok(())
    }

    fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
        let subscene_ref = &mut self.subscene;
        if subscene_ref.is_some() {
            subscene_ref.as_mut().unwrap().tick(state, ctx)?;

            if keyboard::is_key_pressed(ctx, ScanCode::Escape) {
                *subscene_ref = None;
            }

            // hijack scene switches
            let next_scene = std::mem::take(&mut state.next_scene);
            if let Some(next_scene) = next_scene {
                *subscene_ref = if let Ok(game_scene) = next_scene.downcast() {
                    let mut game_scene: Box<GameScene> = game_scene;
                    game_scene.init(state, ctx)?;
                    Some(game_scene)
                } else {
                    None
                };
            }

            if subscene_ref.is_none() {
                state.sound_manager.play_song(0, &state.constants, &state.settings, ctx, false)?;
            }

            return Ok(());
        }

        Ok(())
    }

    fn draw_tick(&mut self, state: &mut SharedGameState) -> GameResult {
        if let Some(scene) = &mut self.subscene {
            scene.draw_tick(state)?;
            return Ok(());
        }

        Ok(())
    }

    fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
        if let Some(scene) = &self.subscene {
            scene.draw(state, ctx)?;

            state.font.builder().position(4.0, 4.0).draw(
                "Press [ESC] to return.",
                ctx,
                &state.constants,
                &mut state.texture_set,
            )?;
            
            return Ok(());
        }

        if let Some(instance) = self.instances.get(self.selected_instance) {
            instance.draw(state, ctx, self.current_tool)?;
        }

        Ok(())
    }

    fn imgui_draw(
        &mut self,
        _game_ui: &mut Components,
        state: &mut SharedGameState,
        ctx: &mut Context,
        ui: &mut imgui::Ui,
    ) -> GameResult {
        self.perform_actions(state, ctx);

        if let Some(_) = self.subscene {
            return Ok(());
        }

        let mut menu_bar_size = (0.0, 0.0);
        if let Some(menu_bar) = ui.begin_main_menu_bar() {
            let [menu_bar_w, menu_bar_h] = ui.window_size();
            menu_bar_size = (menu_bar_w, menu_bar_h);

            if let Some(menu) = ui.begin_menu("File") {
                if MenuItem::new("Open stage").shortcut("Ctrl+O").build(ui) {
                    self.stage_list.show();
                }

                ui.separator();

                if MenuItem::new("Exit editor").build(ui) {
                    self.exit_editor(state);
                }

                menu.end();
            }
            menu_bar.end();
        }

        ui.window("Toolbar")
            .title_bar(false)
            .resizable(false)
            .position([0.0, menu_bar_size.1], Condition::Always)
            .size([menu_bar_size.0, 0.0], Condition::Always)
            .build(|| {
                if ui.tool_button("Move", self.current_tool == CurrentTool::Move) {
                    self.current_tool = CurrentTool::Move;
                }
                ui.same_line();
                if ui.tool_button("Brush", self.current_tool == CurrentTool::Brush) {
                    self.current_tool = CurrentTool::Brush;
                }
                ui.same_line();
                if ui.tool_button("Fill", self.current_tool == CurrentTool::Fill) {
                    self.current_tool = CurrentTool::Fill;
                }
                ui.same_line();
                if ui.tool_button("Rectangle", self.current_tool == CurrentTool::Rectangle) {
                    self.current_tool = CurrentTool::Rectangle;
                }

                ui.same_line();
                ui.text("|");

                ui.same_line();
                if ui.button("Test Stage") {
                    self.test_stage(state, ctx);
                }

                if let Some(tab) = ui.tab_bar("Stages") {
                    for (idx, inst) in self.instances.iter().enumerate() {
                        let mut flags = TabItemFlags::NO_CLOSE_WITH_MIDDLE_MOUSE_BUTTON;
                        if self.switch_tab && self.selected_instance == idx {
                            self.switch_tab = false;
                            flags |= TabItemFlags::SET_SELECTED;
                        }

                        if let Some(item) = TabItem::new(&inst.stage.data.name).flags(flags).begin(ui) {
                            if !self.switch_tab {
                                self.selected_instance = idx;
                            }
                            item.end();
                        }
                    }

                    tab.end();
                }
            });

        self.stage_list.action(state, ctx, ui);

        if let Some(instance) = self.instances.get_mut(self.selected_instance) {
            instance.process(state, ctx, ui, self.current_tool);
        }

        Ok(())
    }
}

struct StageListWindow {
    visible: bool,
    selected_stage: i32,
    actions: Vec<StageListAction>,
}

enum StageListAction {
    OpenStage(usize),
}

impl StageListWindow {
    fn new() -> Self {
        StageListWindow { visible: false, selected_stage: 0, actions: Vec::new() }
    }

    fn show(&mut self) {
        self.visible = true;
    }

    fn action(&mut self, state: &mut SharedGameState, _ctx: &mut Context, ui: &mut imgui::Ui) {
        if !self.visible {
            return;
        }

        ui.window("Stage list")
            .resizable(false)
            .collapsible(false)
            .position_pivot([0.5, 0.5])
            .size([300.0, 352.0], Condition::FirstUseEver)
            .build(|| {
                let mut stages = Vec::with_capacity(state.stages.len());
                for stage in state.stages.iter() {
                    stages.push(stage.name.as_str());
                }

                ui.push_item_width(-1.0);
                ui.list_box("", &mut self.selected_stage, &stages, 14);

                ui.disabled(self.selected_stage < 0, || {
                    if ui.button("Open") {
                        self.actions.push(StageListAction::OpenStage(self.selected_stage as usize));
                    }

                    ui.same_line();
                    if ui.button("Edit table entry") {}
                });

                ui.same_line();
                if ui.button("Cancel") {
                    self.visible = false;
                }
            });
    }
}
