#![no_std]
#![no_main]

extern crate alloc;

mod allocator;

use core::panic::PanicInfo;
use pxl8_server::*;

#[global_allocator]
static ALLOCATOR: allocator::Allocator = allocator::Allocator;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

const TICK_RATE: u64 = 30;
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;

#[cfg(unix)]
fn get_time_ns() -> u64 {
    let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
    unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) };
    (ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64)
}

#[cfg(windows)]
fn get_time_ns() -> u64 {
    use windows_sys::Win32::System::Performance::*;
    static mut FREQ: i64 = 0;
    unsafe {
        if FREQ == 0 {
            QueryPerformanceFrequency(&mut FREQ);
        }
        let mut count: i64 = 0;
        QueryPerformanceCounter(&mut count);
        ((count as u128 * 1_000_000_000) / FREQ as u128) as u64
    }
}

#[cfg(unix)]
fn sleep_ms(ms: u64) {
    let ts = libc::timespec {
        tv_sec: (ms / 1000) as i64,
        tv_nsec: ((ms % 1000) * 1_000_000) as i64,
    };
    unsafe { libc::nanosleep(&ts, core::ptr::null_mut()) };
}

#[cfg(windows)]
fn sleep_ms(ms: u64) {
    use windows_sys::Win32::System::Threading::Sleep;
    unsafe { Sleep(ms as u32) };
}

fn extract_spawn_position(payload: &[u8]) -> (f32, f32, f32, f32, f32) {
    let x = f32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
    let y = f32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
    let z = f32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]);
    let yaw = f32::from_be_bytes([payload[12], payload[13], payload[14], payload[15]]);
    let pitch = f32::from_be_bytes([payload[16], payload[17], payload[18], payload[19]]);
    (x, y, z, yaw, pitch)
}

#[unsafe(no_mangle)]
pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
    let mut transport = match transport::Transport::bind(transport::DEFAULT_PORT) {
        Some(t) => t,
        None => return 1,
    };

    let mut sim = Simulation::new();
    let mut player_id: u64 = 0;
    let mut last_client_tick: u64 = 0;

    let mut sequence: u32 = 0;
    let mut last_tick = get_time_ns();
    let mut entities_buf = [protocol::pxl8_entity_state {
        entity_id: 0,
        userdata: [0u8; 56],
    }; 64];
    let mut inputs_buf: [protocol::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() };

    loop {
        let now = get_time_ns();
        let elapsed = now.saturating_sub(last_tick);

        if elapsed >= TICK_NS {
            last_tick = now;
            let dt = (elapsed as f32) / 1_000_000_000.0;

            let mut latest_input: Option<protocol::pxl8_input_msg> = None;
            while let Some(msg_type) = transport.recv() {
                match msg_type {
                    x if x == protocol::pxl8_msg_type::PXL8_MSG_INPUT as u8 => {
                        latest_input = Some(transport.get_input());
                    }
                    x if x == protocol::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => {
                        let cmd = transport.get_command();
                        if cmd.cmd_type == protocol::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 {
                            let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload);
                            let player = sim.spawn_player(x, y, z);
                            player_id = player.to_bits();
                        }
                    }
                    _ => {}
                }
            }

            if let Some(input) = latest_input {
                last_client_tick = input.tick;
                inputs_buf[0] = input;
                sim.step(&inputs_buf[..1], dt);
            } else {
                sim.step(&[], dt);
            }

            let mut count = 0;
            sim.generate_snapshot(player_id, |state| {
                if count < entities_buf.len() {
                    entities_buf[count] = *state;
                    count += 1;
                }
            });

            let header = protocol::pxl8_snapshot_header {
                entity_count: count as u16,
                event_count: 0,
                player_id,
                tick: last_client_tick,
                time: sim.time,
            };

            transport.send_snapshot(&header, &entities_buf[..count], sequence);
            sequence = sequence.wrapping_add(1);
        }

        sleep_ms(1);
    }
}
