use ffmpeg_dev::sys as ffi;
use failure::bail;
use enum_primitive::*;
use std::marker::PhantomData;
use fraction::Fraction;

use crate::ffmpeg_api::enums::*;

// TODO: Use proper errors (with struct etc) for this
enum_from_primitive! {
    #[derive(Debug, Copy, Clone, PartialEq)]
    #[repr(i32)]
    pub enum AVErrorKind {
        Unknown = ffi::AVERROR_EXPERIMENTAL,
        InputChanged = ffi::AVERROR_INPUT_CHANGED,
        OutputChanged = ffi::AVERROR_OUTPUT_CHANGED
    }
}

pub struct AVFormatContext {
    base: *mut ffi::AVFormatContext,
}

impl<'a> AVFormatContext {
    pub fn new() -> Result<Self, failure::Error> {
        let base = unsafe { ffi::avformat_alloc_context() };
        if base.is_null() {
            bail!("avformat_alloc_context() failed");
        }
        Ok(AVFormatContext { base })
    }

    // TODO: Just for testing
    pub unsafe fn raw(&self) -> *mut ffi::AVFormatContext {
        self.base
    }

    pub fn open_input(&mut self, path: &str) -> Result<(), failure::Error> {
        match unsafe {
            ffi::avformat_open_input(
                &mut self.base,
                std::ffi::CString::new(path)
                    .map_err(|_| failure::format_err!("Could not convert path to c string"))?
                    .as_ptr(),
                std::ptr::null_mut(),
                std::ptr::null_mut(),
            )
        } {
            0 => Ok(()),
            _ => bail!("Could not open input")
        }
    }

    pub fn streams(&self) -> Vec<AVStream> {
        return unsafe {
            std::slice::from_raw_parts(
                (*self.base).streams,
                (*self.base).nb_streams as usize,
            )
        }
            .iter()
            .map(|stream| {
                AVStream::new(unsafe { (*stream).as_mut() }.expect("not null"), self)
            })
            .collect();
    }
}

impl Drop for AVFormatContext {
    fn drop(&mut self) {
        unsafe { ffi::avformat_free_context(self.base) }
    }
}

pub struct AVBuffer {
    base: *mut u8,
    size: usize,
}

impl AVBuffer {
    pub fn new(size: usize) -> Result<Self, failure::Error> {
        let base = unsafe { ffi::av_malloc(size) } as *mut u8;
        if base.is_null() {
            bail!("av_malloc() failed");
        }
        Ok(AVBuffer { base, size })
    }

    pub fn empty() -> Self {
        AVBuffer { base: std::ptr::null_mut(), size: 0 }
    }

    pub fn data(&self) -> &[u8] {
        unsafe {
            std::slice::from_raw_parts(self.base, self.size)
        }
    }

    pub fn data_mut(&mut self) -> &[u8] {
        unsafe {
            std::slice::from_raw_parts_mut(self.base, self.size)
        }
    }
}

pub struct AVFrame {
    base: *mut ffi::AVFrame,
    buffer: AVBuffer,
}

impl AVFrame {
    pub fn new() -> Result<Self, failure::Error> {
        let base = unsafe { ffi::av_frame_alloc() };
        if base.is_null() {
            bail!("avformat_alloc_frame() failed");
        }
        Ok(AVFrame { base, buffer: AVBuffer::empty() })
    }

    // TODO: Just for testing
    pub unsafe fn as_mut(&mut self) -> &mut ffi::AVFrame {
        self.base.as_mut().expect("not null")
    }

    pub fn init(&mut self, width: i32, height: i32, format: AVPixelFormat) -> Result<(), failure::Error>{
        let mut base = unsafe { self.base.as_mut() }.expect("not null");

        base.width = width;
        base.height = height;
        base.format = format as ffi::AVPixelFormat;

        self.buffer = AVBuffer::new(self.size())?;

        unsafe {
            ffi::avpicture_fill(
                self.base as *mut ffi::AVPicture,
                self.buffer.base as *mut u8,
                self.format() as ffi::AVPixelFormat,
                self.width(),
                self.height(),
            )
        };

        Ok(())
    }

    pub fn width(&self) -> i32 {
        let base = unsafe { self.base.as_ref() }.expect("not null");

        base.width
    }

    pub fn height(&self) -> i32 {
        let base = unsafe { self.base.as_ref() }.expect("not null");

        base.height
    }

    pub fn format(&self) -> AVPixelFormat {
        let base = unsafe { self.base.as_ref() }.expect("not null");

        AVPixelFormat::from_i32(base.format)
            .unwrap_or(AVPixelFormat::NONE)
    }

    pub fn size(&self) -> usize {
        unsafe {
            ffi::avpicture_get_size(self.format() as ffi::AVPixelFormat, self.width(), self.height()) as usize
        }
    }

    pub fn key_frame(&self) -> bool {
        let base = unsafe { self.base.as_ref() }.expect("not null");

        base.key_frame != 0
    }

    pub fn pts(&self) -> i64 {
        let base = unsafe { self.base.as_ref() }.expect("not null");

        base.pts
    }

    pub fn coded_picture_number(&self) -> i32 {
        let base = unsafe { self.base.as_ref() }.expect("not null");

        base.coded_picture_number
    }

    pub fn display_picture_number(&self) -> i32 {
        let base = unsafe { self.base.as_ref() }.expect("not null");

        base.display_picture_number
    }

    pub fn linesize(&self) -> &[i32] {
        let base = unsafe { self.base.as_ref() }.expect("not null");

        &base.linesize
    }

    pub fn data_ptr(&self) -> *const *const u8 {
        let base = unsafe { self.base.as_ref() }.expect("not null");

        base.data.as_ptr() as *const *const u8
    }

    pub fn data_mut_ptr(&mut self) -> *mut *mut u8 {
        let base = unsafe { self.base.as_mut() }.expect("not null");

        base.data.as_mut_ptr() as *mut *mut u8
    }

    pub fn data(&self, index: usize) -> &[u8] {
        let base = unsafe { self.base.as_ref() }.expect("not null");

        unsafe {
            std::slice::from_raw_parts(base.data[index], self.size())
        }
    }

    pub fn data_mut(&mut self, index: usize) -> &mut [u8] {
        let base = unsafe { self.base.as_mut() }.expect("not null");

        unsafe {
            std::slice::from_raw_parts_mut(base.data[index], self.size())
        }
    }
}

impl Drop for AVFrame {
    fn drop(&mut self) {
        unsafe { ffi::av_frame_free(&mut self.base) }
    }
}

pub struct AVStream<'a> {
    base: &'a mut ffi::AVStream,
    phantom: PhantomData<&'a AVFormatContext>,
}

impl<'a> AVStream<'a> {
    fn new(base: &'a mut ffi::AVStream, _: &'a AVFormatContext) -> Self {
        return AVStream { base, phantom: PhantomData };
    }

    pub fn index(self: &AVStream<'a>) -> i32 {
        self.base.index
    }

    pub fn time_base(self: &AVStream<'a>) -> Fraction {
        Fraction::new(
            self.base.time_base.num as u32,
            self.base.time_base.den as u32,
        )
    }

    pub fn timestamp(self: &AVStream<'a>, timestamp: i64) -> std::time::Duration {
        std::time::Duration::from_millis(
            1000 *
                timestamp as u64 *
                self.base.time_base.num as u64 /
                self.base.time_base.den as u64
        )
    }

    pub fn duration(self: &AVStream<'a>) -> std::time::Duration {
        self.timestamp(self.base.duration)
    }

    pub fn frame_count(self: &AVStream<'a>) -> i64 {
        self.base.nb_frames
    }

    pub fn discard(self: &AVStream<'a>) -> Option<AVDiscard> {
        AVDiscard::from_i32(self.base.discard)
    }

    pub fn set_discard(self: &mut AVStream<'a>, value: AVDiscard) {
        self.base.discard = value as ffi::AVDiscard;
    }

    pub fn sample_aspect_ratio(self: &AVStream<'a>) -> Fraction {
        Fraction::new(
            self.base.sample_aspect_ratio.num as u32,
            self.base.sample_aspect_ratio.den as u32,
        )
    }

    pub fn codec_parameters(self: &AVStream<'a>) -> AVCodecParameters {
        AVCodecParameters::new(unsafe { self.base.codecpar.as_mut() }.expect("not null"), self)
    }
}

pub struct AVCodecParameters<'a> {
    base: &'a mut ffi::AVCodecParameters,
    phantom: PhantomData<&'a AVStream<'a>>,
}

impl<'a> AVCodecParameters<'a> {
    fn new(base: &'a mut ffi::AVCodecParameters, _: &'a AVStream) -> Self {
        return AVCodecParameters { base, phantom: PhantomData };
    }

    // TODO: Just for testing
    pub unsafe fn as_ref(&self) -> &ffi::AVCodecParameters {
        self.base
    }

    pub fn codec_type(self: &AVCodecParameters<'a>) -> AVMediaType {
        AVMediaType::from_i32(self.base.codec_type).unwrap_or(AVMediaType::Unknown)
    }

    pub fn codec_id(self: &AVCodecParameters<'a>) -> Option<AVCodecID> {
        AVCodecID::from_u32(self.base.codec_id)
    }

    pub fn find_decoder(self: &AVCodecParameters<'a>) -> AVCodec {
        AVCodec::new(
            unsafe { ffi::avcodec_find_decoder(self.base.codec_id).as_mut() }.expect("Decoder not found"),
            self,
        )
    }
}

pub struct AVCodec<'a> {
    base: &'a mut ffi::AVCodec,
    phantom: PhantomData<&'a AVCodecParameters<'a>>,
}

impl<'a> AVCodec<'a> {
    fn new(base: &'a mut ffi::AVCodec, _: &'a AVCodecParameters) -> Self {
        return AVCodec { base, phantom: PhantomData };
    }

    // TODO: Just for testing
    pub unsafe fn as_ref(&self) -> &ffi::AVCodec {
        self.base
    }

    pub fn name(self: &AVCodec<'a>) -> std::string::String {
        String::from(unsafe { std::ffi::CStr::from_ptr(self.base.name) }.to_str().unwrap())
    }
}