use crate::critical;
use crate::flash;
use crate::serial;
use crate::serial::SIOCNT_;
use crate::task::{self, mutex::BlockingMutex, BlockingOn, Task};
use core::fmt::Write;
use gba::prelude::*;
use gba_utils::gfx::{self, Font, GfxWriter};

type Writer = GfxWriter<gfx::Subpixel4x8>;

static mut TASK_0: Task = Task::new(task_0);
static WRITER: Mutex<Option<Writer>> = Mutex::new(None);

pub fn init() -> ! {
    {
        let mut opt_writer = WRITER.lock();
        let di = gfx::text_mode_setup(true);
        let mut writer = GfxWriter::new(0, 0, di);
        opt_writer.replace(writer);
    }

    const FLAGS: InterruptFlags = InterruptFlags::new().with_serial(true);

    serial::init(true);

    task::register(unsafe { &mut TASK_0 });

    unsafe {
        IE.write(FLAGS);
        IME.write(true);
    }

    task::start();
}

fn with_writer(func: impl FnOnce(&mut Writer)) {
    critical!({
        let mut lock = WRITER.lock();
        let writer = lock.as_mut().unwrap();
        func(writer)
    });
}

pub fn timer_irq(timer_num: u16) {
    /*
    with_writer(|w| unsafe {
        for task in task::Q_PENDING.iter() {
            write!(w, "Task pending on {:?}\n", task.block_on);
        }
    }); */
}

/* This function looks for a 'hello' message.
 * The message is as follows:
 * 0x666C6573 ("fles").
 */
fn detect_hello() {
    let mut state = 0;
    loop {
        let byte = serial::read_byte();
        match byte {
            0x66 => state += 1,
            0x6C if state == 1 => state += 1,
            0x65 if state == 2 => state += 1,
            0x73 if state == 3 => {
                break;
            }
            _ => state = 0,
        }
    }
}

pub fn serial_irq(_: u16) {
    let state = SIOCNT_.read();

    if !state.rx_empty() {
        task::unblock_on(BlockingOn::SerialReadable);
    }

    if !state.tx_full() {
        task::unblock_on(BlockingOn::SerialWritable);
    }
}

extern "C" fn task_0() {
    let mut wbl_buf: [u8; 512] = [0; 512];
    let mut checksum: u32 = 0;

    with_writer(|w| {
        write!(w, "Flashing task initialized...\n");
    });

    detect_hello();
    with_writer(|w| {
        write!(w, "Detected hello...\n");
    });

    let total_size = serial::read_dword() as usize;

    let mut sector_num = serial::read_dword() as usize;
    let mut wbl_num = 0;

    let mut size_ctr = 0;
    with_writer(|w| {
        write!(
            w,
            "Total flash size: {} bytes\nErasing first sector...",
            total_size
        );
    });

    let res = unsafe { flash::erase_sector(sector_num) };
    if res != flash::EAResult::SUCCESS {
        panic!("Erasing first sector failed!");
    }

    loop {
        with_writer(|w| {
            write!(w, "\rWaiting for sector {} WBL {}", sector_num, wbl_num);
        });

        let size = serial::read_dword() as usize;

        with_writer(|w| {
            write!(w, "\rRead size {}", size);
        });

        let checksum_expected = serial::read_dword();

        with_writer(|w| {
            write!(w, "\rRead checksum {}", checksum_expected);
        });

        size_ctr += size;

        for i in 0..size {
            let b = serial::read_byte();
            wbl_buf[i] = b;
            checksum = checksum.wrapping_add(b as u32);
        }

        if checksum != checksum_expected {
            panic!(
                "Checksum doesn't match! (sector {}, wbl {}, size: {}, theirs: {}, ours: {})",
                sector_num, wbl_num, size, checksum_expected, checksum
            );
        }

        checksum = 0;

        with_writer(|w| {
            write!(w, "\rReceived sector {} WBL {}", sector_num, wbl_num);
        });

        unsafe {
            let slice_buf = &wbl_buf[0..size];
            let res = flash::write_buffer(sector_num, wbl_num, slice_buf);
            if res != flash::EAResult::SUCCESS {
                panic!(
                    "Flash write of sector {} WBL {} failed!",
                    sector_num, wbl_num
                );
            }
        }

        with_writer(|w| {
            write!(w, "\rFlashed sector {} WBL {}", sector_num, wbl_num);
        });

        if size_ctr == total_size {
            break;
        }

        wbl_num += 1;
        if wbl_num == 256 {
            sector_num += 1;
            wbl_num = 0;
            with_writer(|w| {
                write!(w, "\rReached new sector, erasing...");
            });

            let res = unsafe { flash::erase_sector(sector_num) };
            if res != flash::EAResult::SUCCESS {
                panic!("Erasing sector {} failed!", sector_num);
            }
        }
    }

    with_writer(|w| {
        write!(w, "\nDone!");
    })
}
