pub mod tables;
use super::{sector::SECTOR_SIZE, lsb_msb::{ReadWriteEndian, LittleEndianU32}};

type RawSector = [u8; SECTOR_SIZE];

#[repr(packed(1))]
#[derive(Clone, Copy, Default)]
pub struct EDC {
    data: LittleEndianU32
}

#[repr(packed(1))]
#[derive(Clone, Copy)]
pub struct ECC {
    data: [u8; 276]
}

impl EDC {
    pub fn calculate_for(data: &[u8]) -> EDC {
        let mut edc = EDC{data: LittleEndianU32::default()};

        edc.calculate(data);
        edc
    }

    pub fn calculate(&mut self, data: &[u8]) {
        let mut edc = 0u32;

        for byte in data {
            edc = edc ^ *byte as u32;
            edc = (edc >> 8) ^ tables::TABLES.edc[(edc & 0xFF) as usize];
        }

        self.data.write(edc);
    }
}

impl std::fmt::Display for EDC {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "0x{:X}", self.data.read())
    }
}

impl ECC {
    pub fn calculate_for<T>(sector: &mut T) {
        unsafe {
            let mut raw_sector = std::mem::transmute::<&mut T, &mut [u8; SECTOR_SIZE]>(sector);

            Self::calc_p_parity(&mut raw_sector);
            Self::calc_q_parity(&mut raw_sector);
        }
    }

    fn calc_parity(sector: &mut RawSector, offset: usize, len: usize, j0: usize, step1: usize, step2: usize) {
        let mut src = 0xC;
        let mut dst = 0x81C + offset;
        let src_max = dst;

        for _ in 0..len {
            let     base = src;
            let mut x    = 0u16;
            let mut y    = 0u16;

            for j in j0..=42 {
                x ^= tables::TABLES.gf8_product[j][sector[src + 0] as usize];
                y ^= tables::TABLES.gf8_product[j][sector[src + 1] as usize];

                src += step1;
                if step1 == 2*44 && src >= src_max {
                    src = src - 2*1118;
                }
            }

            sector[dst + 2*len + 0] = (x & 0x00FF) as u8; sector[dst + 0] = (x >> 8) as u8;
            sector[dst + 2*len + 1] = (y & 0x00FF) as u8; sector[dst + 1] = (y >> 8) as u8;

            dst += 2;
            src = base + step2;
        }
    }

    fn calc_p_parity(sector: &mut RawSector) {
        Self::calc_parity(sector, 0, 43, 19, 2*43, 2);
    }

    fn calc_q_parity(sector: &mut RawSector) {
        Self::calc_parity(sector, 43*4, 26, 0, 2*44, 2*43);
    }
}

impl std::fmt::Display for ECC {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for n in (0..self.data.len()).step_by(4) {
            let value = unsafe { *std::mem::transmute::<&u8, &u32>(&self.data[n]) };
            
            if n != 0 && n%(4*4) == 0 {
                write!(f, "\n")?;
            }
            write!(f, "0x{:08X} ", value)?;            
        }
        std::fmt::Result::Ok(())
    }
}

impl std::cmp::PartialEq for ECC {
    fn eq(&self, other: &Self) -> bool {
        for i in 0..std::mem::size_of::<ECC>() {
            if self.data[i] != other.data[i] {
                return false;
            }
        }
        true
    }
}

impl Default for ECC {
    fn default() -> Self {
        ECC{data: [0u8; 276]}
    }
}