use clap::Parser;
use egui::{self, Separator};

mod component;
mod lock;
mod model;
mod plugin;

const APP_ID: &str = "centerpiece";
use smithay_client_toolkit::shell::WaylandSurface;

pub fn main() {
    let args = settings::cli::CliArgs::parse();
    settings::Settings::try_from(args).unwrap_or_else(|_| {
        eprintln!("There is an issue with the settings, please check the configuration file.");
        std::process::exit(1);
    });

    simple_logger::init_with_level(log::Level::Info).unwrap();

    let _lock = lock::LockFile::acquire();

    // Create channel for external events
    let (tx, rx) = std::sync::mpsc::channel::<AppEvent>();

    let mut app = wayapp::Application::new(move |t| {
        let _ = tx.send(AppEvent::WaylandDispatch(t));
    });

    // Example layer surface
    let layer_surface = app.layer_shell.create_layer_surface(
        &app.qh,
        app.compositor_state.create_surface(&app.qh),
        smithay_client_toolkit::shell::wlr_layer::Layer::Top,
        Some(APP_ID),
        None,
    );
    layer_surface.set_keyboard_interactivity(
        smithay_client_toolkit::shell::wlr_layer::KeyboardInteractivity::Exclusive,
    );
    layer_surface.set_margin(0, 0, 0, 0);
    layer_surface.set_size(800, 600);
    layer_surface.commit();

    let mut my_app = Centerpiece::new(&layer_surface);
    let mut egui_surface = wayapp::EguiSurfaceState::new(&app, &layer_surface, 800, 600);

    // Run the Wayland event loop
    app.run_dispatcher();

    loop {
        if let Ok(event) = rx.recv() {
            match event {
                AppEvent::WaylandDispatch(token) => {
                    let events = app.dispatch_pending(token);
                    egui_surface.handle_events(&mut app, &events, &mut |ctx| my_app.update(ctx));
                } // Handle other events here
            }
        }
    }
}

enum AppEvent {
    WaylandDispatch(wayapp::DispatchToken),
    // Other events can be added here
}

#[derive(Debug, Clone)]
pub enum Message {
    RegisterPlugin(model::Plugin),
    UpdateEntries(String, Vec<model::Entry>),
    Exit,
}

#[derive(Default)]
struct Centerpiece {
    query: String,
    active_entry_index: usize,
    plugins: Vec<model::Plugin>,
    plugin_channels: Vec<async_channel::Receiver<Message>>,
}

impl Centerpiece {
    fn new(_cc: &smithay_client_toolkit::shell::wlr_layer::LayerSurface) -> Self {
        let mut centerpiece = Self::default();
        println!("creating centerpiece");
        centerpiece.launch_plugins();
        centerpiece
    }

    fn launch_plugins(self: &mut Centerpiece) {
        let settings = settings::Settings::get_or_init();

        if settings.plugin.applications.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::applications::ApplicationsPlugin,
            >());
        }

        if settings.plugin.brave_bookmarks.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::brave::bookmarks::BookmarksPlugin,
            >());
        }

        if settings.plugin.brave_progressive_web_apps.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::brave::progressive_web_apps::ProgressiveWebAppsPlugin,
            >());
        }

        if settings.plugin.brave_history.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::brave::history::HistoryPlugin,
            >());
        }

        if settings.plugin.clock.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::clock::ClockPlugin,
            >());
        }

        if settings.plugin.firefox_bookmarks.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::firefox::bookmarks::BookmarksPlugin,
            >());
        }

        if settings.plugin.firefox_history.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::firefox::history::HistoryPlugin,
            >());
        }

        if settings.plugin.git_repositories.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::git_repositories::GitRepositoriesPlugin,
            >());
        }

        if settings.plugin.gitmoji.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::gitmoji::GitmojiPlugin,
            >());
        }

        if settings.plugin.resource_monitor_battery.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::resource_monitor::battery::BatteryPlugin,
            >());
        }

        if settings.plugin.resource_monitor_cpu.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::resource_monitor::cpu::CpuPlugin,
            >());
        }

        if settings.plugin.resource_monitor_disks.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::resource_monitor::disks::DisksPlugin,
            >());
        }

        if settings.plugin.resource_monitor_memory.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::resource_monitor::memory::MemoryPlugin,
            >());
        }

        if settings.plugin.system.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::system::SystemPlugin,
            >());
        }

        if settings.plugin.wifi.enable {
            self.plugin_channels
                .push(crate::plugin::utils::spawn::<crate::plugin::wifi::WifiPlugin>());
        }

        if settings.plugin.niri_windows.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::niri_windows::NiriWindowsPlugin,
            >());
        }

        if settings.plugin.sway_windows.enable {
            self.plugin_channels.push(crate::plugin::utils::spawn::<
                crate::plugin::sway_windows::SwayWindowsPlugin,
            >());
        }
    }

    fn exit() -> ! {
        lock::LockFile::cleanup();
        std::process::exit(0);
    }

    fn handle_input(&mut self, ctx: &egui::Context) {
        if ctx.input(|i| i.key_pressed(egui::Key::Enter)) {
            self.activate_selected_entry();
        }
        if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
            Self::exit();
        }
        if ctx.input(|i| {
            (i.modifiers.ctrl && i.key_pressed(egui::Key::K)) || i.key_pressed(egui::Key::ArrowUp)
        }) {
            self.select_previous_entry();
        }
        if ctx.input(|i| {
            (i.modifiers.ctrl && i.key_pressed(egui::Key::J)) || i.key_pressed(egui::Key::ArrowDown)
        }) {
            self.select_next_entry();
        }
        if ctx.input(|i| i.modifiers.ctrl && i.key_pressed(egui::Key::P)) {
            self.select_previous_plugin();
        }
        if ctx.input(|i| i.modifiers.ctrl && i.key_pressed(egui::Key::N)) {
            self.select_next_plugin();
        }
    }

    fn handle_messages(&mut self, messages: Vec<Message>) {
        for message in messages {
            match message {
                Message::RegisterPlugin(plugin) => self.register_plugin(plugin),

                Message::UpdateEntries(plugin_id, entries) => {
                    self.update_entries(plugin_id, entries)
                }

                Message::Exit => {
                    Self::exit();
                }
            }
        }
    }

    fn entries(&self) -> Vec<&model::Entry> {
        self.plugins
            .iter()
            .flat_map(|plugin| &plugin.entries)
            .collect()
    }

    fn active_entry_id(&self) -> Option<&String> {
        let entries = self.entries();
        let active_entry = entries.get(self.active_entry_index);
        match active_entry {
            Some(entry) => Some(&entry.id),
            None => None,
        }
    }

    fn search(&mut self) {
        for plugin in self.plugins.iter_mut() {
            let _ = plugin
                .app_channel_out
                .send_blocking(crate::model::PluginRequest::Search(self.query.clone()));
        }

        self.select_first_entry();
    }

    fn select_first_entry(&mut self) {
        self.active_entry_index = 0;
    }

    fn select_previous_entry(&mut self) {
        let entries = self.entries();
        if entries.is_empty() {
            return self.select_first_entry();
        }

        if self.active_entry_index == 0 {
            self.active_entry_index = entries.len() - 1;
            return;
        }

        self.active_entry_index -= 1;
    }

    fn select_next_entry(&mut self) {
        let entries = self.entries();
        if entries.is_empty() || self.active_entry_index == entries.len() - 1 {
            return self.select_first_entry();
        }

        self.active_entry_index += 1;
    }

    fn select_next_plugin(&mut self) {
        let accumulated_entries = self
            .plugins
            .iter()
            .map(|plugin| plugin.entries.len())
            .scan(0, |acc, len| {
                let prev = *acc;
                *acc += len;
                Some(prev)
            })
            .find(|&total| total > self.active_entry_index)
            .unwrap_or(self.active_entry_index);

        self.active_entry_index = accumulated_entries;
    }

    fn select_previous_plugin(&mut self) {
        if self.plugins.is_empty() || self.active_entry_index == 0 {
            return self.select_first_entry();
        }

        let accumulated_entries = self
            .plugins
            .iter()
            .map(|plugin| plugin.entries.len())
            .scan(0, |acc, len| {
                let prev = *acc;
                *acc += len;
                Some(prev)
            })
            .take_while(|&total| total < self.active_entry_index)
            .last()
            .unwrap_or(0);

        self.active_entry_index = accumulated_entries;
    }

    fn register_plugin(&mut self, plugin: crate::model::Plugin) {
        drop(
            plugin
                .app_channel_out
                .send(crate::model::PluginRequest::Search(self.query.clone())),
        );
        self.plugins.push(plugin);
        self.plugins.sort_by(|a, b| b.priority.cmp(&a.priority));
    }

    fn update_entries(&mut self, plugin_id: String, entries: Vec<crate::model::Entry>) {
        let plugin = self
            .plugins
            .iter_mut()
            .find(|plugin| plugin.id == plugin_id);
        if plugin.is_none() {
            println!(
                "Appending entry failed. Could not find plugin with id {:?}",
                plugin_id
            );
            return;
        }

        let plugin = plugin.unwrap();
        plugin.entries = entries;
    }

    fn activate_selected_entry(&mut self) -> Option<()> {
        let active_entry_id = self.active_entry_id()?.clone();

        let entry = self
            .entries()
            .into_iter()
            .find(|entry| entry.id == *active_entry_id)?
            .clone();

        let plugin = self.plugins.iter_mut().find(|plugin| {
            plugin
                .entries
                .iter()
                .any(|entry| entry.id == *active_entry_id)
        })?;

        plugin
            .app_channel_out
            .send_blocking(model::PluginRequest::Activate(entry))
            .ok()
    }

    fn set_fonts(&mut self, ctx: &egui::Context) {
        let mut fonts = egui::FontDefinitions::default();
        fonts.font_data.insert(
            "NerdFontSymbols".to_owned(),
            std::sync::Arc::new(egui::FontData::from_static(include_bytes!(
                "../assets/SymbolsNerdFontMono-Regular.ttf"
            ))),
        );
        fonts
            .families
            .entry(egui::FontFamily::Monospace)
            .or_default()
            .push("NerdFontSymbols".to_owned());
        ctx.set_fonts(fonts);
    }

    fn set_theme(&mut self, ctx: &egui::Context) {
        let text_styles: std::collections::BTreeMap<_, _> = [
            (
                egui::TextStyle::Heading,
                egui::FontId::new(0.75 * crate::REM, egui::FontFamily::Monospace),
            ),
            (
                egui::TextStyle::Body,
                egui::FontId::new(1. * crate::REM, egui::FontFamily::Monospace),
            ),
        ]
        .into();
        ctx.all_styles_mut(move |style| style.text_styles = text_styles.clone());

        let settings = settings::Settings::get_or_init();
        ctx.set_visuals_of(
            egui::Theme::Dark,
            egui::Visuals {
                override_text_color: Some(settings::hexcolor(&settings.color.text)),
                ..Default::default()
            },
        );
    }

    fn update(&mut self, ctx: &egui::Context) {
        self.set_fonts(ctx);
        self.set_theme(ctx);
        self.handle_input(ctx);

        let mut messages = vec![];

        for plugin_channel in &self.plugin_channels {
            let message_result = plugin_channel.try_recv();
            if message_result.is_err() {
                continue;
            }
            messages.push(message_result.unwrap());
        }

        self.handle_messages(messages);

        let settings = settings::Settings::get_or_init();

        egui::CentralPanel::default()
            .frame(egui::Frame::new())
            .show(ctx, |ui| {
                egui::Frame::new()
                    .inner_margin(egui::epaint::MarginF32 {
                        bottom: 1. * crate::REM,
                        ..Default::default()
                    })
                    .corner_radius(0.5 * crate::REM)
                    .fill(settings::hexcolor(&settings.color.background))
                    .show(ui, |ui| {
                        let response = component::query_input::view(ui, &mut self.query);
                        response.request_focus();
                        if response.changed() {
                            self.search();
                        }

                        let entries = self.entries();
                        if !entries.is_empty() {
                            ui.add(Separator::default().spacing(0.));
                        }

                        let mut divider_added = true;
                        let mut header_added = false;
                        let mut next_entry_index_to_add = self.active_entry_index;
                        let mut lines_added = 0;

                        while ui.available_height() > 0. {
                            if next_entry_index_to_add >= entries.len() {
                                break;
                            }

                            let mut plugin_to_add = None;
                            let mut last_plugin_start_index = 0;
                            for plugin in self.plugins.iter() {
                                if last_plugin_start_index == next_entry_index_to_add {
                                    plugin_to_add = Some(plugin);
                                }
                                last_plugin_start_index += plugin.entries.len();
                            }

                            if !divider_added && plugin_to_add.is_some() {
                                ui.separator();
                                divider_added = true;
                                lines_added += 1;
                                continue;
                            }

                            if !header_added && let Some(plugin) = plugin_to_add {
                                component::plugin_header::view(ui, plugin);
                                header_added = true;
                                lines_added += 1;
                                continue;
                            } else if lines_added == 0 {
                                component::entry::view(
                                    ui,
                                    entries[next_entry_index_to_add - 1],
                                    false,
                                );
                            }

                            component::entry::view(
                                ui,
                                entries[next_entry_index_to_add],
                                next_entry_index_to_add == self.active_entry_index,
                            );
                            divider_added = false;
                            header_added = false;
                            next_entry_index_to_add += 1;
                            lines_added += 1;
                        }
                    });
            });
    }
}

impl Centerpiece {}

pub const REM: f32 = 14.0;
pub const ENTRY_HEIGHT: f32 = 2.3 * crate::REM;
