use std::borrow::BorrowMut;
use std::env::args;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::exit;
use std::sync::{Arc, Mutex};
use std::thread::{self, sleep};
use std::time::Duration;

use config_traits::{StdConfig, StdConfigLoad1};
use dmi_id::DMIID;
use gumdrop::Options;
use log::{info, LevelFilter};
use rog_control_center::cli_options::CliStart;
use rog_control_center::config::Config;
use rog_control_center::error::Result;
use rog_control_center::notify::start_notifications;
use rog_control_center::slint::ComponentHandle;
use rog_control_center::tray::init_tray;
use rog_control_center::ui::setup_window;
use rog_control_center::{
    get_ipc_file, on_tmp_dir_exists, print_versions, MainWindow, QUIT_APP, SHOWING_GUI, SHOW_GUI,
};
use tokio::runtime::Runtime;

#[tokio::main]
async fn main() -> Result<()> {
    let self_version = env!("CARGO_PKG_VERSION");
    let conn = zbus::blocking::Connection::system()?;
    let proxy = rog_dbus::zbus_platform::PlatformProxyBlocking::new(&conn)?;
    let asusd_version = proxy.version().unwrap();
    if asusd_version != self_version {
        println!("Version mismatch: asusctl = {self_version}, asusd = {asusd_version}");
        return Ok(());
    }

    let dmi = DMIID::new().unwrap_or_default();
    let board_name = dmi.board_name;
    let prod_family = dmi.product_family;
    info!("Running on {board_name}, product: {prod_family}");
    let is_rog_ally = prod_family == "RC71L";

    // tmp-dir must live to the end of program life
    let _tmp_dir = match tempfile::Builder::new()
        .prefix("rog-gui")
        .rand_bytes(0)
        .tempdir()
    {
        Ok(tmp) => tmp,
        Err(_) => on_tmp_dir_exists().unwrap(),
    };

    let args: Vec<String> = args().skip(1).collect();

    let cli_parsed = match CliStart::parse_args_default(&args) {
        Ok(p) => p,
        Err(err) => {
            panic!("source {}", err);
        }
    };

    if do_cli_help(&cli_parsed) {
        return Ok(());
    }

    let mut logger = env_logger::Builder::new();
    logger
        .filter_level(LevelFilter::Warn)
        .parse_default_env()
        .target(env_logger::Target::Stdout)
        .format_timestamp(None)
        .init();

    let supported_properties = match proxy.supported_properties() {
        Ok(s) => s,
        Err(_e) => {
            // TODO: show an error window
            Vec::default()
        }
    };

    // Startup
    let mut config = Config::new().load();
    if cli_parsed.fullscreen {
        config.start_fullscreen = true;
        if cli_parsed.width_fullscreen != 0 {
            config.fullscreen_width = cli_parsed.width_fullscreen;
        }
        if cli_parsed.height_fullscreen != 0 {
            config.fullscreen_height = cli_parsed.height_fullscreen;
        }
    } else if cli_parsed.windowed {
        config.start_fullscreen = false;
    }

    if is_rog_ally {
        config.notifications.enabled = false;
        config.enable_tray_icon = false;
        config.run_in_background = false;
        config.startup_in_background = false;
    }

    if config.startup_in_background {
        config.run_in_background = true;
    } else {
        get_ipc_file().unwrap().write_all(&[SHOW_GUI, 0]).unwrap();
    }
    config.write();

    let enable_tray_icon = config.enable_tray_icon;
    let startup_in_background = config.startup_in_background;
    let config = Arc::new(Mutex::new(config));

    // start tokio
    let rt = Runtime::new().expect("Unable to create Runtime");
    // Enter the runtime so that `tokio::spawn` is available immediately.
    let _enter = rt.enter();
    start_notifications(config.clone(), &rt)?;

    if enable_tray_icon {
        init_tray(supported_properties, config.clone());
    }

    thread_local! { pub static UI: std::cell::RefCell<Option<MainWindow>> = Default::default()};
    // i_slint_backend_selector::with_platform(|_| Ok(())).unwrap();

    let mut do_once = !startup_in_background;

    if std::env::var("RUST_TRANSLATIONS").is_ok() {
        // don't care about content
        log::debug!("---- Using local-dir translations");
        slint::init_translations!("/usr/share/locale/");
    } else {
        log::debug!("Using system installed translations");
        slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/translations/"));
    }

    thread::spawn(move || {
        let mut buf = [0u8; 2];
        // blocks until it is read, typically the read will happen after a second
        // process writes to the IPC (so there is data to actually read)
        loop {
            if do_once {
                buf[0] = SHOW_GUI;
                do_once = false;
            } else {
                get_ipc_file().unwrap().read_exact(&mut buf).unwrap();
            }

            if buf[0] == SHOW_GUI {
                // There's a balancing act with read/write timing of IPC, there needs to be a
                // small sleep after this to give any other process a chance to
                // read the IPC before looping
                get_ipc_file()
                    .unwrap()
                    .write_all(&[SHOWING_GUI, 0])
                    .unwrap();
                sleep(Duration::from_millis(50));

                let config_copy = config.clone();
                slint::invoke_from_event_loop(move || {
                    UI.with(|ui| {
                        let mut ui = ui.borrow_mut();
                        if let Some(ui) = ui.as_mut() {
                            ui.window().show().unwrap();
                            ui.window().on_close_requested(|| {
                                get_ipc_file().unwrap().write_all(&[0, 0]).unwrap();
                                slint::CloseRequestResponse::HideWindow
                            });
                        } else {
                            let newui = setup_window(config_copy);
                            newui.window().show().unwrap();
                            newui.window().on_close_requested(|| {
                                get_ipc_file().unwrap().write_all(&[0, 0]).unwrap();
                                slint::CloseRequestResponse::HideWindow
                            });
                            ui.replace(newui);
                        }
                    });
                })
                .unwrap();
            } else {
                if buf[1] == QUIT_APP {
                    slint::quit_event_loop().unwrap();
                    exit(0);
                }
                if buf[0] != SHOWING_GUI {
                    if let Ok(lock) = config.lock() {
                        if !lock.run_in_background {
                            slint::quit_event_loop().unwrap();
                            exit(0);
                        }
                    }

                    slint::invoke_from_event_loop(move || {
                        UI.with(|ui| {
                            let mut ui = ui.take();
                            if let Some(ui) = ui.borrow_mut() {
                                ui.window().hide().unwrap();
                            }
                        });
                    })
                    .unwrap();
                }
            }
        }
    });

    slint::run_event_loop_until_quit().unwrap();
    rt.shutdown_background();
    Ok(())
}

// /// Bah.. the icon dosn't work on wayland anyway, but we'll leave it in for
// now. fn load_icon() -> IconData {
//     let path = PathBuf::from(APP_ICON_PATH);
//     let mut rgba = Vec::new();
//     let mut height = 512;
//     let mut width = 512;
//     if path.exists() {
//         if let Ok(data) = std::fs::read(path)
//             .map_err(|e| error!("Error reading app icon: {e:?}"))
//             .map_err(|e| error!("Error opening app icon: {e:?}"))
//         {
//             let data = std::io::Cursor::new(data);
//             let decoder = png_pong::Decoder::new(data).unwrap().into_steps();
//             let png_pong::Step { raster, delay: _ } =
// decoder.last().unwrap().unwrap();

//             if let png_pong::PngRaster::Rgba8(ras) = raster {
//                 rgba = ras.as_u8_slice().to_vec();
//                 width = ras.width();
//                 height = ras.height();
//                 info!("Loaded app icon. Not actually supported in Wayland
// yet");             }
//         }
//     } else {
//         error!("Missing {APP_ICON_PATH}");
//     }

//     IconData {
//         height,
//         width,
//         rgba
//
//
// /     }
// }

fn do_cli_help(parsed: &CliStart) -> bool {
    if parsed.help {
        println!("{}", CliStart::usage());
        println!();
        if let Some(cmdlist) = CliStart::command_list() {
            let commands: Vec<String> = cmdlist.lines().map(|s| s.to_owned()).collect();
            for command in &commands {
                println!("{}", command);
            }
        }
    }

    if parsed.version {
        print_versions();
        println!();
    }

    parsed.help
}

pub fn get_layout_path(path: &Path, layout_name: &str) -> PathBuf {
    let mut data_path = PathBuf::from(path);
    let layout_file = format!("{}_US.ron", layout_name);
    data_path.push("layouts");
    data_path.push(layout_file);
    data_path
}
