use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr;

use crate::tools::{from_cstr, to_cstr};
use crate::{Instance, InternalError};
use vlc_sys as sys;

pub trait Vlm {
    fn add_broadcast(
        &self,
        name: &str,
        input: &str,
        output: &str,
        options: Option<Vec<String>>,
        enabled: bool,
        loop_broadcast: bool,
    ) -> Result<(), InternalError>;

    fn add_vod(
        &self,
        name: &str,
        input: &str,
        mux: &str,
        options: Option<Vec<String>>,
        enabled: bool,
    ) -> Result<(), InternalError>;

    fn play_media(&self, name: &str) -> Result<(), InternalError>;

    fn pause_media(&self, name: &str) -> Result<(), InternalError>;

    fn stop_media(&self, name: &str) -> Result<(), InternalError>;

    fn get_media_instance_position(&self, name: &str, instance: i32) -> Result<f32, InternalError>;

    fn get_media_instance_length(&self, name: &str, instance: i32) -> Result<i32, InternalError>;

    fn get_media_instance_time(&self, name: &str, instance: i32) -> Result<i32, InternalError>;

    fn get_media_instance_rate(&self, name: &str, instance: i32) -> Result<i32, InternalError>;

    fn show_media(&self, name: &str) -> Result<String, InternalError>;
}

impl Vlm for Instance {
    fn add_broadcast(
        &self,
        name: &str,
        input: &str,
        output: &str,
        options: Option<Vec<String>>,
        enabled: bool,
        loop_broadcast: bool,
    ) -> Result<(), InternalError> {
        let name = to_cstr(name);
        let input = to_cstr(input);
        let output = to_cstr(output);
        let opts_c_ptr: Vec<*const c_char>;
        let opts_c: Vec<CString>;
        let enabled = if enabled { 1 } else { 0 };
        let loop_broadcast = if loop_broadcast { 1 } else { 0 };
        if let Some(vec) = options {
            opts_c = vec
                .into_iter()
                .map(|x| CString::new(x).expect("Error: Unexpected null byte"))
                .collect();
            opts_c_ptr = opts_c.iter().map(|x| x.as_ptr()).collect();
        } else {
            opts_c_ptr = Vec::new();
        }
        let result = unsafe {
            if opts_c_ptr.is_empty() {
                sys::libvlc_vlm_add_broadcast(
                    self.ptr,
                    name.as_ptr(),
                    input.as_ptr(),
                    output.as_ptr(),
                    0,
                    ptr::null(),
                    enabled,
                    loop_broadcast,
                )
            } else {
                sys::libvlc_vlm_add_broadcast(
                    self.ptr,
                    name.as_ptr(),
                    input.as_ptr(),
                    output.as_ptr(),
                    opts_c_ptr.len() as i32,
                    opts_c_ptr.as_ptr(),
                    enabled,
                    loop_broadcast,
                )
            }
        };
        if result == 0 {
            Ok(())
        } else {
            Err(InternalError)
        }
    }

    fn add_vod(
        &self,
        name: &str,
        input: &str,
        mux: &str,
        options: Option<Vec<String>>,
        enabled: bool,
    ) -> Result<(), InternalError> {
        let name = to_cstr(name);
        let input = to_cstr(input);
        let mux = to_cstr(mux);
        let opts_c_ptr: Vec<*const c_char>;
        let opts_c: Vec<CString>;
        let enabled = if enabled { 1 } else { 0 };
        if let Some(vec) = options {
            opts_c = vec
                .into_iter()
                .map(|x| CString::new(x).expect("Error: Unexpected null byte"))
                .collect();
            opts_c_ptr = opts_c.iter().map(|x| x.as_ptr()).collect();
        } else {
            opts_c_ptr = Vec::new();
        }
        let result = unsafe {
            if opts_c_ptr.is_empty() {
                sys::libvlc_vlm_add_vod(
                    self.ptr,
                    name.as_ptr(),
                    input.as_ptr(),
                    0,
                    ptr::null(),
                    enabled,
                    mux.as_ptr(),
                )
            } else {
                sys::libvlc_vlm_add_vod(
                    self.ptr,
                    name.as_ptr(),
                    input.as_ptr(),
                    opts_c_ptr.len() as i32,
                    opts_c_ptr.as_ptr(),
                    enabled,
                    mux.as_ptr(),
                )
            }
        };
        if result == 0 {
            Ok(())
        } else {
            Err(InternalError)
        }
    }

    fn play_media(&self, name: &str) -> Result<(), InternalError> {
        let name = to_cstr(name);
        let result = unsafe { sys::libvlc_vlm_play_media(self.ptr, name.as_ptr()) };
        if result == 0 {
            Ok(())
        } else {
            Err(InternalError)
        }
    }

    fn pause_media(&self, name: &str) -> Result<(), InternalError> {
        let name = to_cstr(name);
        let result = unsafe { sys::libvlc_vlm_pause_media(self.ptr, name.as_ptr()) };
        if result == 0 {
            Ok(())
        } else {
            Err(InternalError)
        }
    }

    fn stop_media(&self, name: &str) -> Result<(), InternalError> {
        let name = to_cstr(name);
        let result = unsafe { sys::libvlc_vlm_stop_media(self.ptr, name.as_ptr()) };
        if result == 0 {
            Ok(())
        } else {
            Err(InternalError)
        }
    }

    fn get_media_instance_position(&self, name: &str, instance: i32) -> Result<f32, InternalError> {
        let name = to_cstr(name);
        let result = unsafe {
            sys::libvlc_vlm_get_media_instance_position(self.ptr, name.as_ptr(), instance)
        };
        // if result != -1f32 { Ok(result) } else { Err(()) }
        if (result - -1f32).abs() < f32::EPSILON {
            Err(InternalError)
        } else {
            Ok(result)
        }
    }

    fn get_media_instance_length(&self, name: &str, instance: i32) -> Result<i32, InternalError> {
        let name = to_cstr(name);
        let result =
            unsafe { sys::libvlc_vlm_get_media_instance_length(self.ptr, name.as_ptr(), instance) };
        if result != -1 {
            Ok(result)
        } else {
            Err(InternalError)
        }
    }

    fn get_media_instance_time(&self, name: &str, instance: i32) -> Result<i32, InternalError> {
        let name = to_cstr(name);
        let result =
            unsafe { sys::libvlc_vlm_get_media_instance_time(self.ptr, name.as_ptr(), instance) };
        if result != -1 {
            Ok(result)
        } else {
            Err(InternalError)
        }
    }

    fn get_media_instance_rate(&self, name: &str, instance: i32) -> Result<i32, InternalError> {
        let name = to_cstr(name);
        let result =
            unsafe { sys::libvlc_vlm_get_media_instance_rate(self.ptr, name.as_ptr(), instance) };
        if result != -1 {
            Ok(result)
        } else {
            Err(InternalError)
        }
    }

    fn show_media(&self, name: &str) -> Result<String, InternalError> {
        let name = to_cstr(name);
        let result = unsafe { from_cstr(sys::libvlc_vlm_show_media(self.ptr, name.as_ptr())) };
        if let Some(data) = result {
            Ok(data)
        } else {
            Err(InternalError)
        }
    }
}