use std::fmt::Debug;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use egui::{CentralPanel, Panel, Ui};
use egui_tiles::TileId;
use common::formats::binary_dse::DseExecutable;
use emulator_core::memory::ram::MemoryBank;
use emulator_core::processor::state::SharedState;
use crate::ui::components::controller::Controller;
use crate::ui::components::disassembler::Disassembler;
use crate::ui::components::memory::MemoryInspector;
use crate::ui::DsaUi;
use crate::ui::components::Component;
use dialog::confirm_dialog::ConfirmDialog;
use panels::documentation::Documentation;
use panels::editor::Editor;
use dialog::file_dialog::DsaFileDialog;
use emulator_core::memory::PhysAddr;
use panels::PanelFactory;
use crate::ui::components::display::Display;
use crate::ui::components::framebuffer::FrameBuffer;
use crate::ui::components::registers::Registers;
use crate::ui::components::serial::Serial;
use crate::ui::debugger::dialog::error_dialog::ErrorDialog;
use crate::ui::debugger::panels::{CentralTiles, TilePane};
use crate::ui::debugger::panels::about::AboutScreen;

pub mod dialog;
pub mod panels;
pub mod ui_helpers;

trait FileFormat: Debug + Clone {
    fn extension(&self) -> &'static str;
}

impl FileFormat for DseExecutable {
    fn extension(&self) -> &'static str {
        "dse"
    }
}

#[derive(Debug, Clone)]
pub enum Action {
    RequestCloseTab(TileId),
    CloseTab(TileId),
    Quit,
    OpenFile(PathBuf),
    // OpenCachedFile(Box<dyn FileFormat>),
    LoadBinary(PathBuf),
    SaveFile(PathBuf, TileId),
    RequestSaveAs(TileId),
    ShowError(String),
}

struct EmulatorHandle {
    state: Arc<SharedState>,
    memory: MemoryBank,
}

impl EmulatorHandle {
    pub fn new(state: Arc<SharedState>, memory: MemoryBank) -> Self {
        Self { state, memory }
    }

    pub fn write_bytes(&mut self, offset: PhysAddr, data: &[u8]) {
        self.memory.write_bytes(offset, data);
    }
}


pub struct DebuggerUi {
    emulator: EmulatorHandle,

    central_tiles: CentralTiles,

    controller: Controller,
    confirm_dialog: ConfirmDialog<Action>,
    file_dialog: DsaFileDialog<Action>,
    error_dialog: ErrorDialog,

    tools: Vec<PanelFactory>
}

impl DebuggerUi {

    fn open_or_focus<T: TilePane + 'static>(
        central_tiles: &mut CentralTiles,
        slot: &mut Option<TileId>,
        make_pane: impl FnOnce() -> T,
    ) {
        match slot {
            Some(tile_id) => central_tiles.activate_tab(*tile_id),
            None => *slot = Some(central_tiles.add_tab(make_pane())),
        }
    }

    pub fn new(
        cc: &eframe::CreationContext,
        state: Arc<SharedState>,
        mem_handle: MemoryBank,
    ) -> Self {
        DsaUi::configure_appearance(&cc.egui_ctx);
        Self {
            emulator: EmulatorHandle::new(state.clone(), mem_handle.clone()),
            central_tiles: CentralTiles::new(),
            controller: Controller::new(state.clone(), mem_handle.clone()),
            confirm_dialog: ConfirmDialog::new(),
            file_dialog: DsaFileDialog::new(),
            error_dialog: ErrorDialog::default(),

            tools: vec![
                PanelFactory::register("Memory Inspector", {
                    let mem_handle = mem_handle.clone();
                    move || MemoryInspector::new(mem_handle.clone())
                }),
                PanelFactory::register("Documentation", Documentation::default),
                PanelFactory::register("Display", {
                    let mem_handle = mem_handle.clone();
                    move || Display::new(80, 25, 0x20000, mem_handle.clone())
                }),
                PanelFactory::register("Framebuffer", {
                    let mem_handle = mem_handle.clone();
                    move || FrameBuffer::new(320, 240, 0x30000, mem_handle.clone())
                }),
                PanelFactory::register("Serial",  {
                    let mem_handle = mem_handle.clone();
                    let state = state.clone();
                    move || Serial::new(0x40000, state.clone(), mem_handle.clone())
                }),
                PanelFactory::register("Registers",  {
                    let state = state.clone();
                    move || Registers::new(state.clone())
                })
            ]
        }
    }

    pub fn perform_action(&mut self, action: Action) {
        match action {

            Action::RequestCloseTab(tile_id) => {
                self.confirm_dialog.open(
                    "Close Tab",
                    "Closing this tab may result in losing unsaved work",
                    Action::CloseTab(tile_id)
                );
            }

            Action::CloseTab(tile_id) => {
                self.central_tiles.close_tab(tile_id);
            }

            Action::Quit => std::process::exit(0),

            // Opens a file (usually constructed by the file dialogue)
            Action::OpenFile(path) => {
                match path.extension().map(|s| s.to_str()).flatten() {
                    Some("dsa" | "dsc") => { self.central_tiles.add_tab(Editor::new_open(&path)); },
                    Some("dsb") => { self.central_tiles.add_tab(Disassembler::new_open(&path)); },
                    Some("md") => { self.central_tiles.add_tab(Documentation::open(&path)); },
                    Some(_) => todo!(),
                    None => {},
                }
            }

            Action::ShowError(err) => {
                self.error_dialog.open(err);
            }

            Action::LoadBinary(path) => {
                if let Some(ext) = path.extension().map(|s| s.to_str()).flatten() && ext == "dsb" {

                    let Ok(file) = File::open(path) else {
                        self.perform_action(Action::ShowError("Failed to open file".into()));
                        return
                    };

                    let Ok(binary) = DseExecutable::read(&mut BufReader::new(file)) else {
                        self.perform_action(Action::ShowError("Failed to read binary".into()));
                        return;
                    };

                    self.emulator.write_bytes(0, &binary.to_memory_image())
                }
            }

            Action::SaveFile(path, tile) => {
                if let Some(editor) = self.central_tiles.pane_mut::<Editor>(tile) {
                    if let Err(e) = std::fs::write(path, editor.get_buffer().as_bytes()) {
                        self.perform_action(Action::ShowError(e.to_string()))
                    }
                }
            }

            Action::RequestSaveAs(tile) => {
                self.file_dialog.open_save(move |path| Action::SaveFile(path, tile));
            }
        };
    }

    fn toolbar(&mut self, ui: &mut Ui) {
        egui::MenuBar::new().ui(ui, |ui| {

            let save_enabled = self.central_tiles.active_tile()
                .and_then(|id| self.central_tiles.pane_mut::<Editor>(id))
                .is_some();

            ui.set_height(30.0);
            ui.menu_button("File", |ui| {

                if ui.button("Open").clicked() {
                    self.file_dialog.open_pick(Action::OpenFile);
                }

                if ui.button("Load Binary").clicked() {
                    self.file_dialog.open_pick(Action::LoadBinary);
                }

                if ui.add_enabled(save_enabled, egui::Button::new("Save")).clicked() {
                    if let Some(active) = self.central_tiles.active_tile() {
                        if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
                            match editor.filename().cloned() {
                                Some(path) => std::fs::write(path, editor.get_buffer().as_bytes()).unwrap(),
                                None => self.file_dialog.open_save(move |path| Action::SaveFile(path, active)),
                            }
                        }
                    }
                }

                if ui.add_enabled(save_enabled, egui::Button::new("Save As")).clicked() {
                    if let Some(active) = self.central_tiles.active_tile() {
                        if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
                            self.file_dialog.open_save(move |path| Action::SaveFile(path, active))
                        }
                    }
                }

                if ui.button("Exit").clicked() {
                    self.confirm_dialog.open("Exit","Exit Application\n(you will lose unsaved work)", Action::Quit)
                }
            });

            ui.menu_button("Tools", |ui| {
                for PanelFactory(name, constructor) in &self.tools {
                    if ui.button(name).clicked() {
                        self.central_tiles.add_dyn_tab(constructor());
                    }
                }
            });

            ui.menu_button("Help", |ui| {
                if ui.button("About").clicked() {
                    self.central_tiles.add_tab(AboutScreen);
                }
            });
        });
    }
}

impl eframe::App for DebuggerUi {
    fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
        ui.request_repaint_after(Duration::from_millis(16));

        if let Some(action) = self.confirm_dialog.show(ui) {
            self.perform_action(action);
        }

        if let Some(action) = self.file_dialog.show(ui) {
            self.perform_action(action);
        }

        self.error_dialog.show(ui);

        Panel::top("menu_bar").resizable(false).show(ui, |ui| {
            self.toolbar(ui);
        });

        Panel::top("control_panel").resizable(false).show(ui, |ui| {
            self.controller.ui(ui);
        });

        Panel::left("left_panel").resizable(false).show(ui, |ui| {
            // TODO: File Explorer!
        });

        // Panel::right("right_panel").resizable(false).show(ui, |ui| {
        //     // TODO: not sure what to put here!
        // });

        CentralPanel::default().show(ui, |ui| {
            if let Some(action) = self.central_tiles.ui(ui) {
                self.perform_action(action);
            }
        });
    }
}
