use super::{date::SmallDate, helper::force_convert_ascii_to_str, lsb_msb::{ReadWriteEndian, BigEndianU16, LittleBigEndianU32, LittleBigEndianU16}};
use crate::read_write_bit_getter_setter;
use std::concat;

#[repr(packed(1))]
pub struct DirectoryRecord {
    pub length:                     [u8; 1],
    pub ex_attribute_record_length: [u8; 1],
    pub data_block_number:          LittleBigEndianU32,
    pub data_size:                  LittleBigEndianU32,
    pub time_stamp:                 SmallDate,
    pub flags:                      [u8; 1],
    pub unit_size:                  [u8; 1],
    pub interleave_gap_size:        [u8; 1],
    pub volume_sequence_number:     LittleBigEndianU16,
    pub name_length:                [u8; 1],
    //name:       [u8; name_length]
    //padding:    [u8; if name is even this is 0x00 otherwise not present]
    //system_use: <any system use>
}

#[repr(packed(1))]
pub struct CDXASystemUse {
    pub owner_id_group: BigEndianU16,
    pub owner_id_user:  BigEndianU16,
    pub file_attribute: FileAttribute,
    pub signature:      [u8; 2],
    pub file_number:    [u8; 1],
    _reserved:          [u8; 5],
}

#[repr(packed(1))]
pub struct FileAttribute {
    value: BigEndianU16
}

impl DirectoryRecord {
    pub unsafe fn new(&mut self, name: &str, has_system_use: bool) {
        let length     = Self::calculate_size_for(name, has_system_use);
        *self = DirectoryRecord{
            length:                     [length as u8],
            ex_attribute_record_length: [0u8],
            data_block_number:          LittleBigEndianU32::default(),
            data_size:                  LittleBigEndianU32::default(),
            time_stamp:                 SmallDate::default(),
            flags:                      [0u8],
            unit_size:                  [0u8],
            interleave_gap_size:        [0u8],
            volume_sequence_number:     LittleBigEndianU16::new(1),
            name_length:                [name.len() as u8],
        };

        self.set_name(name);
        if has_system_use {
            if let Some(system_use) = self.get_cdxa_system_use_mut() {
                *system_use = CDXASystemUse::default();
            }
        }
    }

    fn get_system_use_offset(name_length: u8, length: u8) -> Option<(usize, usize)> {
        let offset = std::mem::size_of::<Self>() + name_length as usize + Self::padding_field_size(name_length as usize);
        let len_su = length as usize - offset;
        if len_su == 0 {
            None
        }

        else {
            Some((offset, len_su))
        }
    }

    pub fn get_system_use_raw(&self) -> Option<&[u8]> {
        unsafe {
            let (offset, len_su)         = Self::get_system_use_offset(self.name_length[0], self.length[0])?;
            let raw_system_use:*const u8 = (&self.length as *const u8).add(offset);
            
            Some(std::slice::from_raw_parts(raw_system_use, len_su))
        }
    }

    pub fn get_system_use_raw_mut(&mut self) -> Option<&mut [u8]> {
        unsafe {
            let (offset, len_su)       = Self::get_system_use_offset(self.name_length[0], self.length[0])?;
            let raw_system_use:*mut u8 = (&mut self.length as *mut u8).add(offset);
            
            Some(std::slice::from_raw_parts_mut(raw_system_use, len_su))
        }
    }

    pub fn get_cdxa_system_use(&self) -> Option<&CDXASystemUse> {
        let raw = self.get_system_use_raw()?;

        if let Ok(raw) = raw.try_into() {
            unsafe {
                Some(std::mem::transmute::<&[u8;14], &CDXASystemUse>(raw))
            }
        }

        else {
            None
        }
    }

    pub fn get_cdxa_system_use_mut(&mut self) -> Option<&mut CDXASystemUse> {
        let raw = self.get_system_use_raw_mut()?;

        if let Ok(raw) = raw.try_into() {
            unsafe {
                Some(std::mem::transmute::<&mut [u8;14], &mut CDXASystemUse>(raw))
            }
        }

        else {
            None
        }
    }

    pub fn set_name(&mut self, name: &str) {
        unsafe {
            let mut raw_str:*mut u8 = (&mut self.name_length as *mut u8).add(1);
            for char in name.as_bytes() {
                *raw_str = *char;
                raw_str = raw_str.add(1);
            }
        }
    }

    pub fn get_name(&self) -> &str {
        unsafe {
            let raw_str:*const u8 = (&self.name_length as *const u8).add(1);
            force_convert_ascii_to_str(std::slice::from_raw_parts(raw_str, self.name_length[0] as usize))
        }
    }

    pub fn set_file(&mut self) {
        self.flags[0] = 0;
    }

    pub fn set_directory(&mut self) {
        self.flags[0] = 2;
    }

    pub fn is_file(&self) -> bool {
        self.flags[0] == 0
    }

    pub fn is_directory(&self) -> bool {
        self.flags[0] == 2
    }

    pub fn calculate_size_for(name: &str, has_system_use: bool) -> usize {
        std::mem::size_of::<Self>() + name.len() + Self::padding_field_size(name.len()) + {
            if has_system_use {
                std::mem::size_of::<CDXASystemUse>()
            }

            else {
                0usize
            }
        }
    }

    fn padding_field_size(length: usize) -> usize {
        if (length & 0x1) == 0 {
            1
        }

        else {
            0
        }
    }
}

impl std::fmt::Display for DirectoryRecord {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "\tData Block Number:      {}\n", self.data_block_number.read_any())?;
        write!(f, "\tData Size Bytes:        {}\n", self.data_size.read_any())?;
        write!(f, "\tTime Stamp:             {}\n", self.time_stamp)?;
        write!(f, "\tFile Flags:             {}\n", self.flags[0])?;
        write!(f, "\tFile Unit Size:         {}\n", self.unit_size[0])?;
        write!(f, "\tInterleaved Gap Size:   {}\n", self.interleave_gap_size[0])?;
        write!(f, "\tVolume Sequence Number: {}\n", self.volume_sequence_number.read_any())?;
        write!(f, "\tName:                   {}\n", self.get_name())?;

        if let Some(system_use) = self.get_cdxa_system_use() {
            write!(f, "System Use Area:\n{}", system_use)
        }
        
        else {
            write!(f, "\t<No System Use Area>")
        }
    }
}

impl std::default::Default for CDXASystemUse {
    fn default() -> CDXASystemUse {
        CDXASystemUse{
            owner_id_group: BigEndianU16::new(0),
            owner_id_user:  BigEndianU16::new(0),
            file_attribute: FileAttribute::default(),
            signature:      ['X' as u8, 'A' as u8],
            file_number:    [0u8],
            _reserved:      [0u8; 5],
        }
    }
}

impl std::fmt::Display for CDXASystemUse {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, concat!(
            "\t\towner id group: {}\n",
            "\t\towner id user:  {}\n",
            "\t\tfile attribute:\n{}",
            "\t\tsignature:      {}\n",
            "\t\tfile number:    {}"), 
        self.owner_id_group.read(), self.owner_id_user.read(), self.file_attribute, force_convert_ascii_to_str(&self.signature), self.file_number[0])
    }
}

impl FileAttribute {
    read_write_bit_getter_setter!(value,  0, owner_read);
    read_write_bit_getter_setter!(value,  2, owner_execute);
    read_write_bit_getter_setter!(value,  4, group_read);
    read_write_bit_getter_setter!(value,  6, group_execute);
    read_write_bit_getter_setter!(value,  8, world_read);
    read_write_bit_getter_setter!(value, 10, world_execute);
    read_write_bit_getter_setter!(value, 11, mode2);
    read_write_bit_getter_setter!(value, 12, mode2_form2);
    read_write_bit_getter_setter!(value, 13, interleaved);
    read_write_bit_getter_setter!(value, 14, cdda);
    read_write_bit_getter_setter!(value, 15, directory);
}

impl std::default::Default for FileAttribute {
    fn default() -> FileAttribute {
        let mut att = FileAttribute{value: BigEndianU16::new(0)};

        att.set_owner_read();
        att.set_owner_execute();
        att.set_group_read();
        att.set_group_execute();
        att.set_world_read();
        att.set_world_execute();
        
        att
    }
}

impl std::fmt::Display for FileAttribute {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "\t\t\towner_read:  {}, owner_execute: {}\n", self.is_owner_read(),  self.is_owner_execute())?;
        write!(f, "\t\t\tgroup_read:  {}, group_execute: {}\n", self.is_group_read(),  self.is_group_execute())?;
        write!(f, "\t\t\tworld_read:  {}, world_execute: {}\n", self.is_world_read(),  self.is_world_execute())?;
        write!(f, "\t\t\tmode2:       {}, mode2_form2:   {}\n", self.is_mode2(),       self.is_mode2_form2())?;
        write!(f, "\t\t\tinterleaved: {}, CDDA:          {}\n", self.is_interleaved(), self.is_cdda())?;
        write!(f, "\t\t\tdirectory:   {}\n",                    self.is_directory())
    }
}