use std::collections::HashMap;
use std::io;
use std::io::SeekFrom::{Current, Start};
use std::io::{Read, Seek};

use byteorder::LittleEndian;
use byteorder::ReadBytesExt;

use super::errors::Error;
use super::fourcc::{FourCC, ReadFourCC};
use super::fourcc::{BW64_SIG, DATA_SIG, DS64_SIG, RF64_SIG, RIFF_SIG, WAVE_SIG};

// just for your reference...
// RF64 documentation https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf

// EBU long files being with RF64, and the ITU recommends using BW64, so we recorgnize both.

const RF64_SIZE_MARKER: u32 = 0xFF_FF_FF_FF;

#[derive(Debug)]
pub enum Event {
    StartParse,
    ReadHeader {
        signature: FourCC,
        length_field: u32,
    },
    ReadRF64Header {
        signature: FourCC,
    },
    ReadDS64 {
        file_size: u64,
        long_sizes: HashMap<FourCC, u64>,
    },
    BeginChunk {
        signature: FourCC,
        content_start: u64,
        content_length: u64,
    },
    Failed {
        error: Error,
    },
    FinishParse,
}

#[derive(Debug)]
enum State {
    New,
    ReadyForHeader,
    ReadyForDS64,
    ReadyForChunk { at: u64, remaining: u64 },
    Error,
    Complete,
}

pub struct Parser<R: Read + Seek> {
    stream: R,
    state: State,
    ds64state: HashMap<FourCC, u64>,
}

#[derive(Debug, PartialEq, Eq)]
pub struct ChunkIteratorItem {
    pub signature: FourCC,
    pub start: u64,
    pub length: u64,
}

impl<R: Read + Seek> Parser<R> {
    // wraps a stream
    pub fn make(stream: R) -> Result<Self, Error> {
        let newmap: HashMap<FourCC, u64> = HashMap::new();
        let mut the_stream = stream;
        the_stream.seek(Start(0))?;
        Ok(Parser {
            stream: the_stream,
            state: State::New,
            ds64state: newmap,
        })
    }

    // pub fn into_inner(self) -> R {
    //     self.stream
    // }

    pub fn into_chunk_iterator(self) -> impl Iterator<Item = Result<ChunkIteratorItem, Error>> {
        self.filter_map({
            |event| {
                if let Event::BeginChunk {
                    signature,
                    content_start,
                    content_length,
                } = event
                {
                    Some(Ok(ChunkIteratorItem {
                        signature,
                        start: content_start,
                        length: content_length,
                    }))
                } else if let Event::Failed { error } = event {
                    Some(Err(error))
                } else {
                    None
                }
            }
        })
    }

    pub fn into_chunk_list(self) -> Result<Vec<ChunkIteratorItem>, Error> {
        let mut error = Ok(());

        let chunks = self
            .into_chunk_iterator()
            .scan(&mut error, |err, res| match res {
                Ok(ok) => Some(ok),
                Err(e) => {
                    **err = Err(e);
                    None
                }
            })
            .collect();

        error?;

        Ok(chunks)
    }
}

impl<R: Read + Seek> Iterator for Parser<R> {
    type Item = Event;

    fn next(&mut self) -> Option<Event> {
        let (event, next_state) = self.advance();
        self.state = next_state;
        event
    }
}

impl<R: Read + Seek> Parser<R> {
    fn parse_header(&mut self) -> Result<(Event, State), io::Error> {
        let file_sig = self.stream.read_fourcc()?;
        let length = self.stream.read_u32::<LittleEndian>()?;
        let list_sig = self.stream.read_fourcc()?;

        let event: Event;
        let next_state: State;

        match (file_sig, length, list_sig) {
            (RIFF_SIG, size, WAVE_SIG) => {
                event = Event::ReadHeader {
                    signature: file_sig,
                    length_field: size,
                };

                next_state = State::ReadyForChunk {
                    at: 12,
                    remaining: (length - 4) as u64,
                };
            }
            (RF64_SIG, RF64_SIZE_MARKER, WAVE_SIG) | (BW64_SIG, RF64_SIZE_MARKER, WAVE_SIG) => {
                event = Event::ReadRF64Header {
                    signature: file_sig,
                };

                next_state = State::ReadyForDS64;
            }
            _ => {
                event = Event::Failed {
                    error: Error::HeaderNotRecognized,
                };
                next_state = State::Error;
            }
        }

        Ok((event, next_state))
    }

    fn parse_ds64(&mut self) -> Result<(Event, State), Error> {
        let at: u64 = 12;

        let ds64_sig = self.stream.read_fourcc()?;
        let ds64_size = self.stream.read_u32::<LittleEndian>()? as u64;
        let mut read: u64 = 0;

        if ds64_sig != DS64_SIG {
            Err(Error::MissingRequiredDS64)
        } else {
            let long_file_size = self.stream.read_u64::<LittleEndian>()?;
            let long_data_size = self.stream.read_u64::<LittleEndian>()?;
            let _long_frame_count = self.stream.read_u64::<LittleEndian>(); // dead frame count field
            read += 24;

            let field_count = self.stream.read_u32::<LittleEndian>()?;
            read += 4;

            for _ in 0..field_count {
                let this_fourcc = self.stream.read_fourcc()?;
                let this_field_size = self.stream.read_u64::<LittleEndian>()?;
                self.ds64state.insert(this_fourcc, this_field_size);
                read += 12;
            }

            self.ds64state.insert(DATA_SIG, long_data_size);

            if read < ds64_size {
                /*  for some reason the ds64 chunk returned by Pro Tools is longer than
                    it should be but it's all zeroes so... skip.

                    For the record libsndfile seems to do the same thing...
                    https://github.com/libsndfile/libsndfile/blob/08d802a3d18fa19c74f38ed910d9e33f80248187/src/rf64.c#L230
                */
                let _ = self.stream.seek(Current((ds64_size - read) as i64));
            }

            let event = Event::ReadDS64 {
                file_size: long_file_size,
                long_sizes: self.ds64state.clone(),
            };

            let state = State::ReadyForChunk {
                at: at + 8 + ds64_size,
                remaining: long_file_size - (4 + 8 + ds64_size),
            };

            Ok((event, state))
        }
    }

    fn enter_chunk(&mut self, at: u64, remaining: u64) -> Result<(Event, State), io::Error> {
        let event;
        let state;

        if remaining == 0 {
            event = Event::FinishParse;
            state = State::Complete;
        } else {
            let this_fourcc = self.stream.read_fourcc()?;
            let this_size: u64;

            if self.ds64state.contains_key(&this_fourcc) {
                this_size = self.ds64state[&this_fourcc];
                let _skip = self.stream.read_u32::<LittleEndian>()? as u64;
            } else {
                this_size = self.stream.read_u32::<LittleEndian>()? as u64;
            }

            let this_displacement: u64 = if this_size % 2 == 1 {
                this_size + 1
            } else {
                this_size
            };
            self.stream.seek(Current(this_displacement as i64))?;

            event = Event::BeginChunk {
                signature: this_fourcc,
                content_start: at + 8,
                content_length: this_size,
            };

            state = State::ReadyForChunk {
                at: at + 8 + this_displacement,
                remaining: remaining - 8 - this_displacement,
            }
        }

        Ok((event, state))
    }

    fn handle_state(&mut self) -> Result<(Option<Event>, State), Error> {
        match self.state {
            State::New => Ok((Some(Event::StartParse), State::ReadyForHeader)),
            State::ReadyForHeader => {
                let (event, state) = self.parse_header()?;
                Ok((Some(event), state))
            }
            State::ReadyForDS64 => {
                let (event, state) = self.parse_ds64()?;
                Ok((Some(event), state))
            }
            State::ReadyForChunk { at, remaining } => {
                let (event, state) = self.enter_chunk(at, remaining)?;
                Ok((Some(event), state))
            }
            State::Error => Ok((Some(Event::FinishParse), State::Complete)),
            State::Complete => Ok((None, State::Complete)),
        }
    }

    fn advance(&mut self) -> (Option<Event>, State) {
        match self.handle_state() {
            Ok((event, state)) => (event, state),
            Err(error) => (Some(Event::Failed { error }), State::Error),
        }
    }
}
