use crate::track::*;
use crate::*;

use ffms2_sys::*;

use std::default::Default;
use std::ffi::c_char;
use std::ffi::CString;
use std::mem;
use std::os::raw::c_void;
use std::panic;
use std::path::Path;
use std::process;

pub struct Index {
    index: *mut FFMS_Index,
    buffer: Vec<u8>,
}

unsafe impl Send for Index {}

impl Index {
    pub fn new(IndexFile: &Path) -> Result<Self, Error> {
        let source = CString::new(IndexFile.to_str().unwrap()).unwrap();
        let mut error: Error = Default::default();
        let index =
            unsafe { FFMS_ReadIndex(source.as_ptr(), error.as_mut_ptr()) };

        if index.is_null() {
            Err(error)
        } else {
            Ok(Index {
                index,
                buffer: Vec::new(),
            })
        }
    }

    pub fn ErrorHandling(&self) -> IndexErrorHandling {
        let index_error_handling =
            unsafe { FFMS_GetErrorHandling(self.index) };
        match index_error_handling {
            FFMS_IndexErrorHandling::FFMS_IEH_ABORT => {
                IndexErrorHandling::IEH_ABORT
            }
            FFMS_IndexErrorHandling::FFMS_IEH_CLEAR_TRACK => {
                IndexErrorHandling::IEH_CLEAR_TRACK
            }
            FFMS_IndexErrorHandling::FFMS_IEH_STOP_TRACK => {
                IndexErrorHandling::IEH_STOP_TRACK
            }
            FFMS_IndexErrorHandling::FFMS_IEH_IGNORE => {
                IndexErrorHandling::IEH_IGNORE
            }
        }
    }

    pub fn ReadIndexFromBuffer(Buffer: &[u8]) -> Result<Self, Error> {
        let mut error: Error = Default::default();
        let size = mem::size_of_val(Buffer);
        let index = unsafe {
            FFMS_ReadIndexFromBuffer(Buffer.as_ptr(), size, error.as_mut_ptr())
        };

        if index.is_null() {
            Err(error)
        } else {
            Ok(Index {
                index,
                buffer: Vec::new(),
            })
        }
    }

    pub fn IndexBelongsToFile(&self, SourceFile: &Path) -> Result<(), Error> {
        let source = CString::new(SourceFile.to_str().unwrap()).unwrap();
        let mut error: Error = Default::default();
        let err = unsafe {
            FFMS_IndexBelongsToFile(
                self.index,
                source.as_ptr(),
                error.as_mut_ptr(),
            )
        };

        if err != 0 {
            Err(error)
        } else {
            Ok(())
        }
    }

    pub fn WriteIndex(&self, SourceFile: &Path) -> Result<(), Error> {
        let source = CString::new(SourceFile.to_str().unwrap()).unwrap();
        let mut error: Error = Default::default();
        let err = unsafe {
            FFMS_WriteIndex(source.as_ptr(), self.index, error.as_mut_ptr())
        };

        if err != 0 {
            Err(error)
        } else {
            Ok(())
        }
    }

    pub fn WriteIndexToBuffer(&mut self) -> Result<&Vec<u8>, Error> {
        let mut error: Error = Default::default();
        let mut size = 0;
        let err = unsafe {
            FFMS_WriteIndexToBuffer(
                &mut self.buffer.as_mut_ptr(),
                &mut size,
                self.index,
                error.as_mut_ptr(),
            )
        };

        if err != 0 {
            Err(error)
        } else {
            Ok(&self.buffer)
        }
    }

    pub fn FirstTrackOfType(
        &self,
        TrackType: TrackType,
    ) -> Result<usize, Error> {
        let mut error: Error = Default::default();
        let num_tracks = unsafe {
            FFMS_GetFirstTrackOfType(
                self.index,
                TrackType::to_track_type(TrackType) as i32,
                error.as_mut_ptr(),
            )
        };
        if num_tracks < 0 {
            Err(error)
        } else {
            Ok(num_tracks as usize)
        }
    }

    pub fn FirstIndexedTrackOfType(
        &self,
        TrackType: TrackType,
    ) -> Result<usize, Error> {
        let mut error: Error = Default::default();
        let num_tracks = unsafe {
            FFMS_GetFirstIndexedTrackOfType(
                self.index,
                TrackType::to_track_type(TrackType) as i32,
                error.as_mut_ptr(),
            )
        };
        if num_tracks < 0 {
            Err(error)
        } else {
            Ok(num_tracks as usize)
        }
    }

    pub fn NumTracks(&self) -> usize {
        unsafe { FFMS_GetNumTracks(self.index) as usize }
    }

    pub(crate) fn as_mut_ptr(&self) -> *mut FFMS_Index {
        self.index
    }
}

impl Drop for Index {
    fn drop(&mut self) {
        unsafe {
            if !self.buffer.is_empty() {
                FFMS_FreeIndexBuffer(&mut self.buffer.as_mut_ptr());
            }
            FFMS_DestroyIndex(self.index);
        }
    }
}

pub struct Indexer {
    indexer: *mut FFMS_Indexer,
}

unsafe impl Send for Indexer {}

impl Indexer {
    pub fn new(SourceFile: &Path) -> Result<Self, Error> {
        let source = CString::new(SourceFile.to_str().unwrap()).unwrap();
        let mut error: Error = Default::default();
        let indexer =
            unsafe { FFMS_CreateIndexer(source.as_ptr(), error.as_mut_ptr()) };

        if indexer.is_null() {
            Err(error)
        } else {
            Ok(Indexer { indexer })
        }
    }

    pub fn CodecNameI(&self, Track: usize) -> String {
        let c_ptr = unsafe { FFMS_GetCodecNameI(self.indexer, Track as i32) };
        let c_str = unsafe { CString::from_raw(c_ptr as *mut c_char) };
        c_str.to_str().unwrap().to_owned()
    }

    pub fn FormatNameI(&self) -> String {
        let c_ptr = unsafe { FFMS_GetFormatNameI(self.indexer) };
        let c_str = unsafe { CString::from_raw(c_ptr as *mut c_char) };
        c_str.to_str().unwrap().to_owned()
    }

    pub fn NumTracksI(&self) -> usize {
        unsafe { FFMS_GetNumTracksI(self.indexer) as usize }
    }

    pub fn TrackTypeI(&self, Track: usize) -> TrackType {
        let track_type =
            unsafe { FFMS_GetTrackTypeI(self.indexer, Track as i32) };
        TrackType::from_i32(track_type)
    }

    pub fn CancelIndexing(&self) {
        unsafe {
            FFMS_CancelIndexing(self.indexer);
        }
    }

    pub fn DoIndexing2(
        &self,
        ErrorHandling: IndexErrorHandling,
    ) -> Result<Index, Error> {
        let mut error: Error = Default::default();
        let handling = IndexErrorHandling::to_idx_errors(ErrorHandling) as i32;
        let index = unsafe {
            FFMS_DoIndexing2(self.indexer, handling, error.as_mut_ptr())
        };

        if index.is_null() {
            Err(error)
        } else {
            Ok(Index {
                index,
                buffer: Vec::new(),
            })
        }
    }

    pub fn TrackIndexSettings(&self, Track: usize, Index: usize) {
        unsafe {
            FFMS_TrackIndexSettings(
                self.indexer,
                Track as i32,
                Index as i32,
                0,
            );
        }
    }

    pub fn TrackTypeIndexSettings(&self, TrackType: TrackType, Index: usize) {
        unsafe {
            FFMS_TrackTypeIndexSettings(
                self.indexer,
                TrackType::to_track_type(TrackType) as i32,
                Index as i32,
                0,
            );
        }
    }

    pub fn ProgressCallback<F>(&self, callback: F, value: &mut usize)
    where
        F: FnMut(usize, usize, Option<&mut usize>) -> usize + 'static,
    {
        struct CallbackData<'a> {
            #[expect(
                clippy::type_complexity,
                reason = "can't make trait into type alias"
            )]
            callback: Box<
                dyn FnMut(usize, usize, Option<&mut usize>) -> usize + 'static,
            >,
            value: &'a mut usize,
        }

        unsafe extern "C" fn IndexCallback(
            Current: i64,
            Total: i64,
            ICPrivate: *mut c_void,
        ) -> i32 {
            let mut user_data = Box::from_raw(ICPrivate as *mut CallbackData);

            let closure = panic::AssertUnwindSafe(|| {
                (user_data.callback)(
                    Current as usize,
                    Total as usize,
                    Some(user_data.value),
                ) as i32
            });

            let res = panic::catch_unwind(closure);

            if res.is_err() {
                process::abort();
            }

            Box::leak(user_data);

            res.unwrap()
        }

        let ICPrivate = Box::new(CallbackData {
            callback: Box::new(callback),
            value,
        });

        unsafe {
            FFMS_SetProgressCallback(
                self.indexer,
                Some(IndexCallback),
                Box::into_raw(ICPrivate) as *mut c_void,
            )
        }
    }
}
