#![allow(unused)]

use core::fmt::{self, Write};

use libk::dma::BusAddress;
use tock_registers::{interfaces::Readable, register_structs, registers::ReadOnly, UIntLike};

use crate::queue::PhysicalRegionPage;

use super::queue::SubmissionQueueEntry;

pub trait Command {
    fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry);
}

pub trait Request: Command {
    type Response;
}

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct String<const N: usize> {
    data: [u8; N],
}

#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
#[repr(u8)]
pub enum ControllerType {
    Reserved,
    Io,
    Discovery,
    Administrative,
}

// I/O commands

#[derive(Clone, Copy, Debug)]
pub struct IoRead {
    pub nsid: u32,
    pub lba: u64,
    pub count: u32,
}

#[derive(Clone, Copy, Debug)]
pub struct IoWrite {
    pub nsid: u32,
    pub lba: u64,
    pub count: u32,
}

// Requests

#[derive(Clone, Copy, Debug)]
pub enum SetFeatureRequest {
    NumberOfQueues(u32, u32),
}

#[derive(Clone, Copy, Debug)]
pub struct IdentifyControllerRequest;

#[derive(Clone, Copy, Debug)]
pub struct IdentifyActiveNamespaceIdListRequest {
    pub start_id: u32,
}

#[derive(Clone, Copy, Debug)]
pub struct IdentifyNamespaceRequest {
    pub nsid: u32,
}

#[derive(Clone, Copy, Debug)]
pub struct CreateIoCompletionQueue {
    pub id: u32,
    pub size: usize,
    pub vector: u32,
    pub data: BusAddress,
}

#[derive(Clone, Copy, Debug)]
pub struct CreateIoSubmissionQueue {
    pub id: u32,
    pub cq_id: u32,
    pub size: usize,
    pub data: BusAddress,
}

// Replies

#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct IdentifyControllerResponse {
    pub pci_vid: u16,
    pub pci_ssvid: u16,
    pub serial_number: String<20>,
    pub model_number: String<40>,
    pub firmware_rev: u64,
    _0: [u8; 5], // 72..77
    pub mdts: u8,
    pub cntlid: u16,
    pub ver: u32,
    _1: [u8; 12], // 84..96
    pub ctratt: u32,
    _2: [u8; 11], // 100..111
    pub cntrltype: ControllerType,
}

#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct IdentifyActiveNamespaceIdListResponse {
    pub entries: [u32; 1024],
}

register_structs! {
    #[allow(non_snake_case)]
    pub IdentifyNamespaceResponse {
        (0 => NSZE: ReadOnly<u64>),
        (8 => _0),
        (25 => NLBAF: ReadOnly<u8>),
        (26 => FLBAS: ReadOnly<u8>),
        (27 => _1),
        (128 => LBAFS: [ReadOnly<u32>; 64]),
        (384 => _2),
        (4096 => @END),
    }
}

#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub struct LbaFormat(u32);

impl Command for IdentifyControllerRequest {
    fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
        sqe.command.set_opcode(0x06);
        sqe.command_specific[0] = 0x01;
    }
}

impl Request for IdentifyControllerRequest {
    type Response = IdentifyControllerResponse;
}

impl Command for IdentifyActiveNamespaceIdListRequest {
    fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
        sqe.command.set_opcode(0x06);
        sqe.command_specific[0] = 0x02;
        sqe.nsid = self.start_id;
    }
}

impl Request for IdentifyActiveNamespaceIdListRequest {
    type Response = IdentifyActiveNamespaceIdListResponse;
}

impl Command for IdentifyNamespaceRequest {
    fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
        sqe.command.set_opcode(0x06);
        sqe.command_specific[0] = 0x00;
        sqe.nsid = self.nsid;
    }
}

impl Request for IdentifyNamespaceRequest {
    type Response = IdentifyNamespaceResponse;
}

impl IdentifyNamespaceResponse {
    pub fn current_lba_fmt_idx(&self) -> usize {
        let flbas = self.FLBAS.get();
        let mut index = flbas & 0xF;
        if self.NLBAF.get() > 16 {
            index |= (flbas & 0xE0) >> 1;
        }
        index as usize
    }

    pub fn lba_fmt(&self, idx: usize) -> Option<LbaFormat> {
        if idx > self.NLBAF.get() as usize {
            return None;
        }
        Some(LbaFormat(self.LBAFS[idx].get()))
    }

    pub fn total_lba_count(&self) -> u64 {
        self.NSZE.get()
    }
}

impl LbaFormat {
    pub fn lba_data_size(&self) -> Option<u64> {
        let lbads = (self.0 >> 16) & 0xFF;
        if lbads < 9 {
            return None;
        }
        Some(1 << lbads)
    }
}

impl Command for SetFeatureRequest {
    fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
        sqe.command.set_opcode(0x09);

        match self {
            Self::NumberOfQueues(cq, sq) => {
                let dw11 = (cq << 16) | sq;

                sqe.command_specific[0] = 0x07;
                sqe.command_specific[1] = dw11;
            }
        }
    }
}

impl Command for CreateIoCompletionQueue {
    fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
        sqe.command.set_opcode(0x05);
        sqe.data_pointer[0] = PhysicalRegionPage::with_addr(self.data);
        sqe.command_specific[0] = ((self.size as u32 - 1) << 16) | self.id;
        sqe.command_specific[1] = (self.vector << 16) | 3;
    }
}

impl Command for CreateIoSubmissionQueue {
    fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
        sqe.command.set_opcode(0x01);
        sqe.data_pointer[0] = PhysicalRegionPage::with_addr(self.data);
        sqe.command_specific[0] = ((self.size as u32 - 1) << 16) | self.id;
        // Medium priority
        sqe.command_specific[1] = (self.cq_id << 16) | 1;
    }
}

impl<const N: usize> fmt::Debug for String<N> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_char('"')?;
        for ch in self.data {
            if ch == b' ' || ch == 0 {
                break;
            }
            f.write_char(ch as _)?;
        }
        f.write_char('"')?;
        Ok(())
    }
}

impl Command for IoRead {
    fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
        assert!(self.count < 65536);
        assert_ne!(self.count, 0);

        sqe.command.set_opcode(0x02);
        sqe.command_specific[0] = self.lba as u32;
        sqe.command_specific[1] = (self.lba >> 32) as u32;
        sqe.command_specific[2] = self.count - 1;
        sqe.nsid = self.nsid;
    }
}

impl Command for IoWrite {
    fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
        assert!(self.count < 65536);
        assert_ne!(self.count, 0);

        sqe.command.set_opcode(0x01);
        sqe.command_specific[0] = self.lba as u32;
        sqe.command_specific[1] = (self.lba >> 32) as u32;
        sqe.command_specific[2] = self.count - 1;
        sqe.nsid = self.nsid;
    }
}
