#![deny(
    clippy::unwrap_used,
    clippy::nursery,
    clippy::perf,
    clippy::pedantic,
    clippy::complexity
)]
#![allow(
    clippy::cast_possible_truncation,
    clippy::missing_panics_doc,
    clippy::missing_errors_doc,
    clippy::match_wildcard_for_single_variants
)]

pub mod emulator;

use std::{
    sync::{
        Arc,
        mpsc::{Receiver, Sender},
    },
    thread,
};

#[cfg(target_os = "android")]
use winit::platform::android::{EventLoopBuilderExtAndroid, activity::AndroidApp};

use crate::emulator::{
    misc::rpc::RpcClient,
    system::{
        emulator::run_emulator,
        memory::MainStore,
        model::{Command, StateUpdate},
        processor::Processor,
    },
    ui::{
        control_unit::ControlPanel, display::Display, editor::Editor,
        interface::EmulatorUI, memory_inspector::MemoryInspector,
        stack_inspector::StackInspector,
    },
};

#[cfg(target_os = "android")]
#[unsafe(no_mangle)]
pub fn android_main(app: AndroidApp) -> Result<(), Box<dyn std::error::Error>> {
    use crate::emulator::{config::Config, misc::rpc::get_rpc_client_or_none};
    use std::path::Path;

    // Initialize channels and read in configuration.
    let (cmd_sender, cmd_receiver) = std::sync::mpsc::channel();
    let (state_sender, state_reciever) = std::sync::mpsc::channel();
    let config = Config::load(Path::new(".dsa.emulator.toml"))?;

    // Setup RPC if enabled.
    let (rpc_sender, rpc_reciever) = std::sync::mpsc::channel();

    let rpc_client =
        get_rpc_client_or_none(&config, rpc_sender, rpc_reciever)?.map(Arc::new);

    setup_emulator(cmd_receiver, state_sender, rpc_client);

    let ui = setup_ui(cmd_sender, state_reciever);

    // Run UI.
    #[allow(unused_variables)]
    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]),
        event_loop_builder: Some(Box::new(move |builder| {
            #[cfg(target_os = "android")]
            builder.with_android_app(app);
        })),
        ..Default::default()
    };

    eframe::run_native(
        "DSA Simulator (Damn Simple Architecture 🔥)",
        options,
        Box::new(move |cc| {
            cc.egui_ctx.set_visuals(egui::Visuals::default());
            Ok(Box::new(ui))
        }),
    )?;

    Ok(())
}

pub fn setup_emulator(
    cmd_receiver: Receiver<Command>,
    state_sender: Sender<StateUpdate>,
    rpc_client: Option<Arc<RpcClient>>,
) {
    let main_store = MainStore::new();
    let processor = Processor::new(Box::new(main_store), vec![]);

    thread::spawn(move || {
        run_emulator(&cmd_receiver, &state_sender, processor, rpc_client.as_ref());
    });
}

/// Creates the [`EmulatorUI`].
#[must_use]
pub fn setup_ui(
    cmd_sender: Sender<Command>,
    state_reciever: Receiver<StateUpdate>,
) -> EmulatorUI {
    let mut ui = EmulatorUI::new(cmd_sender, state_reciever);

    // Create UI modules.
    let control_unit = ControlPanel::new();

    ui.add_component(Box::new(control_unit));

    let mem_inspector = MemoryInspector::new();
    ui.add_component(Box::new(mem_inspector));

    let stack_inspector = StackInspector::new();
    ui.add_component(Box::new(stack_inspector));

    let editor = Editor::new();
    ui.add_component(Box::new(editor));

    let display = Display::new();
    ui.add_component(Box::new(display));

    let history = emulator::ui::history::History::new();
    ui.add_component(Box::new(history));

    let loader = emulator::ui::loader::Loader::new();
    ui.add_component(Box::new(loader));

    ui
}
