
#![feature(lang_items, const_fn, asm)]
#![no_std]

#[cfg(test)]
#[macro_use]
extern crate std;

extern crate rlibc;
extern crate multiboot2;

/* externs */
#[no_mangle]
pub extern fn rust_setup_PIC() {
    PIC::setup_PIC();
}

#[no_mangle]
pub extern fn rust_handle_keyboard(x: u8) {
    use vga_buffer::{checkerboard, print_u32, Color};
    checkerboard(Color::Green);
    print_u32(x as u32, 0);
}


#[no_mangle]
pub extern fn rust_interrupt_handler() {
    use vga_buffer::{checkerboard, Color};
    checkerboard(Color::White);
}

static mut global_timer_count: u64 = 0;

#[no_mangle]
pub extern fn rust_handle_timer() {
    let gtc = unsafe { global_timer_count };
    unsafe {
        global_timer_count += 1;
    }

    //timer_callback(gtc);
    //timer_callback2(gtc);
}

#[no_mangle]
pub extern fn rust_setup_PIT() {
    PIT::configure_PIT(PIT::MILLISECOND_CONSTANT);
}

mod PIT {
    use x86_asm::{outb, inb, iowait};

    const PIT_CH0: u16      = 0x40;
    const PIT_MODE_CMD: u16 = 0x43;

    //channel 0, low-byte/hi-byte, rate generator
    const TIMER_CONFIG_SPECIFIER: u8 = 0b00_11_010_0;

    //generate ticks at about 1000.15 Hz
    pub const MILLISECOND_CONSTANT: u16 = 1193;

    pub fn configure_PIT(timer_divider: u16) {

        let (low_bits, high_bits) = ::util::lo_hi_bits(timer_divider);

        unsafe {
            outb(PIT_MODE_CMD, TIMER_CONFIG_SPECIFIER);
            outb(PIT_CH0, low_bits);
            outb(PIT_CH0, high_bits);
        }
    }
}

mod PIC {
    use x86_asm::{outb, inb, iowait};

    const PIC1_COMMAND: u16 = 0x20;
    const PIC2_COMMAND: u16 = 0xa0;
    const PIC1_DATA: u16 = 0x21;
    const PIC2_DATA: u16 = 0xa1;

    const INITIALIZE: u8 = 0x11;
    const ENVIRONMENT: u8 = 0x01; //I don't really know what this does
    pub fn setup_PIC() {

        let interrupt_mask1: u8 = 0xfc;
        let interrupt_mask2: u8 = 0xff;

        unsafe {
            asm!("cli");

            outb(PIC1_COMMAND, INITIALIZE);
            iowait();
            outb(PIC2_COMMAND, INITIALIZE);
            iowait();

            // change IRQ offsets to int 32-47
            outb(PIC1_DATA, 32);
            iowait();
            outb(PIC2_DATA, 40);
            iowait();

            outb(PIC1_DATA, 0x4); //master: slave IRQ location (IRQ 2)
            iowait();
            outb(PIC2_DATA, 0x2); //slave: cascade identity
            iowait();

            outb(PIC1_DATA, ENVIRONMENT);
            iowait();
            outb(PIC2_DATA, ENVIRONMENT);
            iowait();

            outb(PIC1_DATA, interrupt_mask1);
            iowait();
            outb(PIC2_DATA, interrupt_mask2);
            iowait();

            asm!("sti");
        }
    }
}

mod x86_asm {

    pub unsafe fn iowait() {
        outb(0x80, 0x0);
    }

    pub unsafe fn outb(port: u16, value: u8) {
        asm!("outb %al, %dx" : : "{dx}" (port), "{al}"(value));
    }

    pub unsafe fn inb(port: u16) -> u8{
        let inval: u8;
        asm!("inb %dx, %al" : "={al}"(inval) : "{dx}"(port));
        return inval;
    }
}

fn timer_callback2(count: u64) {
    use vga_buffer::*;

    let color = Color::LightCyan;

    if count % 500 == 0 {
        checkerboard(color);
    } else if count % 500 == 250 {
        alt_checkerboard(color);
    }

}

fn timer_callback(count: u64) {
    use vga_buffer::{Color, print_u32, checkerboard, clear, move_cursor};

    if count < 10 {
        clear();
    }

    if count < 3000 {

        move_cursor((count % 80) as u8, 2);

        print_u32(count as u32, 20);

    } else {
        if count % 1000  == 0 {
            checkerboard(Color::White);
        }
        if count % 1000 == 500 {
            checkerboard(Color::LightCyan);
        }
        move_cursor((count % 80) as u8, 2);
    }

    print_u32(count as u32, 0);
}

#[no_mangle]
pub extern fn rust_main(multiboot_info_header: usize) {
    use vga_buffer::{clear, checkerboard, Color};
    clear();

    checkerboard(Color::Red);
    //let boot_info = unsafe { multiboot2::load(multiboot_info_header) };
    printstr::write_to_screen(6, "Gamarjoba munde");
    util::spin_wait(100_000_000);
    printstr::write_to_screen(2, "HELLA");

    loop {

    }
}

mod util {

    pub fn spin_wait(count: u32) {
        let mut i: u32 = 0;
        loop {
            i += 1;
            if i > count {
                break;
            }
        }
    }

    pub fn lo_hi_bits(n: u16) -> (u8, u8) {
        ( (n & 0xff) as u8, (n >> 8) as u8)
    }

    pub fn u32_to_chars(n: u32) -> [u8; 10] {
        let mut accum = [0; 10];
        let mut i = 0;
        let mut val = n;
        loop {
            if val < 10 {
                let ch = b'0' + (val as u8);
                accum[i] = ch;
                break;
            } else {
                let ch = b'0' + ((val % 10) as u8);
                val = val / 10;
                accum[i] = ch;
                i += 1;
            }
        }
        let mut output = [0; 10];
        let mut j = 0;
        loop {
            output[j] = accum[i];
            j += 1;
            if i == 0 { break; }
            i -= 1;
        }

        output
    }

    #[test]
    fn test_u32_to_chars() {
        assert_eq!(u32_to_chars(12345), [b'1', b'2', b'3', b'4', b'5', 0, 0, 0, 0, 0]);
        assert_eq!(u32_to_chars(9), [b'9', 0, 0, 0, 0, 0, 0, 0, 0, 0]);
    }
}

mod printstr {
    use vga_buffer::{Color, ColorCode, write_to_coord};

    const std_code: ColorCode = ColorCode::new(Color::LightBlue, Color::Black);

    pub fn write_to_screen(line_num: u8, s: &str) {

        let mut x_position: usize = 0;
        let mut y_position: usize = line_num as usize;

        for ch in s.bytes() {

            if x_position > ::vga_buffer::BUFFER_WIDTH {
                x_position = 0;
                y_position += 1;
            }

            if y_position > ::vga_buffer::BUFFER_HEIGHT {
                return;
            }

            match ch {
                b'\n' => { y_position += 1; x_position = 0; },
                byte => {
                    write_to_coord(x_position, y_position, byte, std_code);
                    x_position += 1;
                }
            }
        }
    }

}

mod vga_buffer {
    #[repr(u8)]
    #[derive(Clone, Copy)]
    pub enum Color {
        Black      = 0,
        Blue       = 1,
        Green      = 2,
        Cyan       = 3,
        Red        = 4,
        Magenta    = 5,
        Brown      = 6,
        LightGray  = 7,
        DarkGray   = 8,
        LightBlue  = 9,
        LightGreen = 10,
        LightCyan  = 11,
        LightRed   = 12,
        Pink       = 13,
        Yellow     = 14,
        White      = 15,
    }

    #[derive(Clone, Copy)]
    pub struct ColorCode(u8);
    impl ColorCode {
        pub const fn new(foreground: Color, background: Color) -> ColorCode {
            ColorCode((background as u8) << 4 | (foreground as u8))
        }
    }

    pub fn clear() {
        let blank_color = ColorCode::new( Color::White, Color::Black);
        for i in 0..BUFFER_WIDTH {
            for j in 0..BUFFER_HEIGHT {
                write_to_coord(i, j, b' ', blank_color);
            }
        }
    }

    pub fn checkerboard(color: Color) {
        checkerboard_logic(color, false);
    }
    pub fn alt_checkerboard(color: Color) {
        checkerboard_logic(color, true);
    }

    fn checkerboard_logic(color: Color, alternate: bool) {
        let active = ColorCode::new(color, color);
        let blank = ColorCode::new(Color::White, Color::Black);
        for i in 0..BUFFER_WIDTH {
            for j in 0..BUFFER_HEIGHT {
                if (i + j) % 2 == 0 {
                    write_to_coord(i, j, b' ', if alternate { active } else { blank } );
                } else {
                    write_to_coord(i, j, b' ', if alternate { blank } else { active });
                }
            }
        }
    }

    #[repr(C)]
    struct ScreenChar {
        ascii_char: u8,
        color_code: ColorCode
    }

    pub const BUFFER_HEIGHT: usize = 25;
    pub const BUFFER_WIDTH: usize = 80;
    const BUFFER_PTR: usize = 0xb8000;

    pub fn charloop() {
        for ch in 32..255 {
            let ptr = BUFFER_PTR + 2*ch as usize;
            let data = ScreenChar {
                ascii_char: ch,
                color_code: ColorCode::new(Color::Black, Color::White)
            };

            unsafe {
                *(ptr as *mut _) = data;
            }
        }
    }

    pub fn print_array(array: [u8; 10], line: usize, color_code: ColorCode) {
        let mut x = 0;
        for ch in array.into_iter() {
            if *ch == 0 {
                break;
            }
            write_to_coord(x, line, *ch, color_code);
            x += 1;
        }
    }

    pub fn print_u32(num: u32, location: usize) {
        let default_color = ColorCode::new(Color::Black, Color::White);
        let char_array = ::util::u32_to_chars(num);
        print_array(char_array, location, default_color);
    }

    pub fn write_to_coord(x: usize, y: usize, character: u8, color_code: ColorCode) {
        let ptr = BUFFER_PTR + (2*x as usize) + (BUFFER_WIDTH*2*y as usize);
        let data = ScreenChar {
            ascii_char: character,
            color_code: color_code,
        };

        if x > BUFFER_WIDTH || y > BUFFER_HEIGHT {
            return;
        }

        unsafe {
            *(ptr as *mut _) = data;
        }
    }

    pub fn move_cursor(x: u8, y: u8) {
        use x86_asm::{outb};
        const CURSOR_COMMAND_PORT: u16 = 0x3d4;
        const CURSOR_DATA_PORT: u16 = 0x3d5;

        let position: u16 = (y as u16)*80 + (x as u16);
        let (low_bits, high_bits) = ::util::lo_hi_bits(position);

        unsafe {
            outb(CURSOR_COMMAND_PORT, 14);
            outb(CURSOR_DATA_PORT, low_bits);
            outb(CURSOR_COMMAND_PORT, 15);
            outb(CURSOR_DATA_PORT, high_bits);
        }
    }
}

#[cfg(not(test))]
#[lang = "eh_personality"]
#[no_mangle]
pub extern "C" fn eh_personality() {}

#[cfg(not(test))]
#[lang = "panic_fmt"]
#[no_mangle]
pub extern "C" fn panic_fmt() -> ! { loop {}}

#[cfg(not(test))]
#[lang = "eh_unwind_resume"]
#[no_mangle]
pub extern "C" fn eh_unwind_resume() { }

#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn _Unwind_Resume() -> ! {
    loop {}
}
