// Copyright 2019, The Gtk-rs Project Developers.
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

//! # Examples
//!
//! ```
//! use glib::prelude::*; // or `use gtk::prelude::*;`
//! use glib::ByteArray;
//!
//! let ba = ByteArray::from(b"def");
//! ba.append(b"ghi").prepend(b"abc");
//! ba.remove_range(3, 3);
//! assert_eq!(ba, "abcghi".as_bytes());
//! ```

use glib_sys;
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::mem;
use std::ops::Deref;
use std::ptr::NonNull;
use std::slice;
use translate::*;

use Bytes;

glib_wrapper! {
    pub struct ByteArray(Shared<glib_sys::GByteArray>);

    match fn {
        ref => |ptr| glib_sys::g_byte_array_ref(ptr),
        unref => |ptr| glib_sys::g_byte_array_unref(ptr),
        get_type => || glib_sys::g_byte_array_get_type(),
    }
}

impl ByteArray {
    pub fn new() -> ByteArray {
        unsafe { from_glib_full(glib_sys::g_byte_array_new()) }
    }

    pub fn with_capacity(size: usize) -> ByteArray {
        unsafe { from_glib_full(glib_sys::g_byte_array_sized_new(size as u32)) }
    }

    pub fn into_gbytes(self) -> Bytes {
        unsafe {
            let ret = from_glib_full(glib_sys::g_byte_array_free_to_bytes(mut_override(
                self.to_glib_none().0,
            )));
            mem::forget(self);
            ret
        }
    }

    pub fn append<T: ?Sized + AsRef<[u8]>>(&self, data: &T) -> &Self {
        let bytes = data.as_ref();
        unsafe {
            glib_sys::g_byte_array_append(
                self.to_glib_none().0,
                bytes.as_ptr() as *const _,
                bytes.len() as u32,
            );
        }
        self
    }

    pub fn prepend<T: ?Sized + AsRef<[u8]>>(&self, data: &T) -> &Self {
        let bytes = data.as_ref();
        unsafe {
            glib_sys::g_byte_array_prepend(
                self.to_glib_none().0,
                bytes.as_ptr() as *const _,
                bytes.len() as u32,
            );
        }
        self
    }

    pub fn remove_index(&self, index: usize) {
        unsafe {
            glib_sys::g_byte_array_remove_index(self.to_glib_none().0, index as u32);
        }
    }

    pub fn remove_index_fast(&self, index: usize) {
        unsafe {
            glib_sys::g_byte_array_remove_index_fast(self.to_glib_none().0, index as u32);
        }
    }

    pub fn remove_range(&self, index: usize, length: usize) {
        unsafe {
            glib_sys::g_byte_array_remove_range(self.to_glib_none().0, index as u32, length as u32);
        }
    }

    pub unsafe fn set_size(&self, size: usize) {
        glib_sys::g_byte_array_set_size(self.to_glib_none().0, size as u32);
    }

    pub fn sort<F: FnMut(&u8, &u8) -> Ordering>(&self, compare_func: F) {
        unsafe extern "C" fn compare_func_trampoline(
            a: glib_sys::gconstpointer,
            b: glib_sys::gconstpointer,
            func: glib_sys::gpointer,
        ) -> i32 {
            let func = func as *mut &mut (dyn FnMut(&u8, &u8) -> Ordering);

            let a = &*(a as *const u8);
            let b = &*(b as *const u8);

            match (*func)(&a, &b) {
                Ordering::Less => -1,
                Ordering::Equal => 0,
                Ordering::Greater => 1,
            }
        }
        unsafe {
            let mut func = compare_func;
            let func_obj: &mut (dyn FnMut(&u8, &u8) -> Ordering) = &mut func;
            let func_ptr =
                &func_obj as *const &mut (dyn FnMut(&u8, &u8) -> Ordering) as glib_sys::gpointer;

            glib_sys::g_byte_array_sort_with_data(
                self.to_glib_none().0,
                Some(compare_func_trampoline),
                func_ptr,
            );
        }
    }
}

impl AsRef<[u8]> for ByteArray {
    fn as_ref(&self) -> &[u8] {
        &*self
    }
}

impl<'a, T: ?Sized + Borrow<[u8]> + 'a> From<&'a T> for ByteArray {
    fn from(value: &'a T) -> ByteArray {
        let ba = ByteArray::new();
        ba.append(value.borrow());
        ba
    }
}

impl Deref for ByteArray {
    type Target = [u8];

    fn deref(&self) -> &[u8] {
        unsafe {
            let mut ptr = (*self.to_glib_none().0).data;
            let len = (*self.to_glib_none().0).len as usize;
            debug_assert!(!ptr.is_null() || len == 0);
            if ptr.is_null() {
                ptr = NonNull::dangling().as_ptr();
            }
            slice::from_raw_parts(ptr as *const u8, len)
        }
    }
}

impl Default for ByteArray {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Debug for ByteArray {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("ByteArray")
            .field("ptr", &self.to_glib_none().0)
            .field("data", &&self[..])
            .finish()
    }
}

macro_rules! impl_cmp {
    ($lhs:ty, $rhs: ty) => {
        impl<'a, 'b> PartialEq<$rhs> for $lhs {
            #[inline]
            fn eq(&self, other: &$rhs) -> bool {
                self[..].eq(&other[..])
            }
        }

        impl<'a, 'b> PartialEq<$lhs> for $rhs {
            #[inline]
            fn eq(&self, other: &$lhs) -> bool {
                self[..].eq(&other[..])
            }
        }

        impl<'a, 'b> PartialOrd<$rhs> for $lhs {
            #[inline]
            fn partial_cmp(&self, other: &$rhs) -> Option<Ordering> {
                self[..].partial_cmp(&other[..])
            }
        }

        impl<'a, 'b> PartialOrd<$lhs> for $rhs {
            #[inline]
            fn partial_cmp(&self, other: &$lhs) -> Option<Ordering> {
                self[..].partial_cmp(&other[..])
            }
        }
    };
}

impl_cmp!(ByteArray, [u8]);
impl_cmp!(ByteArray, &'a [u8]);
impl_cmp!(&'a ByteArray, [u8]);
impl_cmp!(ByteArray, Vec<u8>);
impl_cmp!(&'a ByteArray, Vec<u8>);

impl PartialEq for ByteArray {
    fn eq(&self, other: &Self) -> bool {
        self[..] == other[..]
    }
}

impl Eq for ByteArray {}

impl Hash for ByteArray {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.len().hash(state);
        Hash::hash_slice(&self[..], state)
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashSet;

    #[test]
    fn various() {
        let ba: ByteArray = Default::default();
        ba.append("foo").append("bar").prepend("baz");
        ba.remove_index(0);
        ba.remove_index_fast(1);
        ba.remove_range(1, 2);
        ba.sort(|a, b| a.cmp(b));
        unsafe { ba.set_size(3) };
        assert_eq!(ba, "aab".as_bytes());
        let abc: &[u8] = b"abc";
        assert_eq!(ByteArray::from(abc), "abc".as_bytes());
    }

    #[test]
    fn hash() {
        let b1 = ByteArray::from(b"this is a test");
        let b2 = ByteArray::from(b"this is a test");
        let b3 = ByteArray::from(b"test");
        let mut set = HashSet::new();
        set.insert(b1);
        assert!(set.contains(&b2));
        assert!(!set.contains(&b3));
    }
}
