use super::{Error, Sector, SectorWriter};
use cdtypes::types::{cue::DataType as CueDataType, cue::Format as CueFormat, cue::Specifier as CueSpecifier, cue::writer::write_generic as cue_write_generic, time::Time};
use std::{path::PathBuf, io::Write};
use tool_helper::get_file_name_from_path_buf;

pub struct BinCueWriter {
    bin_out: std::boxed::Box<dyn Write>,
    cd_time: Time,
    track:   u64,
    cue:     Vec<CueSpecifier>,
}

impl BinCueWriter {
    pub fn new<T: Write + 'static>(writer: T, bin_path: &PathBuf) -> Result<BinCueWriter, Error> {
        Ok(BinCueWriter{bin_out: std::boxed::Box::new(writer), cd_time: Time::cd_start(), track: 1, cue: Self::default_cue(bin_path)?})
    }

    pub fn write_cue<T: Write>(self, dst: T) -> Result<(), Error> {
        Ok(cue_write_generic(dst, self.cue)?)
    }

    fn finalize(&mut self, sector: &mut Sector) -> Result<(), Error> {
        sector.finalize();

        if let Some(header) = sector.get_header_mut() {
            header.set_time(self.cd_time.clone());
        }

        self.cd_time.add_sector()?;
        Ok(())
    }

    fn default_cue(bin_path: &PathBuf) -> Result<Vec<CueSpecifier>, Error> {
        let mut cue = Vec::new();

        cue.push(CueSpecifier::File{path: get_file_name_from_path_buf(bin_path, "bin output path")?, format: CueFormat::Binary});
        cue.push(CueSpecifier::Track{number: 1, data_type: CueDataType::Mode2_2352});
        cue.push(CueSpecifier::Index{number: 1, time: Time::cue_start()});
        Ok(cue)
    }
}

impl SectorWriter for BinCueWriter {
    fn write(&mut self, mut sector: Sector) -> Result<usize, Error> {
        self.finalize(&mut sector)?;

        Ok(self.bin_out.write(sector.as_raw())?)
    }

    fn cd_da_start(&mut self) -> Result<(), Error> {
        self.track += 1;
        self.cue.push(CueSpecifier::Track{number: self.track, data_type: CueDataType::Audio});
        
        if self.track == 2 {
            self.cue.push(CueSpecifier::PreGap{time: Time::cd_pregap()});
        }

        else {
            self.cue.push(CueSpecifier::Index{number: 0, time: self.cd_time.clone()});
            self.cd_time.add_sectors(Time::cd_pregap().as_sectors())?;
        }
        self.cue.push(CueSpecifier::Index{number: 1, time: self.cd_time.clone()});
        Ok(())
    }

    fn sectors_to_next_second(&self) -> usize {
        let (_, _, sectors) = self.cd_time.get();
        Time::MAX_SECTORS - sectors as usize
    }
}