pub mod config;
pub mod geometry;
pub mod gui;
pub mod interpreter;
pub mod lexer;
pub mod machine;
pub mod parser;
pub mod source;
pub mod tui;

use crate::{gui::Gui, machine::MotionSummary, parser::Point, tui::Tui};
use std::fmt::Display;

/// Non-Zero extremes for each axis of the machine.
/// Passed to both GUI and TUI.
// const MACHINE_TRAVELS: Point = Point::new(1200.0, 600.0, 600.0);
const MACHINE_TRAVELS: Point = Point::new(500.0, 250.0, 250.0);

/// Single block execution at program start.
pub const SINGLE: bool = false;
/// Tool visibility at program start.
pub const TOOL: bool = true;
/// XY plane grid visibility at program start.
pub const GRID: bool = true;
/// Origin visibility at program start.
pub const ORIGIN: bool = true;
/// Machine boudnary visibility at program start.
pub const BOUNDARY: bool = false;

/// Represents the possible views that can be used in the [`Gui`] and controlled using [`Tui`].
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, bytemuck::Zeroable)]
pub enum View {
    /// Simuate all three axes, from **isometric view**.
    #[default]
    Isometric,
    /// Simlutate `X` & `Y` axes, from **top view**.
    Top,
}

// required for use in gui uniforms
unsafe impl bytemuck::Pod for View {}

impl Display for View {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let string = match self {
            View::Isometric => "ISOMETRIC",
            View::Top => "TOP",
        };

        write!(f, "{string}")
    }
}

/// Communicates changes from the [`Ratatui`](ratatui) loop,
/// to the [`Winit`](winit) event loop.
#[derive(Debug)]
pub enum Command {
    Render(MotionSummary),
    SetView(View),
    SetSingle(bool),
    SetTool(bool),
    SetGrid(bool),
    SetOrigin(bool),
    SetBoundary(bool),
    Clear,
    Stop(Option<anyhow::Error>),
}

/// Communicates when the [`Winit`](winit) event loop is ready to process
/// another [`Command`] from [`Ratatui`](ratatui) loop.
#[derive(Debug, Clone, Copy)]
pub enum Signal {
    Proceed,
    Stop,
}

fn display_banner() {
    println!(
        "\x1b[1;37m
 ██████╗ ███████╗██╗███╗   ███╗      ██████╗ ███████╗
██╔════╝ ██╔════╝██║████╗ ████║      ██╔══██╗██╔════╝
██║  ███╗███████╗██║██╔████╔██║█████╗██████╔╝███████╗
██║   ██║╚════██║██║██║╚██╔╝██║╚════╝██╔══██╗╚════██║
╚██████╔╝███████║██║██║ ╚═╝ ██║      ██║  ██║███████║
 ╚═════╝ ╚══════╝╚═╝╚═╝     ╚═╝      ╚═╝  ╚═╝╚══════╝
\x1b[0m"
    );
}

/// Main entry point for the program.
///
/// Sets up [`Gui`] in the **main thread**, and [`Tui`] in a **new thread**.
/// Sets up bidirectional communication between both the threads,
/// using a [`Channel`](std::sync::mpsc::channel) and an [`EventLoopProxy`](winit::event_loop::EventLoopProxy).
pub fn run() -> anyhow::Result<()> {
    display_banner();
    let (sender, receiver) = std::sync::mpsc::channel();

    let gui = Gui::build(sender, MACHINE_TRAVELS)?;
    let tui = Tui::build(receiver, MACHINE_TRAVELS, gui.create_proxy())?;

    let tui = std::thread::Builder::new()
        .name("TUI".to_string())
        .spawn(move || tui.run())?;

    // any errors from the tui thread will be returned through this call
    let res = gui.run();

    tui.join().unwrap();

    res
}
