use std::fmt::Debug;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use std::time::Duration;
use eframe::NativeOptions;
use egui::{CentralPanel, Panel, Ui};
use egui_tiles::TileId;
use common::formats::binary_dse::DseExecutable;
use dialog::confirm_dialog::ConfirmDialog;
use panels::documentation::Documentation;
use panels::editor::Editor;
use dialog::file_dialog::DsaFileDialog;
use emulator_core::{EmulatorController};
use panels::PanelFactory;
use crate::ui::controller::Controller;
use crate::ui::dialog::error_dialog::ErrorDialog;
use crate::ui::panels::{CentralTiles, TilePane};
use crate::ui::panels::about::AboutScreen;
use crate::ui::panels::disassembler::BinaryViewer;
use crate::ui::panels::display::Display;
use crate::ui::panels::framebuffer::FrameBuffer;
use crate::ui::panels::memory::MemoryInspector;
use crate::ui::panels::registers::Registers;
use crate::ui::panels::serial::Serial;

mod dialog;
mod panels;
mod ui_helpers;
mod controller;

pub fn run_app(mut controller: EmulatorController) -> eframe::Result<()> {
    // initialise emulator
    controller.run().unwrap();

    eframe::run_native(
        "Damn Simple Architecture",
        NativeOptions::default(),
        Box::new(|cc| Ok(Box::new(DamnSimpleUI::new(cc, controller)))),
    )
}

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

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

#[derive(Debug, Clone)]
pub enum DataSource {
    File(PathBuf),
    Data(Vec<u8>),
}

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


pub struct DamnSimpleUI {
    emulator: EmulatorController,
    central_tiles: CentralTiles,

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

    tools: Vec<PanelFactory>,
}


impl eframe::App for DamnSimpleUI {
    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::bottom("bottom_panel").resizable(false).show(ui, |ui| {
            // self.process_manager.ui(ui);
        });

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




impl DamnSimpleUI {

    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,
        mut emulator: EmulatorController,
    ) -> Self {
        configure_appearance(&cc.egui_ctx);
        let handle = emulator.get_handle().clone();
        Self {
            central_tiles: CentralTiles::new(),
            controller: Controller::new(handle.clone()),
            confirm_dialog: ConfirmDialog::new(),
            file_dialog: DsaFileDialog::new(),
            error_dialog: ErrorDialog::default(),
            
            tools: vec![
                PanelFactory::register("Memory Inspector", {
                    let handle = handle.clone();
                    move || MemoryInspector::new(handle.clone())
                }),
                PanelFactory::register("Documentation", Documentation::default),
                PanelFactory::register("Display", {
                    let handle = handle.clone();
                    move || Display::new(80, 25, 0x20000, handle.clone())
                }),
                PanelFactory::register("Framebuffer", {
                    let handle = handle.clone();
                    move || FrameBuffer::new(320, 240, 0x30000, handle.clone())
                }),
                PanelFactory::register("Serial",  {
                    let handle = handle.clone();
                    move || Serial::new(0x40000, handle.clone())
                }),
                PanelFactory::register("Registers",  {
                    let handle = handle.clone();
                    move || Registers::new(handle.clone())
                })
            ],
            emulator,
        }
    }

    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(BinaryViewer::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(DataSource::Data(exe)) => {
                self.emulator.get_handle().mem_write(0, &exe);
            }

            Action::LoadBinary(DataSource::File(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.get_handle().mem_write(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(|path| Action::LoadBinary(DataSource::File(path)));
                }

                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);
                }
            });
        });
    }
}

pub fn configure_appearance(ctx: &egui::Context) {
    // configure appearance of UI elements
    let mut visuals = egui::Visuals::dark();

    // --- your existing settings ---
    visuals.window_fill = egui::Color32::from_rgb(20, 20, 20);
    visuals.panel_fill = egui::Color32::from_rgb(20, 20, 20);
    visuals.widgets.inactive.fg_stroke =
        egui::Stroke::from((1.0, egui::Color32::from_rgb(255, 255, 255)));
    visuals.widgets.inactive.bg_stroke =
        egui::Stroke::from((1.0, egui::Color32::from_rgb(60, 60, 60)));
    visuals.widgets.inactive.corner_radius = egui::CornerRadius::from(4);
    visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(20, 20, 20);
    visuals.widgets.inactive.weak_bg_fill = egui::Color32::from_rgb(20, 20, 20);
    visuals.widgets.inactive.expansion = 1.0;

    // tile resize handle
    visuals.window_stroke = egui::Stroke::new(1.0, egui::Color32::from_rgb(45, 45, 45));
    visuals.extreme_bg_color = egui::Color32::from_rgb(20, 20, 20);

    ctx.set_visuals(visuals);

    let mut fonts = egui::FontDefinitions::default();
    fonts.font_data.insert(
        "JetBrains Mono Nerd Font".to_string(),
        std::sync::Arc::new(egui::FontData::from_static(include_bytes!(
            "../../font/JetBrainsMonoNerdFontMono_Regular.ttf",
        ))),
    );

    fonts
        .families
        .entry(egui::FontFamily::Proportional)
        .or_default()
        .insert(0, "JetBrains Mono Nerd Font".to_string());

    fonts
        .families
        .entry(egui::FontFamily::Monospace)
        .or_default()
        .insert(0, "JetBrains Mono Nerd Font".to_string());

    ctx.set_fonts(fonts);
}
