#![allow(non_snake_case)]

// Programmable Interrupt Controller
// http://stanislavs.org/helppc/8259.html

pub const PIC_LOG: bool = false;
pub const PIC_LOG_VERBOSE: bool = false;

use cpu::cpu;
use std::sync::{Mutex, MutexGuard};

// Note: This layout is deliberately chosen to match the old JavaScript pic state
// (cpu.get_state_pic depens on this layout)
#[repr(C, packed)]
struct Pic0 {
    irq_mask: u8,

    irq_map: u8,

    // in-service register
    // Holds interrupts that are currently being serviced
    isr: u8,

    // interrupt request register
    // Holds interrupts that have been requested
    irr: u8,

    master: bool,
    dummy: u8, // remove when state image is updated

    expect_icw4: bool,
    state: u8,
    read_isr: bool,
    auto_eoi: bool,

    elcr: u8,

    irq_value: u8,
    special_mask_mode: bool,
}

pub struct Pic {
    master: Pic0,
    slave: Pic0,
}

static PIC: Mutex<Pic> = Mutex::new(Pic {
    master: Pic0 {
        // all irqs off
        irq_mask: 0,
        // Bogus default value (both master and slave mapped to 0).
        // Will be initialized by the BIOS
        irq_map: 0,
        // in-service register
        // Holds interrupts that are currently being serviced
        isr: 0,
        // interrupt request register
        // Holds interrupts that have been requested
        irr: 0,
        irq_value: 0,
        expect_icw4: false,
        state: 0,
        read_isr: false,
        auto_eoi: false,
        special_mask_mode: false,
        elcr: 0,
        master: true,
        dummy: 0,
    },
    slave: Pic0 {
        // all irqs off
        irq_mask: 0,
        // Bogus default value (both master and slave mapped to 0).
        // Will be initialized by the BIOS
        irq_map: 0,
        // in-service register
        // Holds interrupts that are currently being serviced
        isr: 0,
        // interrupt request register
        // Holds interrupts that have been requested
        irr: 0,
        irq_value: 0,
        expect_icw4: false,
        state: 0,
        read_isr: false,
        auto_eoi: false,
        special_mask_mode: false,
        elcr: 0,
        master: false,
        dummy: 0,
    },
});

pub fn get_pic() -> MutexGuard<'static, Pic> { PIC.try_lock().unwrap() }

// Checking for callable interrupts:
// (cpu changes interrupt flag) -> cpu.handle_irqs -> pic_acknowledge_irq
// (pic changes isr/irr) -> pic.check_irqs -> cpu.handle_irqs -> ...

// triggering irqs:
// (io device has irq) -> cpu.device_raise_irq -> pic.set_irq -> pic.check_irqs -> cpu.handle_irqs -> (see above)

impl Pic0 {
    unsafe fn get_irq(&mut self) -> Option<u8> {
        let enabled_irr = self.irr & self.irq_mask;

        if enabled_irr == 0 {
            if PIC_LOG_VERBOSE {
                dbg_log!(
                    "[PIC] no unmasked irrs. irr={:x} mask={:x} isr={:x}",
                    self.irr,
                    self.irq_mask,
                    self.isr
                );
            }
            return None;
        }

        let irq_mask = enabled_irr & (!enabled_irr + 1);
        let special_mask = if self.special_mask_mode { self.irq_mask } else { 0xFF };

        if self.isr != 0 && (self.isr & (!self.isr + 1) & special_mask) <= irq_mask {
            // wait for eoi of higher or same priority interrupt
            if PIC_LOG {
                dbg_log!(
                    "[PIC] higher prio: master={} isr={:x} mask={:x} irq={:x}",
                    self.master,
                    self.isr,
                    self.irq_mask,
                    irq_mask
                );
            }
            return None;
        }

        dbg_assert!(irq_mask != 0);
        let irq_number = irq_mask.ilog2() as u8;
        dbg_assert!(irq_mask == 1 << irq_number);

        if PIC_LOG_VERBOSE {
            dbg_log!("[PIC] request irq {}", irq_number);
        }

        Some(irq_number)
    }

    unsafe fn port0_read(self: &Pic0) -> u32 {
        (if self.read_isr { self.isr } else { self.irr }) as u32
    }
    unsafe fn port1_read(self: &Pic0) -> u32 { !self.irq_mask as u32 }
}

impl Pic {
    unsafe fn set_irq(self: &mut Pic, i: u8) {
        let mask = 1 << (i & 7);
        let dev = if i < 8 { &mut self.master } else { &mut self.slave };
        if dev.irq_value & mask == 0 || dev.elcr & mask != 0 {
            dev.irr |= mask;
            dev.irq_value |= mask;
            if i < 8 {
                self.check_irqs_master()
            }
            else {
                self.check_irqs_slave()
            }
        }
    }

    unsafe fn clear_irq(self: &mut Pic, i: u8) {
        let mask = 1 << (i & 7);
        let dev = if i < 8 { &mut self.master } else { &mut self.slave };
        dev.irq_value &= !mask;
        if dev.elcr & mask != 0 {
            dev.irr &= !mask;
            if i < 8 {
                self.check_irqs_master()
            }
            else {
                self.check_irqs_slave()
            }
        }
    }

    unsafe fn port0_write(&mut self, index: u8, v: u8) {
        let dev = if index == 0 { &mut self.master } else { &mut self.slave };
        if v & 0x10 != 0 {
            // xxxx1xxx
            // icw1
            dbg_log!("icw1 = {:x}", v);
            dev.isr = 0;
            dev.irr = 0;
            dev.irq_mask = 0xff;
            dev.irq_value = 0;
            dev.auto_eoi = true;

            dev.expect_icw4 = v & 1 != 0;
            dbg_assert!(v & 2 == 0, "unimplemented: single mode");
            dbg_assert!(v & 8 == 0, "unimplemented: level mode");
            dev.state = 1;
        }
        else if v & 8 != 0 {
            // xxx01xxx
            // ocw3
            dbg_log!("ocw3: {:x}", v);
            if v & 2 != 0 {
                dev.read_isr = v & 1 != 0;
            }
            if v & 4 != 0 {
                dbg_assert!(false, "unimplemented: polling");
            }
            if v & 0x40 != 0 {
                dev.special_mask_mode = (v & 0x20) == 0x20;
                dbg_log!("special mask mode: {}", dev.special_mask_mode);
            }
        }
        else {
            // xxx00xxx
            // ocw2
            // end of interrupt
            if PIC_LOG {
                dbg_log!("eoi: {:x}", v);
            }

            let eoi_type = v >> 5;

            if eoi_type == 1 {
                // non-specific eoi
                dev.isr &= dev.isr - 1;
                if PIC_LOG {
                    dbg_log!("new isr: {:x}", dev.isr);
                }
            }
            else if eoi_type == 3 {
                // specific eoi
                dev.isr &= !(1 << (v & 7));
            }
            else if eoi_type == 6 {
                // os2 v4, freebsd
                let priority = v & 7;
                dbg_log!("lowest priority: {:x}", priority);
            }
            else {
                dbg_log!("Unknown eoi: {:x} type={:x}", v, eoi_type);
                dbg_assert!(false);
                dev.isr &= dev.isr - 1;
            }

            if index == 0 {
                self.check_irqs_master()
            }
            else {
                self.check_irqs_slave()
            }
        }
    }

    unsafe fn port1_write(&mut self, index: u8, v: u8) {
        let dev = if index == 0 { &mut self.master } else { &mut self.slave };
        if dev.state == 0 {
            if dev.expect_icw4 {
                // icw4
                dev.expect_icw4 = false;
                dev.auto_eoi = v & 2 != 0;
                dbg_log!("icw4: {:x} autoeoi={}", v, dev.auto_eoi);
                dbg_assert!(v & 0x10 == 0, "unimplemented: nested mode");
                dbg_assert!(v & 1 == 1, "unimplemented: 8086/88 mode");
            }
            else {
                // ocw1
                dev.irq_mask = !v;

                if PIC_LOG_VERBOSE {
                    dbg_log!("interrupt mask: {:x}", dev.irq_mask);
                }

                if index == 0 {
                    self.check_irqs_master()
                }
                else {
                    self.check_irqs_slave()
                }
            }
        }
        else if dev.state == 1 {
            // icw2
            dev.irq_map = v;
            dbg_log!("interrupts are mapped to {:x}", dev.irq_map);
            dev.state += 1;
        }
        else if dev.state == 2 {
            // icw3
            dev.state = 0;
            dbg_log!("icw3: {:x}", v);
        }
    }

    unsafe fn check_irqs_master(&mut self) {
        let is_set = self.master.get_irq().is_some();
        if is_set {
            cpu::handle_irqs_internal(self);
        }
    }
    unsafe fn check_irqs_slave(&mut self) {
        let is_set = self.slave.get_irq().is_some();
        if is_set {
            self.set_irq(2)
        }
        else {
            self.clear_irq(2)
        }
    }
}

// called by the cpu
pub unsafe fn pic_acknowledge_irq(pic: &mut Pic) -> Option<u8> {
    let irq = match pic.master.get_irq() {
        Some(i) => i,
        None => return None,
    };

    if pic.master.irr == 0 {
        dbg_assert!(false);
        //PIC_LOG_VERBOSE && dbg_log!("master> spurious requested=" + irq);
        //Some(pic.irq_map | 7)
        return None;
    }

    let mask = 1 << irq;

    if pic.master.elcr & mask == 0 {
        // not in level mode
        pic.master.irr &= !mask;
    }

    if !pic.master.auto_eoi {
        pic.master.isr |= mask;
    }

    if PIC_LOG_VERBOSE {
        dbg_log!("[PIC] master> acknowledge {}", irq);
    }

    //pic.check_irqs_master(); // XXX

    if irq == 2 {
        acknowledge_irq_slave(pic)
    }
    else {
        Some(pic.master.irq_map | irq)
    }
}

unsafe fn acknowledge_irq_slave(pic: &mut Pic) -> Option<u8> {
    let irq = match pic.slave.get_irq() {
        Some(i) => i,
        None => return None,
    };

    if pic.slave.irr == 0 {
        //PIC_LOG_VERBOSE && dbg_log!("slave> spurious requested=" + irq);
        //Some(pic.irq_map | 7)
        dbg_assert!(false);
        return None;
    }

    let mask = 1 << irq;

    if pic.slave.elcr & mask == 0 {
        // not in level mode
        pic.slave.irr &= !mask;
    }

    if !pic.slave.auto_eoi {
        pic.slave.isr |= mask;
    }

    if PIC_LOG_VERBOSE {
        dbg_log!("[PIC] slave> acknowledge {}", irq);
    }
    //pic.check_irqs_slave(); // XXX

    Some(pic.slave.irq_map | irq)
}

// called by javascript
#[no_mangle]
pub unsafe fn pic_set_irq(i: u8) {
    dbg_assert!(i < 16);

    if PIC_LOG_VERBOSE {
        dbg_log!("[PIC] set irq {}", i);
    }

    get_pic().set_irq(i)
}

// called by javascript
#[no_mangle]
pub unsafe fn pic_clear_irq(i: u8) {
    dbg_assert!(i < 16);

    if PIC_LOG_VERBOSE {
        dbg_log!("[PIC] clear irq {}", i);
    }

    get_pic().clear_irq(i)
}

#[no_mangle]
pub unsafe fn port20_read() -> u32 { get_pic().master.port0_read() }
#[no_mangle]
pub unsafe fn port21_read() -> u32 { get_pic().master.port1_read() }

#[no_mangle]
pub unsafe fn portA0_read() -> u32 { get_pic().slave.port0_read() }
#[no_mangle]
pub unsafe fn portA1_read() -> u32 { get_pic().slave.port1_read() }

#[no_mangle]
pub unsafe fn port20_write(v: u8) { get_pic().port0_write(0, v) }
#[no_mangle]
pub unsafe fn port21_write(v: u8) { get_pic().port1_write(0, v) }

#[no_mangle]
pub unsafe fn portA0_write(v: u8) { get_pic().port0_write(1, v) }
#[no_mangle]
pub unsafe fn portA1_write(v: u8) { get_pic().port1_write(1, v) }

#[no_mangle]
pub unsafe fn port4D0_read() -> u32 { get_pic().master.elcr as u32 }
#[no_mangle]
pub unsafe fn port4D1_read() -> u32 { get_pic().slave.elcr as u32 }
#[no_mangle]
pub unsafe fn port4D0_write(v: u8) { get_pic().master.elcr = v }
#[no_mangle]
pub unsafe fn port4D1_write(v: u8) { get_pic().slave.elcr = v }

#[no_mangle]
pub unsafe fn get_pic_addr_master() -> u32 { &raw const get_pic().master as u32 }
#[no_mangle]
pub unsafe fn get_pic_addr_slave() -> u32 { &raw const get_pic().slave as u32 }
