use bitflags::bitflags;
use tool_helper::{raw::RawConversion, Error};

#[repr(packed)] 
#[derive(Clone)]
pub struct VAGHeader {
    _id:                 [u8; 4],
    version:             u32,
    _reserved:           u32,
    data_size:           u32,
    sampling_frequency:  u32,
    _reserved2:          [u8; 12],
    _name:               [u8; 16]
}

impl VAGHeader {
    const SIZE:      usize    = std::mem::size_of::<VAGHeader>();
    const ID:        [u8; 4]  = ['V' as u8, 'A' as u8, 'G' as u8, 'p' as u8];
    const VERSION:   u32      = 0x02;
    const RESERVED:  u32      = 0;
    const RESERVED2: [u8; 12] = [0; 12];

    pub fn expected_vagadpcm_samples(adpcm_samples: u32) -> u32 {
        ((adpcm_samples as usize + (VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM - 1))/VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM) as u32
    }

    pub fn create(vagadpcm_samples: u32, sampling_frequency: u32, name: &str) -> Result<VAGHeader, Error> {
        let data_size = vagadpcm_samples*VAGADPCM::SIZE as u32;
        let name      = {
            if !name.is_ascii() {
                return Err(Error::from_text(format!("File name {} is not ascii", name)));
            }

            let     name_length = if name.len() > 16 {16} else {name.len()};
            let mut new_name    = [0u8; 16];

            new_name[..name_length].copy_from_slice(&name.as_bytes()[..name_length]);
            new_name
        };

        Ok(VAGHeader{
            _id:                 VAGHeader::ID,
            version:             VAGHeader::VERSION,
            _reserved:           VAGHeader::RESERVED,
            data_size:           data_size,
            sampling_frequency:  sampling_frequency,
            _reserved2:          VAGHeader::RESERVED2,
            _name:               name
        })
    }
}

impl RawConversion<{VAGHeader::SIZE}> for VAGHeader {
    fn convert_to_raw(&self) -> [u8; VAGHeader::SIZE] {
        unsafe {
            let mut vag_header = self.clone();

            vag_header.version            = self.version.to_be();
            vag_header.data_size          = self.data_size.to_be();
            vag_header.sampling_frequency = self.sampling_frequency.to_be();

            let data: [u8; VAGHeader::SIZE] = std::mem::transmute(vag_header);
            data
        }
    }
}

bitflags! {
    struct VAGFlagBits : u8 {
        const LoopEnd   = (1 << 0);
        const Repeat    = (1 << 1);
        const LoopStart = (1 << 2);
    }
}

pub struct VAGADPCM {
    data: [u32; 4]
}

impl VAGADPCM {
               const SIZE:                      usize = std::mem::size_of::<VAGADPCM>();
    pub(super) const ADPCM_SAMPLES_PER_VAGADPCM:usize = 28;

    pub fn empty() -> VAGADPCM {
        VAGADPCM{data: [0; 4]}    
    }

    fn create_for_filter_shift(filter: u32, shift: u32) -> VAGADPCM {
        VAGADPCM{data: [(12 - shift) | filter << 4, 0, 0, 0]}
    }

    pub fn end() -> VAGADPCM {
        VAGADPCM::empty().set_loop_self()
    }

    pub fn create(samples: ADPCMSampleForVag, lpc_tap: LPC) -> (VAGADPCM, LPC) {
        fn cap_value(value: i32, min: i32, max: i32) -> i32 {
            if value < min {
                min
            }

            else if value > max {
                max
            }

            else {
                value
            }
        }
        let mut best_frame = VAGADPCM::empty();
        let mut best_tap   = LPC::empty();
        let mut best_error = std::u64::MAX as u64;

        for (filter_id, filter) in LPC::FILTERS.iter().enumerate() {
            for shift in 0..=12 {
                let mut this_frame = VAGADPCM::create_for_filter_shift(filter_id as u32, shift);
                let     this_tap   = lpc_tap.clone();
                let mut this_error = 0;

                for (sample_id, sample) in samples.iter().enumerate() {
                    let x = *sample as i32;
                    let p = (this_tap.first*filter.first + this_tap.second*filter.second + 32) >> 6;
                    let r = x - p;
                    let q = cap_value((r + (((1 << shift) - (r < 0) as i32) >> 1)) >> shift, -8, 7);
                    let y = cap_value(p + (q << shift), std::i16::MIN as i32, std::i16::MAX as i32);
                    let e = y - x;

                    this_frame.data[(sample_id+4)/8] |= ((q&0xF) << (((sample_id + 4)%8)*4)) as u32;
                    this_error += (e*e) as u64;
                }

                if this_error < best_error {
                    best_tap   = this_tap;
                    best_error = this_error;
                    best_frame = this_frame;
                }
            }
        }
        (best_frame, best_tap)    
    }

    fn set_vag_flags(&mut self, flags: VAGFlagBits) {
        let mut first_sample = self.data[0].to_ne_bytes();
    
        first_sample[1] = flags.bits();
        self.data[0]    = u32::from_ne_bytes(first_sample);
    }

    pub fn set_loop_start(mut self) -> Self {
        self.set_vag_flags(VAGFlagBits::LoopStart);
        self
    }

    pub fn set_loop_end_repeat(mut self) -> Self {
        self.set_vag_flags(VAGFlagBits::LoopEnd | VAGFlagBits::Repeat);
        self
    }

    pub fn set_loop_self(mut self) -> Self {
        self.set_vag_flags(VAGFlagBits::LoopStart | VAGFlagBits::LoopEnd);
        self
    }
}

impl RawConversion<{VAGADPCM::SIZE}> for VAGADPCM {
    fn convert_to_raw(&self) -> [u8; VAGADPCM::SIZE] {
        unsafe {
            let data: [u8; VAGADPCM::SIZE] = std::mem::transmute_copy(&self as &VAGADPCM);
            data
        }
    }    
}

#[derive(Clone)]
pub struct LPC {
    first:  i32,
    second: i32
}

impl LPC {
    const FILTERS: [LPC; 5] = [
        LPC{first:   0, second:   0},
        LPC{first:  60, second:   0},
        LPC{first: 115, second: -52},
        LPC{first:  98, second: -55},
        LPC{first: 122, second: -60}
    ];

    pub fn empty() -> LPC {
        LPC{first: 0, second: 0}
    }
}

pub type ADPCMSampleForVag = [i16; VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM];

pub struct MonoADPCMIterator<I: std::iter::Iterator<Item=Result<i16, hound::Error>>> {
    iter: I
}

impl<I:std::iter::Iterator<Item=Result<i16, hound::Error>>> MonoADPCMIterator<I>{
    pub fn create(iter: I) -> MonoADPCMIterator<I> {
        MonoADPCMIterator{iter}
    }
}

impl<I:std::iter::Iterator<Item=Result<i16, hound::Error>>> std::iter::Iterator for MonoADPCMIterator<I> {
    type Item = Result<[i16; VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM], Error>;

    fn next(&mut self) -> Option<Self::Item> {
        const STREAM_GONE_ERROR: &'static str = "Reading ADPCM sample failed";

        if let Some(next_sample) = self.iter.next() {
            let Ok(next_sample) = next_sample else {return Some(Err(Error::from_str(STREAM_GONE_ERROR)));};

            let mut sample = [0;VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM];
            sample[0] = next_sample;

            for idx in 1..VAGADPCM::ADPCM_SAMPLES_PER_VAGADPCM {
                let Ok(next_sample) = self.iter.next().unwrap_or(Ok(0)) else {return Some(Err(Error::from_str(STREAM_GONE_ERROR)));};
                sample[idx] = next_sample;
            }
            return Some(Ok(sample));
        }
        None
    }
}