pub mod writer;
pub mod read;
use super::time::Time;
use super::super::Error;
use std::ops::Add;

pub enum Specifier {
    File{path: String, format: Format},
    Track{number: u64, data_type: DataType},
    Index{number: u64, time: Time},
    PreGap{time: Time},
}

impl Specifier {
    const FILE_IDENTIFIER:&'static str   = "FILE";
    const TRACK_IDENTIFIER:&'static str  = "TRACK";
    const INDEX_IDENTIFIER:&'static str  = "INDEX";
    const PREGAP_IDENTIFIER:&'static str = "PREGAP";

    pub fn parse_line(string: &str) -> Option<Specifier> {
        let mut slices = string.split_whitespace();

        match slices.next() {
            Some(Self::FILE_IDENTIFIER)   => Self::parse_file(slices),
            Some(Self::TRACK_IDENTIFIER)  => Self::parse_track(slices),
            Some(Self::INDEX_IDENTIFIER)  => Self::parse_index(slices),
            Some(Self::PREGAP_IDENTIFIER) => Self::parse_pregap(slices),
            _ => None,
        }
    }

    fn parse_file(mut slices: std::str::SplitWhitespace) -> Option<Specifier> {
        let mut path = slices.next()?.to_owned();

        if !path.starts_with('"') {
            return None;
        }

        path.remove(0);
        while !path.ends_with('"') {
            path = path.add(slices.next()?);
        }
        path.pop();

        Some(Specifier::File{path, format: Format::parse(slices.next()?)?})
    }

    fn parse_track(mut slices: std::str::SplitWhitespace) -> Option<Specifier> {
        let number    = slices.next()?;
        let data_type = slices.next()?;

        Some(Specifier::Track{number: number.parse().ok()?, data_type: DataType::parse(data_type)?})
    }

    fn parse_index(mut slices: std::str::SplitWhitespace) -> Option<Specifier> {
        let number = slices.next()?;
        let time   = slices.next()?;

        Some(Specifier::Index{number: number.parse().ok()?, time: Time::parse(time)?})
    }

    fn parse_pregap(mut slices: std::str::SplitWhitespace) -> Option<Specifier> {
        let time = slices.next()?;
        
        Some(Specifier::PreGap{time: Time::parse(time)?})
    }
}

impl std::fmt::Display for Specifier {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::File{path, format}       => write!(f, "{} \"{}\" {}", Self::FILE_IDENTIFIER, path, format),
            Self::Track{number, data_type} => write!(f, "{} {:02} {}",  Self::TRACK_IDENTIFIER, number, data_type),
            Self::Index{number, time}      => write!(f, "{} {:02} {}",  Self::INDEX_IDENTIFIER, number, time),
            Self::PreGap{time}             => write!(f, "{} {}",        Self::PREGAP_IDENTIFIER, time),
        }
    }
}

pub enum Format {
    Binary
}

impl Format {
    const BINARY_IDENTIFIER:&'static str = "BINARY";

    pub fn parse(string: &str) -> Option<Format> {
        match string {
            Self::BINARY_IDENTIFIER => Some(Format::Binary),
            _                       => None
        }
    }
}

impl std::fmt::Display for Format {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Binary => write!(f, "{}", Self::BINARY_IDENTIFIER)
        }
    }    
}

#[derive(Clone, Debug)]
pub enum DataType {
    Audio,
    Cdg,
    Mode1_2048,
    Mode1_2353,
    Mode2_2336,
    Mode2_2352,
    Cdi2336,
    Cdi2352
}

impl DataType {
    const      AUDIO_IDENTIFIER:&'static str = "AUDIO";
    const        CDG_IDENTIFIER:&'static str = "CDG";
    const MODE1_2048_IDENTIFIER:&'static str = "MODE1/2048";
    const MODE1_2353_IDENTIFIER:&'static str = "MODE1/2353";
    const MODE2_2336_IDENTIFIER:&'static str = "MODE2/2336";
    const MODE2_2352_IDENTIFIER:&'static str = "MODE2/2352";
    const   CDI_2336_IDENTIFIER:&'static str = "CDI/2336";
    const   CDI_2352_IDENTIFIER:&'static str = "CDI/2352";

    pub fn parse(string: &str) -> Option<DataType> {
        match string {
            Self::AUDIO_IDENTIFIER      => Some(Self::Audio),
            Self::CDG_IDENTIFIER        => Some(Self::Cdg),
            Self::MODE1_2048_IDENTIFIER => Some(Self::Mode1_2048),
            Self::MODE1_2353_IDENTIFIER => Some(Self::Mode1_2353),
            Self::MODE2_2336_IDENTIFIER => Some(Self::Mode2_2336),
            Self::MODE2_2352_IDENTIFIER => Some(Self::Mode2_2352),
            Self::CDI_2336_IDENTIFIER   => Some(Self::Cdi2336),
            Self::CDI_2352_IDENTIFIER   => Some(Self::Cdi2352),
            _                           => None
        }

    }
}

impl std::fmt::Display for DataType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Audio      => f.pad(Self::AUDIO_IDENTIFIER),
            Self::Cdg        => f.pad(Self::CDG_IDENTIFIER),
            Self::Mode1_2048 => f.pad(Self::MODE1_2048_IDENTIFIER),
            Self::Mode1_2353 => f.pad(Self::MODE1_2353_IDENTIFIER),
            Self::Mode2_2336 => f.pad(Self::MODE2_2336_IDENTIFIER),
            Self::Mode2_2352 => f.pad(Self::MODE2_2352_IDENTIFIER),
            Self::Cdi2336    => f.pad(Self::CDI_2336_IDENTIFIER),
            Self::Cdi2352    => f.pad(Self::CDI_2352_IDENTIFIER)
        }
    }    
}

pub struct DataTypeEnd {
    pub r#type: DataType, 
    pub end:    usize
}

impl DataTypeEnd {
    fn new_infinite_entry(r#type: DataType) -> DataTypeEnd {
        DataTypeEnd{r#type, end: usize::MAX}
    }

    fn is_infinite_entry(&self) -> bool {
        self.end == usize::MAX
    }

    pub fn parse_cue_specifier(cue_specifier: Vec<Specifier>) -> Result<Vec<DataTypeEnd>, Error> {
        fn get_prev_if_infinite(parsed_cue: &mut Vec<DataTypeEnd>) -> Option<&mut DataTypeEnd> {
            let cur_size = parsed_cue.len();
            let element  = parsed_cue.get_mut(cur_size - 2)?;
    
            if element.is_infinite_entry() {
                Some(element)
            }
    
            else {
                None
            }
        }
    
        let mut parsed_cue = Vec::new();
    
        for specifier in cue_specifier {
            match specifier {
                Specifier::Track{number: _, data_type} => {
                    match data_type {
                        DataType::Audio | DataType::Mode2_2352 => parsed_cue.push(DataTypeEnd::new_infinite_entry(data_type)),
                        _                                      => return Err(Error::CUEDataTypeNotSupported(data_type)),
                    }
                }
                Specifier::Index{number:_, time} => {
                    if let Some(prev_entry) = get_prev_if_infinite(&mut parsed_cue) {
                        prev_entry.end = time.to_lba();
                    }                
                },
                _ => (),
            }
        }
        Ok(parsed_cue)
    }
}