use super::{cdstring::{AString, DString}, date::Date, dir_record::DirectoryRecord, lsb_msb::{ReadWriteEndian, BigEndianU32, LittleEndianU32, LittleBigEndianU32, LittleBigEndianU16}, sector::Mode2Form1};
use super::helper::force_convert_ascii_to_str as obtain_string;

const _: () = assert!(std::mem::size_of::<PrimaryVolumeDescriptor>() == Mode2Form1::DATA_SIZE, "PVD wrong data size");

#[repr(packed(1))]
pub struct PrimaryVolumeDescriptor {
    pub volume_type:           [u8; 1],            // 1 for Primary Volume Descriptor
    pub std_id:                [u8; 5],            // "CD001"
    pub version:               [u8; 1],            // 1 for Standard
    pub reserved_1:            [u8; 1],
    pub system_id:             AString::<32>,      // "PLAYSTATION"
    pub volume_id:             DString::<32>,      // ???
    pub reserved_2:            [u8; 8],
    pub vol_space_size:        LittleBigEndianU32, // number of logical blocks
    pub reserved_3:            [u8;32],
    pub vol_set_size:          LittleBigEndianU16, // Usually 1
    pub vol_seq_number:        LittleBigEndianU16, // Usually 1
    pub block_size:            LittleBigEndianU16, // Size in bytes of a logical block
    pub path_table_size:       LittleBigEndianU32, // Maximum 0x800 for PSX
    pub path_table_1:          LittleEndianU32,    // Start of Table1  
    pub path_table_2:          LittleEndianU32,    // Start of Table2
    pub path_table_3:          BigEndianU32,       // Start of Table3
    pub path_table_4:          BigEndianU32,       // Start of Table4
    pub root_dir_record:       [u8; 34],           // Root directory record
    pub volume_set_id:         DString::<128>,     // String which is usually empty
    pub publisher_id:          AString::<128>,     // Company name of publisher
    pub data_preparer:         AString::<128>,     // 
    pub app_id:                AString::<128>,     // "PLAYSTATION"
    pub copyright_file:        [u8; 37],           // FILENAME.EXT;VER
    pub abstract_file:         [u8; 37],           // Empty
    pub bibliograph_file:      [u8; 37],           // Empty
    pub vol_create_time:       Date,               // YYYYMMDDHHMMSSFF
    pub vol_modification_time: Date,               // YYYYMMDDHHMMSSFF all zero
    pub vol_expiration_time:   Date,               // YYYYMMDDHHMMSSFF all zero
    pub vol_effective_time:    Date,               // YYYYMMDDHHMMSSFF all zero
    pub file_struct_version:   [u8; 1],            // 1 Standard
    pub reserved_4:            [u8; 1],
    pub app_use_area:          [u8; 141],          // zero for PSX and VCD
    pub cd_xa_id:              [u8;8],             // "CD-XA001" for PSX and VCD
    pub cd_xa_flags:           [u8;2],             // zero for PSX and VCD
    pub cd_xa_startup_dir:     [u8; 8],            // zero for PSX and VCD
    pub cd_xa_reserved:        [u8; 8],            // zero for PSX and VCD
    pub app_use_area_2:        [u8; 345],          // zero for PSX and VCD
    pub reserved_5:            [u8; 653]
}

#[repr(packed(1))]
pub struct VolumeDescriptorTerminator {
    pub volume_type: [u8; 1],   //0xFF for Terminator
    pub std_id:      [u8; 5],   //"CD0001"
    pub version:     [u8; 1],   //0x01 for Standard
    pub reserved:    [u8; 2041] //Zero filled
}

impl PrimaryVolumeDescriptor {
    pub fn new() -> PrimaryVolumeDescriptor {
        PrimaryVolumeDescriptor{
            volume_type:           [1],
            std_id:                ['C' as u8, 'D' as u8, '0' as u8, '0' as u8, '1' as u8],
            version:               [1],
            reserved_1:            [0],
            system_id:             AString::default(),
            volume_id:             DString::default(),
            reserved_2:            [0; 8],
            vol_space_size:        LittleBigEndianU32::default(),
            reserved_3:            [0; 32],
            vol_set_size:          LittleBigEndianU16::new(0x0001),
            vol_seq_number:        LittleBigEndianU16::new(0x0001),
            block_size:            LittleBigEndianU16::new(0x0800),
            path_table_size:       LittleBigEndianU32::default(),
            path_table_1:          LittleEndianU32::default(),
            path_table_2:          LittleEndianU32::default(),
            path_table_3:          BigEndianU32::default(),
            path_table_4:          BigEndianU32::default(),
            root_dir_record:       [0; 34],
            volume_set_id:         DString::default(),
            publisher_id:          AString::default(),
            data_preparer:         AString::default(),
            app_id:                AString::default(),
            copyright_file:        [' ' as u8; 37],
            abstract_file:         [' ' as u8; 37],
            bibliograph_file:      [' ' as u8; 37],
            vol_create_time:       Date::new(),
            vol_modification_time: Date::new(),
            vol_expiration_time:   Date::new(),
            vol_effective_time:    Date::new(),
            file_struct_version:   [1],
            reserved_4:            [0],
            app_use_area:          [0; 141],
            cd_xa_id:              [0;8],
            cd_xa_flags:           [0; 2],
            cd_xa_startup_dir:     [0; 8],
            cd_xa_reserved:        [0; 8],
            app_use_area_2:        [0; 345],
            reserved_5:            [0; 653]
        }
    }

    pub fn get_root_dir_record(&self) -> &DirectoryRecord {
        unsafe {
            std::mem::transmute::<&[u8; 34], &DirectoryRecord>(&self.root_dir_record)
        }
    }

    pub fn get_root_dir_record_mut(&mut self) -> &mut DirectoryRecord {
        unsafe {
            std::mem::transmute::<&mut [u8; 34], &mut DirectoryRecord>(&mut self.root_dir_record)
        }
    }
}

impl std::fmt::Display for PrimaryVolumeDescriptor {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let root_dir = self.get_root_dir_record();

        write!(f, "Volume Descriptor Type:     {}\nStandard Identifier:        {}\n", self.volume_type[0],                      obtain_string(&self.std_id))?;
        write!(f, "Volume Descriptor Version:  {}\nSystem Identifier:          {}\n", self.version[0],                          obtain_string(self.system_id.as_raw()))?;
        write!(f, "Volume Identifier:          {}\nVolume Space Size:          {}\n", obtain_string(self.volume_id.as_raw()),   self.vol_space_size.read_any())?;
        write!(f, "Voulme Set Size:            {}\nVolume Sequence Number:     {}\n", self.vol_set_size.read_any(),              self.vol_seq_number.read_any())?;
        write!(f, "Logical Block Size (Bytes): {}\nPath Table Size (Bytes):    {}\n", self.block_size.read_any(),                self.path_table_size.read_any())?;
        write!(f, "Path Table 1:               {}\nPath Table 2:               {}\n", self.path_table_1.read(),                  self.path_table_2.read())?;
        write!(f, "Path Table 3:               {}\nPath Table 4:               {}\n", self.path_table_3.read(),                  self.path_table_4.read())?;
        write!(f, "Root Directory Record:      \n{}\nVolume Set Identifier:      {}\n", root_dir,                                  obtain_string(self.volume_set_id.as_raw()))?;
        write!(f, "Publisher Identifier:       {}\nData Preparer Identifier:   {}\n", obtain_string(self.publisher_id.as_raw()), obtain_string(self.data_preparer.as_raw()))?;
        write!(f, "Application Identifier:     {}\nCopyright Filename:         {}\n", obtain_string(self.app_id.as_raw()),       obtain_string(&self.copyright_file))?;
        write!(f, "Abstract Filename:          {}\nBibliographic Filename:     {}\n", obtain_string(&self.abstract_file),        obtain_string(&self.bibliograph_file))?;
        write!(f, "Volume Creation Time:       {}\nVolume Modification Time:   {}\n", self.vol_create_time,                      self.vol_modification_time)?;
        write!(f, "Volume Expiration Time:     {}\nVolume Effective Time:      {}\n", self.vol_expiration_time,                  self.vol_effective_time)?;
        write!(f, "File Sturcture Version:     {}\nCD-XA Identifier:           {}\n", self.file_struct_version[0],               obtain_string(&self.cd_xa_id))
    }
}

impl VolumeDescriptorTerminator {
    pub fn new() -> VolumeDescriptorTerminator {
        VolumeDescriptorTerminator {
            volume_type: [0xFF],
            std_id:      ['C' as u8, 'D' as u8, '0' as u8, '0' as u8, '1' as u8],
            version:     [0x01],
            reserved:    [0; 2041]
        }
    }
}