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

use std::str::FromStr;
use std::{path::PathBuf, time::Duration};

use indexmap::IndexSet;
use rocket::futures::StreamExt;
use serde::{Deserialize, Serialize};
use tao::{
    event::{Event, WindowEvent},
    event_loop::{ControlFlow, EventLoop},
    window::WindowBuilder,
};
use tokio::process::{Child, Command};
use tray_icon::{
    menu::{Menu, MenuEvent, MenuEventReceiver, MenuItem, PredefinedMenuItem},
    MouseButton, TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconEventReceiver,
};

#[derive(Serialize, Deserialize, Debug)]
enum WorkspaceClientMessage {
    AddLogEntry(LogFile),
    GetLogHistory,
}

#[derive(Serialize, Deserialize, Debug)]
enum WorkspaceServerMessage {
    LogHistory(IndexSet<LogFile>),
}

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

#[derive(Serialize, Deserialize, Debug)]
struct State {
    log_history: IndexSet<LogFile>,
}

fn main() -> anyhow::Result<()> {
    let log_history_exe = std::env::current_exe()
        .ok()
        .and_then(|exe| exe.parent().map(|p| p.to_path_buf()))
        .map(|exe| exe.join("log_history.exe"))
        .unwrap_or_else(|| PathBuf::from("log_history.exe"));

    let mut nats_server_exe = std::env::current_exe()
        .ok()
        .and_then(|exe| exe.parent().map(|p| p.to_path_buf()))
        .map(|exe| exe.join("nats-server.exe"))
        .unwrap_or_else(|| PathBuf::from("nats-server.exe"));

    if !nats_server_exe.exists() {
        nats_server_exe = PathBuf::from("nats-server.exe");
    }

    if !log_history_exe.exists() {
        panic!("Could not find exe: {:#?}", log_history_exe);
    }

    let mut nats_server = Command::new(nats_server_exe)
        .args(["--addr=127.0.0.1", "--port=42666"])
        .spawn()
        .unwrap();

    let rt = tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap();

    let event_loop = EventLoop::new();

    let mut window = Some(
        WindowBuilder::new()
            .with_title("A fantastic window!")
            .with_visible(false)
            .build(&event_loop)
            .unwrap(),
    );

    let log_history_i = MenuItem::new("Logs", true, None);
    let quit_i = MenuItem::new("Quit", true, None);

    let (client_tx, client_rx) = flume::bounded(1024);

    rt.spawn(async move {
        let nats = async_nats::connect("nats://localhost:42666").await.unwrap();
        let mut sub = nats.subscribe("workspace.>").await.unwrap();
        while let Some(msg) = sub.next().await {
            client_tx.send_async(msg).await.unwrap();
        }
    });

    let (server_tx, server_rx) = flume::bounded::<(&str, Vec<u8>)>(1024);
    rt.spawn(async move {
        let nats = async_nats::connect("nats://localhost:42666").await.unwrap();
        while let Ok((subject, msg)) = server_rx.recv_async().await {
            nats.publish(subject, msg.into()).await.unwrap();
        }
    });

    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(),
    });
    let mut tray_icon = None;
    let mut log_history_process: Option<Child> = None;
    event_loop.run(move |event, _, control_flow| {
        *control_flow = ControlFlow::WaitUntil(
            std::time::Instant::now() + std::time::Duration::from_millis(16),
        );

        if let tao::event::Event::NewEvents(tao::event::StartCause::Init) = event {
            let tray_menu = Menu::new();

            tray_menu
                .append_items(&[&log_history_i, &PredefinedMenuItem::separator(), &quit_i])
                .unwrap();
            let icon = load_icon(include_bytes!("../assets/icon.png"));

            tray_icon = Some(
                TrayIconBuilder::new()
                    .with_menu_on_left_click(false)
                    .with_menu(Box::new(tray_menu.clone()))
                    .with_tooltip("Workspace")
                    .with_icon(icon)
                    .build()
                    .unwrap(),
            );

            // We have to request a redraw here to have the icon actually show up.
            // Tao only exposes a redraw method on the Window so we use core-foundation directly.
            #[cfg(target_os = "macos")]
            unsafe {
                use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};

                let rl = CFRunLoopGetMain();
                CFRunLoopWakeUp(rl);
            }
        }

        if let Ok(msg) = client_rx.try_recv() {
            match bincode::deserialize::<WorkspaceClientMessage>(&msg.payload) {
                Ok(msg) => match msg {
                    WorkspaceClientMessage::AddLogEntry(log_file) => {
                        state.log_history.shift_insert(0, log_file);
                        server_tx
                            .send((
                                "workspace.log_history",
                                bincode::serialize(&state.log_history).unwrap(),
                            ))
                            .unwrap();
                    }
                    WorkspaceClientMessage::GetLogHistory => server_tx
                        .send((
                            "workspace.log_history",
                            bincode::serialize(&state.log_history).unwrap(),
                        ))
                        .unwrap(),
                },
                Err(e) => eprintln!("{}", e),
            }
            // msg.payload
        }

        match event {
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                window_id: _,
                ..
            } => {
                // drop the window to fire the `Destroyed` event
                window = None;
            }
            Event::WindowEvent {
                event: WindowEvent::Destroyed,
                window_id: _,
                ..
            } => {
                _ = nats_server.kill();
                *control_flow = ControlFlow::Exit;
                _ = tray_icon.take();
            }
            Event::MainEventsCleared => {
                if let Some(w) = &window {
                    w.request_redraw();
                }
            }
            _ => (),
        }
        if let Ok(event) = MenuEvent::receiver().try_recv() {
            if event.id == quit_i.id() {
                window = None;
            }
            if event.id == log_history_i.id() {
                match &mut log_history_process {
                    Some(log_history) => {
                        if let Ok(Some(_)) = log_history.try_wait() {
                            *log_history = Command::new(&log_history_exe).spawn().unwrap();
                        }
                    }
                    None => {
                        log_history_process = Some(Command::new(&log_history_exe).spawn().unwrap());
                    }
                }
            }
        }
    });
}

fn load_icon(img: &[u8]) -> tray_icon::Icon {
    let (icon_rgba, icon_width, icon_height) = {
        let image = image::load_from_memory(img)
            .expect("Failed to open icon path")
            .into_rgba8();
        let (width, height) = image.dimensions();
        let rgba = image.into_raw();
        (rgba, width, height)
    };
    tray_icon::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
}
