use super::{sector::Sector, types::time::Time};
use super::super::{Error, types::{cue::{DataType, DataTypeEnd}, sector::*}};
use std::io::Read;

#[derive(Copy, Clone)]
pub enum UnknownSectorBehavior {
    CauseError,
    CauseWarningAddEmpty,
    TreatAsAudio,
}

pub struct Reader<'a> {
    file:                std::fs::File,
    data_type_guide: &'a Vec<DataTypeEnd>,
    lba:                 usize,
    unk_sector_behav:    UnknownSectorBehavior,
    active:              bool
}

impl<'a> Reader<'a> {
    const CD_PREGAP_SECTORS:usize = Time::cd_pregap().as_sectors();

    pub fn new(file: std::fs::File, data_type_guide: &'a Vec<DataTypeEnd>, unk_sector_behav: UnknownSectorBehavior) -> Result<Reader, Error> {
        Ok(Reader{
            file, data_type_guide, unk_sector_behav, lba: Self::CD_PREGAP_SECTORS, active: true
        })
    }

    fn next_data_type(&mut self) -> Option<DataType> {
        self.lba += 1;
        for data_type in self.data_type_guide.iter() {
            if self.lba < data_type.end {
                return Some(data_type.r#type.clone());
            }
        }
        None
    }

    fn process_audio_sector(&mut self, _unk_sector_behav: UnknownSectorBehavior) -> Result<Option<Sector>, Error> {
        let mut buffer = [0;std::mem::size_of::<Audio>()];
        let     bytes  = self.file.read(&mut buffer)?;

        if bytes == 0 {
            Ok(None)
        }
        
        else {
            unsafe {
                Ok(Some(Sector::Audio(std::mem::transmute::<[u8; std::mem::size_of::<Audio>()], Audio>(buffer))))
            }
        }
    }

    fn process_binary_sector(&mut self, unk_sector_behav: UnknownSectorBehavior) -> Result<Option<Sector>, Error> {
        fn make_ok_some(sector: Sector) -> Result<Option<Sector>, Error> {
            Ok(Some(sector))
        }

        let mut buffer    = [0;SECTOR_SIZE];
        let     bytes     = self.file.read(&mut buffer)?;
        let     sector_id = self.lba - 1 - Self::CD_PREGAP_SECTORS;

        if bytes == 0 {
            return Ok(None);
        }

        if bytes != SECTOR_SIZE {
            return Err(Error::GenericError(format!("Failed to read in a complete sector for sector: {}", sector_id)));
        }

        unsafe {
            let sector = &*(buffer.as_ptr() as *const Mode0);
            let mode   = sector.header.get_mode();

            match mode {
                Ok(HeaderMode::Mode0) => make_ok_some(Sector::Empty(std::mem::transmute::<[u8;std::mem::size_of::<Mode0>()], Mode0>(buffer))),
                Ok(HeaderMode::Mode1) => make_ok_some(Sector::CDData(std::mem::transmute::<[u8;std::mem::size_of::<Mode1>()], Mode1>(buffer))),
                Ok(HeaderMode::Mode2) => {
                    let sector = &*(buffer.as_ptr() as *const Mode2);
                    match sector.sub_header.get_form() {
                        SubHeaderForm::Form1 => make_ok_some(Sector::CDXAData(std::mem::transmute::<[u8;std::mem::size_of::<Mode2Form1>()], Mode2Form1>(buffer))),
                        SubHeaderForm::Form2 => make_ok_some(Sector::CDXAAudio(std::mem::transmute::<[u8;std::mem::size_of::<Mode2Form2>()], Mode2Form2>(buffer))),
                    }
                }
                Err(mode) => {
                    let error_str = format!("Unknown Mode: {} @Sector: {}", mode, sector_id);
                    match unk_sector_behav {
                        UnknownSectorBehavior::CauseError           => {
                            return Err(Error::TypeError(error_str));
                        },
                        UnknownSectorBehavior::CauseWarningAddEmpty => {
                            println!("Warning: {}", error_str);
                            return make_ok_some(Sector::Empty(Mode0::new()));
                        }
                        UnknownSectorBehavior::TreatAsAudio         => {
                            return make_ok_some(Sector::Audio(std::mem::transmute::<[u8; std::mem::size_of::<Audio>()], Audio>(buffer)));
                        }
                    }
                }
            }            
        }
    }
}

impl<'a> std::iter::Iterator for Reader<'a> {
    type Item = Result<Sector, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.active {
            let function = {
                match self.next_data_type() {
                    Some(DataType::Audio)      => Self::process_audio_sector,
                    None |
                    Some(DataType::Mode2_2352) => Self::process_binary_sector,
                    Some(data_type)            => return Some(Err(Error::CUEDataTypeNotSupported(data_type)))
                }
            };

            match function(self, self.unk_sector_behav) {
                Ok(sector) => {
                    if let Some(sector) = sector {
                        Some(Ok(sector))
                    }

                    else {
                        None
                    }
                },
                Err(error) => {
                    self.active = false;
                    return Some(Err(error));
                }
            }
        }

        else {
            None
        }
    }
}