#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

use std::collections::HashMap;
use std::process::Child;
use std::str::FromStr;
use std::{path::PathBuf, process::Command};

use indexmap::IndexSet;
use known_folders::{get_known_folder_path, KnownFolder};
use notan::egui::Sense;
use notan::log::warn;
use notan::{
    draw::DrawConfig,
    egui::{self, EguiConfig, EguiPluginSugar, Label},
    extra::FpsLimit,
    prelude::*,
};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Hash, PartialEq, Eq)]
struct LogFile {
    name: String,
    path: PathBuf,
}

#[derive(AppState, Serialize, Deserialize)]
struct State {
    log_history: IndexSet<LogFile>,
    #[serde(skip)]
    log_viewer_path: PathBuf,
    #[serde(skip)]
    open_files: HashMap<PathBuf, Child>,
}

impl State {
    fn save(&mut self) {
        let folder = get_known_folder_path(KnownFolder::LocalAppData)
            .unwrap()
            .join("workspace")
            .join("log_history");
        std::fs::create_dir_all(&folder).unwrap();
        std::fs::write(folder.join("data.rsn"), rsn::to_string(self)).unwrap();
    }
}

fn main() -> Result<(), String> {
    let win = WindowConfig::new()
        .set_vsync(false)
        .set_title("Log History")
        .set_resizable(true)
        .set_lazy_loop(true)
        .set_transparent(true)
        .set_high_dpi(true);

    notan::init_with(init)
        .add_config(win)
        .add_config(EguiConfig)
        .add_config(DrawConfig)
        .add_plugin(FpsLimit::new(60))
        .update(update)
        .event(event)
        .draw(draw)
        .build()
}

fn update(_app: &mut App, _state: &mut State) {}

fn event(_app: &mut App, _assets: &mut Assets, state: &mut State, evt: Event) {
    match evt {
        Event::Exit => {
            // for (_, mut child) in state.open_files.drain() {
            //     if let Ok(Some(_)) = child.try_wait() {
            //         continue;
            //     }
            //     _ = child.kill();
            // }
        }
        Event::Drop(file) => {
            if file.name.ends_with(".log") {
                state.log_history.shift_insert(
                    0,
                    LogFile {
                        name: file.name.clone(),
                        path: file
                            .path
                            .unwrap_or_else(|| PathBuf::from_str(&file.name).unwrap()),
                    },
                );
                state.save();
            }
        }
        _ => {}
    }
}

fn draw(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins, state: &mut State) {
    let mut output = plugins.egui(|ctx| {
        egui::SidePanel::left("side_panel")
            .exact_width(app.window().width() as f32)
            .show(ctx, |ui| {
                ui.heading("Log History");
                ui.separator();
                egui::ScrollArea::vertical().show(ui, |ui| {
                    let mut to_swap = None;
                    let mut to_delete = None;
                    let history_len = state.log_history.len();
                    for (i, log) in state.log_history.iter().enumerate() {
                        let path_text = log.path.to_string_lossy().to_string();
                        let label = ui
                            .add(Label::new(&log.name).sense(Sense::click()))
                            .on_hover_text(&path_text);
                        label.context_menu(|ui| {
                            if ui.button("Open in Notepad").clicked() {
                                _ = Command::new("notepad.exe").arg(&path_text).spawn();
                                ui.close_menu();
                            }
                            if ui.button("Remove").clicked() {
                                to_delete = Some(i);
                                ui.close_menu();
                            }
                        });
                        if label.clicked() {
                            if let Some(child) = state.open_files.get_mut(&log.path) {
                                if let Ok(Some(_)) = child.try_wait() {
                                    *child = Command::new(&state.log_viewer_path)
                                        .arg(&path_text)
                                        .spawn()
                                        .unwrap();
                                }
                            } else {
                                state.open_files.insert(
                                    log.path.clone(),
                                    Command::new(&state.log_viewer_path)
                                        .arg(&path_text)
                                        .spawn()
                                        .unwrap(),
                                );
                            }
                            to_swap = Some(i);
                        }
                        if i < history_len - 1 {
                            ui.separator();
                        }
                    }
                    if let Some(i) = to_swap {
                        if let Some(value) = state.log_history.swap_remove_index(i) {
                            state.log_history.shift_insert(0, value);
                        }
                        state.save();
                    }
                    if let Some(i) = to_delete {
                        _ = state.log_history.shift_remove_index(i);
                        state.save();
                    }
                })
            });
    });

    output.clear_color(Color::BLACK);
    gfx.render(&output);
}

fn init(_gfx: &mut Graphics) -> State {
    let log_viewer_path = std::env::current_exe()
        .ok()
        .and_then(|exe| exe.parent().map(|p| p.to_path_buf()))
        .map(|exe| exe.join("fast_log_viewer.exe"))
        .unwrap_or_else(|| PathBuf::from("fast_log_viewer.exe"));
    let data = known_folders::get_known_folder_path(known_folders::KnownFolder::LocalAppData)
        .unwrap()
        .join("workspace")
        .join("log_history")
        .join("data.rsn");

    let mut state = None;
    if data.exists() {
        match std::fs::read_to_string(data) {
            Ok(data) => {
                state = Some(rsn::from_str(&data).unwrap());
            }
            Err(err) => {
                warn!("Couldn't read data file: {}", err);
            }
        }
    }

    let mut state = state.unwrap_or_else(|| State {
        log_history: IndexSet::new(),
        log_viewer_path: PathBuf::new(),
        open_files: HashMap::new(),
    });
    state.log_viewer_path = log_viewer_path;
    state
}
