pub mod reader;
pub mod sector;

use sector::*;
use reader::{UnknownSectorBehavior, Reader as CDReader};
use super::{Error, types as types};
use types::{cue::{Specifier, DataType, DataTypeEnd}, lsb_msb::ReadWriteEndian, pvd::PrimaryVolumeDescriptor, sector::{Mode2Form1, SubHeaderForm}};
use std::fs::File;

pub struct DirectoryRecordIterator<'a> {
    parent:        &'a CD,
    cur_sector_id: usize,
    cur_data:      &'a [u8],
    is_last:       bool
}

impl<'a> DirectoryRecordIterator<'a> {
    pub fn new(parent: &CD, sector: usize) -> DirectoryRecordIterator {
        let mut new_iter = DirectoryRecordIterator{parent: parent, cur_sector_id: sector, cur_data: &[], is_last: true};

        if let Some((data, is_last)) = new_iter.load(sector) {
            new_iter.cur_data = data;
            new_iter.is_last  = is_last;
        }

        new_iter
    }

    fn load(&self, sector_id: usize) -> Option<(&'a [u8], bool)> {
        if let Some(sector) = self.parent.get_sector(sector_id) {
            if let Some(sub_header) = sector.get_sub_header() {
                return Some((sector.get_data(), sub_header.sub_mode.is_eof()));
            }

            else {
                return None;
            }
        }

        else {
            return None;
        }       
    }
}

impl<'a> std::iter::Iterator for DirectoryRecordIterator<'a> {
    type Item = &'a types::dir_record::DirectoryRecord;

    fn next(&mut self) -> Option<Self::Item> {
        if self.cur_data.is_empty() {
            if self.is_last {
                None
            }

            else {
                self.cur_sector_id += 1;
                if let Some((data, is_last)) = self.load(self.cur_sector_id) {
                    self.cur_data = data;
                    self.is_last  = is_last;

                    self.next()
                }

                else {
                    None
                }
            }
        }

        else {
            let mut data_range = self.cur_data.as_ptr_range();
            unsafe {
                if let Some(cur_dir) = (data_range.start as *const types::dir_record::DirectoryRecord).as_ref() {
                    let dir_length = cur_dir.length[0] as usize;
                    if dir_length == 0 {
                        self.cur_data = &[];
                        self.next()
                    }

                    else {
                        data_range.start = data_range.start.add(dir_length);

                        self.cur_data = std::slice::from_raw_parts(data_range.start, data_range.end.offset_from(data_range.start) as usize);
                        Some(cur_dir)
                    }
                }

                else {
                    None
                }
            }
        }
    }
}

pub struct PathTableIterator<'a> {
    path_table: Box<[u8]>,
    cur_idx:    usize,
    phantom:    std::marker::PhantomData<&'a u8>
}

impl<'a> PathTableIterator<'a> {
    pub fn new(cd: &CD, mut sector: usize) -> PathTableIterator {
        let mut data = Vec::<u8>::new();

        loop {
            if let Some(sector) = cd.get_sector(sector) {
                if let Some(sub_header) = sector.get_sub_header() {
                    for byte in sector.get_data() {
                        data.push(*byte);
                    }
                    
                    if sub_header.sub_mode.is_eof() {
                        return PathTableIterator{path_table: data.into_boxed_slice(), cur_idx: 0usize, phantom: std::marker::PhantomData::default()};
                    }
                }

                else {
                    return PathTableIterator::default();
                }
            }

            else {
                return PathTableIterator::default();
            }
            sector += 1;
        }
    }
}

impl<'a> std::iter::Iterator for PathTableIterator<'a> {
    type Item = &'a types::path_table::PathTableL;

    fn next(&mut self) -> Option<Self::Item> {
        if (self.cur_idx + std::mem::size_of::<Self::Item>()) < self.path_table.len() {
            unsafe {
                if let Some(entry) = (self.path_table.as_ptr().add(self.cur_idx) as *const types::path_table::PathTableL).as_ref() {
                    if entry.name_length[0] > 0 {
                        self.cur_idx += entry.get_size();
                        Some(entry)                        
                    }

                    else {
                        None
                    }
                }

                else {
                    None
                }
            }
        }

        else {
            None
        }
    }
}

impl<'a> std::default::Default for PathTableIterator<'a> {
    fn default() -> PathTableIterator<'a> {
        PathTableIterator{path_table: Box::default(), cur_idx: 0usize, phantom: std::marker::PhantomData::default()}
    }
}

pub struct SectorIterator<'a> {
    parent:      &'a CD,
    cur_sec_idx: usize,
}

impl<'a> SectorIterator<'a> {
    pub fn new(cd: &'a CD) -> SectorIterator<'a> {
        SectorIterator{parent: cd, cur_sec_idx: 0}
    }
}

impl<'a> std::iter::Iterator for SectorIterator<'a> {
    type Item = &'a Sector;

    fn next(&mut self) -> Option<Self::Item> {
        let value = self.parent.get_sector(self.cur_sec_idx);

        self.cur_sec_idx += 1;
        value
    }
}

pub struct CD {
    sectors:    Vec<Sector>,
    track_info: Vec<TrackInfo>,
}

impl CD {
    pub fn from_file(file: File, specifier: Option<Vec<Specifier>>, unk_sector_behav: UnknownSectorBehavior) -> Result<CD, Error> {
        let     data_type_ends = {
            if let Some(specifier) = specifier {
                DataTypeEnd::parse_cue_specifier(specifier)?
            }

            else {
                Vec::new()
            }
        };
        let     reader  = CDReader::new(file, &data_type_ends, unk_sector_behav)?;
        let mut sectors = Vec::new();
        
        for sector in reader {
            sectors.push(sector?);
        }

        Ok(CD{sectors, track_info: TrackInfo::new(data_type_ends)})
    }

    pub fn sector_count(&self) -> usize {
        self.sectors.len()
    }

    pub fn last_sector_idx(&self) -> usize {
        Self::sector_count(self) - 1
    }

    pub fn get_sector(&self, idx: usize) -> Option<&Sector> {
        self.sectors.get(idx)
    }

    pub fn has_tracks(&self) -> bool {
        !self.track_info.is_empty()
    }

    pub fn get_primary_volume_descriptor(&self) -> Result<&PrimaryVolumeDescriptor, Error> {
        if let Some(pvd_sec) = self.get_sector(16) {
            if let Sector::CDXAData(pvd_sec) = pvd_sec {
                unsafe {
                    match pvd_sec.sub_header.get_form()  {
                        SubHeaderForm::Form1 => Ok(std::mem::transmute::<&[u8; Mode2Form1::DATA_SIZE], &PrimaryVolumeDescriptor>(&pvd_sec.data)),
                        _                    => Err(Error::GenericError("Sector is not CD-XA Data Form 1".to_string()))

                    }
                }
            }

            else {
                Err(Error::GenericError("Sector is not CD-XA Data".to_string()))
            }
        }

        else {
            Err(Error::GenericError("Sector not found".to_string()))
        }
    }

    pub fn directory_record_iter(&self, sector: usize) -> DirectoryRecordIterator {
        DirectoryRecordIterator::new(self, sector)
    }

    pub fn path_table_iter(&self) -> PathTableIterator {
        if let Ok(pvd) = self.get_primary_volume_descriptor() {
            PathTableIterator::new(self, pvd.path_table_1.read() as usize)
        }

        else {
            PathTableIterator::default()
        }
    }

    pub fn sector_iter(&self) -> SectorIterator {
        SectorIterator::new(self)
    }

    pub fn track_info_iter(&self) -> std::slice::Iter<TrackInfo> {
        self.track_info.iter()
    }
}

pub struct TrackInfo {
    pub r#type: DataType,
    pub start:  usize,
}

impl TrackInfo {
    pub fn new(data_type_ends: Vec<DataTypeEnd>) -> Vec<TrackInfo> {
        let mut data  = Vec::new();
        let mut start = 0;

        for data_type_end in data_type_ends {
            data.push(TrackInfo{r#type: data_type_end.r#type, start});
            start = data_type_end.end;
        }

        data
    }
}