#![feature(yggdrasil_os, rustc_private)]
use std::{
    os::fd::AsRawFd,
    process::ExitCode,
    sync::{
        atomic::{AtomicU32, AtomicUsize, Ordering},
        Arc,
    },
    time::Duration,
};

use chrono::{DateTime, Timelike};
use cross::io::{Poll, TimerFd};
use libcolors::{
    application::{window::Window, Application},
    error::Error,
    event::WindowManagementEvent,
    message::{CreateWindowInfo, WindowType},
    surface::Surface,
};
use libpsf::PcScreenFont;
use runtime::rt::time::get_real_time;

static WORKSPACE_ACTIVITY: AtomicU32 = AtomicU32::new(0);
static WORKSPACE_MAX: AtomicUsize = AtomicUsize::new(1);
static CURRENT_WORKSPACE: AtomicUsize = AtomicUsize::new(0);

const WHITE: u32 = 0xFFFFFFFF;
const BLACK: u32 = 0xFF000000;

#[allow(clippy::too_many_arguments)]
fn draw_string<S: Surface>(
    surface: &mut S,
    font: &PcScreenFont,
    x: u32,
    y: u32,
    text: &str,
    fg: u32,
    bg: u32,
) {
    let y = y as usize;

    for (i, ch) in text.chars().enumerate() {
        let x = x as usize + i * font.width() as usize;
        font.map_glyph_pixels(ch as u32, |px, py, set| {
            let color = if set { fg } else { bg };
            surface.set_pixel((x + px) as u32, (y + py) as u32, color);
        });
    }
}

fn string_width(font: &PcScreenFont, text: &str) -> u32 {
    (font.width() as usize * text.len()) as u32
}

fn redraw<S: Surface>(surface: &mut S, font: &PcScreenFont) {
    const TAG_SPACING: usize = 4;
    let width = surface.width();

    surface.fill(0);

    let now = get_real_time().unwrap();

    let current = CURRENT_WORKSPACE.load(Ordering::Relaxed);
    let activity = WORKSPACE_ACTIVITY.load(Ordering::Relaxed);
    let max = WORKSPACE_MAX.load(Ordering::Relaxed);
    let mut x = 0;
    for index in 0..max {
        let exists = activity & (1 << index) != 0;
        let active = current == index;
        if !exists && !active {
            continue;
        }
        let text = format!("{}", index + 1);
        let (fg, bg) = if active {
            (BLACK, WHITE)
        } else {
            (WHITE, BLACK)
        };
        draw_string(surface, font, 2 + x, 2, &text, fg, bg);
        x += (font.width() as usize * text.len() + TAG_SPACING) as u32;
    }

    if let Some(now) = DateTime::from_timestamp(now.seconds() as _, now.subsec_nanos() as _) {
        let (pm, hour) = now.hour12();
        let ampm = if pm { "PM" } else { "AM" };
        let text = format!("{}:{:02} {ampm}", hour, now.minute());
        let tw = string_width(font, &text);
        draw_string(surface, font, width - tw - 4, 2, &text, WHITE, BLACK);
    }
}

fn run() -> Result<ExitCode, Error> {
    let font = Arc::new(PcScreenFont::default());
    let mut poll = Poll::new()?;
    let mut timer = TimerFd::new()?;
    let mut app = Application::new()?;
    app.subscribe_to_window_management_events()?;
    let mut window = Window::new_with_info(
        &app,
        CreateWindowInfo {
            ty: WindowType::Reservation(24),
        },
    )?;

    window.set_on_redraw_requested(move |surface| {
        redraw(surface, &font);
    });
    app.set_window_management_event_handler(|event| match event {
        WindowManagementEvent::WorkspaceChanged { new, .. } => {
            CURRENT_WORKSPACE.store(new, Ordering::Relaxed);
            true
        }
        WindowManagementEvent::WorkspaceActivity { mask, max } => {
            WORKSPACE_MAX.store(max, Ordering::Relaxed);
            WORKSPACE_ACTIVITY.store(mask, Ordering::Relaxed);
            true
        }
        _ => false,
    });

    app.add_window(window);

    // TODO signals, events in Application?
    let app_fd = app.connection().lock().unwrap().as_raw_fd();
    let timer_fd = timer.as_raw_fd();
    poll.add(&app_fd)?;
    poll.add(&timer_fd)?;

    timer.start(Duration::from_secs(1))?;

    while app.is_running() {
        let fd = poll.wait(None)?.unwrap();

        if fd == app_fd {
            app.poll_events()?;
        } else if fd == timer_fd {
            app.redraw()?;
            timer.start(Duration::from_secs(1))?;
        } else {
            log::warn!("Unexpected poll event: {fd:?}");
        }
    }

    Ok(ExitCode::SUCCESS)
}

fn main() -> ExitCode {
    logsink::setup_logging(true);
    log::info!("colors-bar starting");
    match run() {
        Ok(code) => code,
        Err(error) => {
            log::error!("colors-bar: {error}");
            ExitCode::FAILURE
        }
    }
}
