use x86::{
    io::inb,
    vmx::{self, vmcs},
};
use x86_64::structures::paging::{FrameAllocator, PhysFrame, Size4KiB};

use super::qual::QualIo;
use crate::{
    info,
    interrupt::subscriber::InterruptContext,
    serial,
    vmm::x86_64::intel::{
        register::GuestRegisters, vmcs::controls::EntryIntrInfo, vmread, vmwrite,
    },
};

pub fn vmm_interrupt_subscriber(vcpu_ptr: *mut core::ffi::c_void, context: &InterruptContext) {
    if vcpu_ptr.is_null() {
        return;
    }

    let vcpu = unsafe { &mut *(vcpu_ptr as *mut super::vcpu::IntelVCpu) };

    if 0x20 <= context.vector && context.vector <= 0x20 + 16 {
        let irq = context.vector - 0x20;
        vcpu.pic.pending_irq |= 1 << irq;
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InitPhase {
    Uninitialized,
    Phase1,
    Phase2,
    Phase3,
    Initialized,
}

pub enum ReadSel {
    Irr,
    Isr,
}

#[derive(Debug, Clone, Copy, Default)]
pub struct Serial {
    pub ier: u8,
    pub mcr: u8,
}

pub struct Pic {
    pub primary_mask: u8,
    pub secondary_mask: u8,
    pub primary_phase: InitPhase,
    pub secondary_phase: InitPhase,
    pub primary_base: u8,
    pub secondary_base: u8,
    pub primary_irr: u8,
    pub primary_isr: u8,
    pub secondary_irr: u8,
    pub secondary_isr: u8,
    pub primary_read_sel: ReadSel,
    pub secondary_read_sel: ReadSel,
    pub serial: Serial,
    pub pending_irq: u16,
}

impl Pic {
    pub fn new() -> Self {
        Self {
            primary_mask: 0xFF,
            secondary_mask: 0xFF,
            primary_phase: InitPhase::Uninitialized,
            secondary_phase: InitPhase::Uninitialized,
            primary_base: 0,
            secondary_base: 0,
            primary_irr: 0,
            primary_isr: 0,
            secondary_irr: 0,
            secondary_isr: 0,
            primary_read_sel: ReadSel::Irr,
            secondary_read_sel: ReadSel::Irr,
            serial: Serial::default(),
            pending_irq: 0,
        }
    }

    pub fn handle_io(&mut self, regs: &mut GuestRegisters, qual: QualIo) {
        match qual.direction() {
            0 => {
                self.handle_io_out(regs, qual);
            }
            1 => {
                self.handle_io_in(regs, qual);
            }
            _ => {}
        }
    }

    pub fn inject_external_interrupt(&mut self) -> Result<bool, &'static str> {
        let pending = self.pending_irq;

        if pending == 0 {
            return Ok(false);
        }

        if self.primary_phase != InitPhase::Initialized {
            return Ok(false);
        }

        let eflags = vmread(vmx::vmcs::guest::RFLAGS)?;
        if eflags >> 9 & 1 == 0 {
            return Ok(false);
        }

        let interruptibility = vmread(vmx::vmcs::guest::INTERRUPTIBILITY_STATE)?;
        if interruptibility & 0x3 != 0 {
            return Ok(false);
        }

        let is_secondary_masked = (self.primary_mask >> 2) & 1 != 0;

        for i in 0..16 {
            if is_secondary_masked && i >= 8 {
                continue;
            }

            let irq_bit = 1 << i;
            if pending & irq_bit == 0 {
                continue;
            }

            let delta = if i < 8 { i } else { i - 8 };
            let is_masked = if i < 8 {
                (self.primary_mask >> delta) & 1 != 0
            } else {
                let is_irq_masked = (self.secondary_mask >> delta) & 1 != 0;
                is_secondary_masked || is_irq_masked
            };

            if is_masked {
                continue;
            }

            let interrupt_info = EntryIntrInfo::new()
                .with_vector(
                    delta as u8
                        + if i < 8 {
                            self.primary_base
                        } else {
                            self.secondary_base
                        },
                )
                .with_typ(0)
                .with_ec_available(false)
                .with_valid(true);

            vmwrite(
                vmx::vmcs::control::VMENTRY_INTERRUPTION_INFO_FIELD,
                u32::from(interrupt_info) as u64,
            )?;

            self.pending_irq &= !irq_bit;
            return Ok(true);
        }

        Ok(false)
    }

    pub fn inject_exception(
        &mut self,
        vector: u32,
        error_code: Option<u32>,
    ) -> Result<(), &'static str> {
        let has_error_code = matches!(vector, 8 | 10..=14 | 17 | 21);

        let interrupt_info = EntryIntrInfo::new()
            .with_vector(vector as u8)
            .with_typ(3)
            .with_ec_available(has_error_code)
            .with_valid(true);

        vmwrite(
            vmx::vmcs::control::VMENTRY_INTERRUPTION_INFO_FIELD,
            u32::from(interrupt_info) as u64,
        )?;

        if has_error_code {
            let ec = error_code.unwrap_or(0);
            vmwrite(vmx::vmcs::control::VMENTRY_EXCEPTION_ERR_CODE, ec as u64)?;
        }

        Ok(())
    }

    fn handle_io_in(&self, regs: &mut GuestRegisters, qual: QualIo) {
        match qual.port() {
            0x0CF8..=0x0CFF => regs.rax = 0,
            0xC000..=0xCFFF => {} //ignore
            0x20..=0x21 => self.handle_pic_in(regs, qual),
            0xA0..=0xA1 => self.handle_pic_in(regs, qual),
            0x0070..=0x0071 => regs.rax = 0,
            0x03F8..=0x03FF => self.handle_serial_in(regs, qual),
            _ => regs.rax = 0,
        }
    }

    fn handle_io_out(&mut self, regs: &mut GuestRegisters, qual: QualIo) {
        match qual.port() {
            0x0CF8..=0x0CFF => {} //ignore
            0xC000..=0xCFFF => {} //ignore
            0x20..=0x21 => self.handle_pic_out(regs, qual),
            0xA0..=0xA1 => self.handle_pic_out(regs, qual),
            0x03F8..=0x03FF => self.handle_serial_out(regs, qual),
            0x0070..=0x0071 => {} //ignore
            _ => {}
        }
    }

    fn handle_serial_in(&self, regs: &mut GuestRegisters, qual: QualIo) {
        match qual.port() {
            0x3F8 => regs.rax = unsafe { inb(qual.port()).into() },
            0x3F9 => regs.rax = self.serial.ier as u64,
            0x3FA => {} //regs.rax = 0, //  unsafe { inb(qual.port()).into() },
            0x3FB => {} //regs.rax = 0,
            0x3FC => {} //regs.rax = 0, //self.serial.mcr as u64,
            0x3FD => {
                if qual.size() == 1 {
                    regs.rax = 0x60
                }
            }
            0x3FE => {
                if qual.size() == 1 {
                    regs.rax = 0xb0
                }
            }
            0x3FF => {} //regs.rax = 0,
            _ => {
                panic!("Serial in: invalid port: {:#x}", qual.port());
            }
        }
    }

    fn handle_serial_out(&mut self, regs: &mut GuestRegisters, qual: QualIo) {
        match qual.port() {
            0x3F8 => serial::write_byte(regs.rax as u8),
            0x3F9 => {
                self.serial.ier = regs.rax as u8;
                if regs.rax & 0b10 != 0 {
                    self.pending_irq |= 1 << 4;
                }
            }
            0x3FA => {}
            0x3FB => {}
            0x3FC => self.serial.mcr = regs.rax as u8,
            0x3FD => {}
            0x3FF => {}
            _ => {
                panic!("Serial out: invalid port: {:#x}", qual.port());
            }
        }
    }

    fn handle_pic_in(&self, regs: &mut GuestRegisters, qual: QualIo) {
        match qual.port() {
            0x20 => {
                let v = match self.primary_read_sel {
                    ReadSel::Irr => self.primary_irr,
                    ReadSel::Isr => self.primary_isr,
                };
                regs.rax = v as u64;
            }
            0xA0 => {
                let v = match self.secondary_read_sel {
                    ReadSel::Irr => self.secondary_irr,
                    ReadSel::Isr => self.secondary_isr,
                };
                regs.rax = v as u64;
            }
            0x21 => match self.primary_phase {
                InitPhase::Uninitialized | InitPhase::Initialized => {
                    regs.rax = self.primary_mask as u64;
                }
                _ => {}
            },
            0xA1 => match self.secondary_phase {
                InitPhase::Uninitialized | InitPhase::Initialized => {
                    regs.rax = self.secondary_mask as u64;
                }
                _ => {}
            },
            _ => {}
        }
    }

    fn handle_pic_out(&mut self, regs: &mut GuestRegisters, qual: QualIo) {
        let pic = self;
        let dx = regs.rax as u8;
        match qual.port() {
            0x20 => match dx {
                0x11 => pic.primary_phase = InitPhase::Phase1,
                0x0A => pic.primary_read_sel = ReadSel::Isr,
                0x0B => pic.primary_read_sel = ReadSel::Irr,
                0x20 => {
                    pic.primary_isr = 0;
                }
                0x60..=0x67 => {
                    let irq = dx & 0x7;
                    pic.primary_isr &= !(1 << irq);
                }
                _ => panic!("Primary Pic command: {:#x}", dx),
            },
            0x21 => match pic.primary_phase {
                InitPhase::Uninitialized | InitPhase::Initialized => pic.primary_mask = dx,
                InitPhase::Phase1 => {
                    pic.primary_base = dx;
                    pic.primary_phase = InitPhase::Phase2;
                }
                InitPhase::Phase2 => {
                    pic.primary_phase = InitPhase::Phase3;
                }
                InitPhase::Phase3 => {
                    info!("Primary Pic Initialized");
                    pic.primary_phase = InitPhase::Initialized
                }
            },
            0xA0 => match dx {
                0x11 => pic.secondary_phase = InitPhase::Phase1,
                0x0A => pic.secondary_read_sel = ReadSel::Isr,
                0x0B => pic.secondary_read_sel = ReadSel::Irr,
                0x20 => {
                    pic.secondary_isr = 0;
                }
                0x60..=0x67 => {
                    let irq = dx & 0x7;
                    pic.secondary_isr &= !(1 << irq);
                }
                _ => panic!("Secondary Pic command: {:#x}", dx),
            },
            0xA1 => match pic.secondary_phase {
                InitPhase::Uninitialized | InitPhase::Initialized => pic.secondary_mask = dx,
                InitPhase::Phase1 => {
                    pic.secondary_base = dx;
                    pic.secondary_phase = InitPhase::Phase2;
                }
                InitPhase::Phase2 => {
                    pic.secondary_phase = InitPhase::Phase3;
                }
                InitPhase::Phase3 => {
                    info!("Secondary Pic Initialized");
                    pic.secondary_phase = InitPhase::Initialized
                }
            },
            _ => {}
        }
    }
}

pub struct IOBitmap {
    pub bitmap_a: PhysFrame,
    pub bitmap_b: PhysFrame,
}

impl IOBitmap {
    pub fn new(frame_allocator: &mut impl FrameAllocator<Size4KiB>) -> Self {
        let bitmap_a = frame_allocator
            .allocate_frame()
            .expect("Failed to allocate I/O bitmap A");
        let bitmap_b = frame_allocator
            .allocate_frame()
            .expect("Failed to allocate I/O bitmap B");

        Self { bitmap_a, bitmap_b }
    }

    pub fn setup(&mut self) -> Result<(), &'static str> {
        let bitmap_a_addr = self.bitmap_a.start_address().as_u64() as usize;
        let bitmap_b_addr = self.bitmap_b.start_address().as_u64() as usize;

        unsafe {
            core::ptr::write_bytes(bitmap_a_addr as *mut u8, u8::MAX, 4096);
            core::ptr::write_bytes(bitmap_b_addr as *mut u8, u8::MAX, 4096);
        }

        self.set_io_ports(0x0040..=0x0047);
        //self.set_io_ports(0x02F8..=0x03EF);

        vmwrite(vmcs::control::IO_BITMAP_A_ADDR_FULL, bitmap_a_addr as u64)?;
        vmwrite(vmcs::control::IO_BITMAP_B_ADDR_FULL, bitmap_b_addr as u64)?;

        Ok(())
    }

    pub fn set_io_ports(&mut self, ports: core::ops::RangeInclusive<u16>) {
        for port in ports {
            if port <= 0x7FFF {
                let byte_index = port as usize / 8;
                let bit_index = port as usize % 8;

                self.get_bitmap_a()[byte_index] &= !(1 << bit_index);
            } else {
                let adjusted_port = port - 0x8000;
                let byte_index = adjusted_port as usize / 8;
                let bit_index = adjusted_port as usize % 8;

                self.get_bitmap_b()[byte_index] &= !(1 << bit_index);
            }
        }
    }

    fn get_bitmap_a(&mut self) -> &mut [u8] {
        unsafe {
            core::slice::from_raw_parts_mut(self.bitmap_a.start_address().as_u64() as *mut u8, 4096)
        }
    }

    fn get_bitmap_b(&mut self) -> &mut [u8] {
        unsafe {
            core::slice::from_raw_parts_mut(self.bitmap_b.start_address().as_u64() as *mut u8, 4096)
        }
    }
}
