/*
    Copyright: 2020 Rasmus Thomsen <oss@cogitri.dev>
    SPDX-License-Identifier: GPL-3.0-or-later
*/

#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::all)]

use std::{
    convert::TryFrom,
    error::Error,
    ffi::{c_void, CString, IntoStringError, NulError},
    fmt, slice,
};

macro_rules! apk_array_funcs {
    ($x:ident) => {
        impl Drop for $x {
            fn drop(&mut self) {
                if self.num != 0 {
                    unsafe { apk_array_resize(self as *mut Self as *mut c_void, 0, 0) };
                }
            }
        }
    };
    ($x:ident, $type:ty) => {
        apk_array_funcs!($x);

        impl $x {
            pub fn as_slice(&self) -> Result<&[$type], std::num::TryFromIntError> {
                Ok(unsafe { self.item.as_slice(usize::try_from(self.num)?) })
            }

            pub fn as_mut_slice(&mut self) -> Result<&mut [$type], std::num::TryFromIntError> {
                Ok(unsafe { self.item.as_mut_slice(usize::try_from(self.num)?) })
            }

            pub fn iter(&self) -> Result<std::slice::Iter<$type>, std::num::TryFromIntError> {
                self.as_slice().map(|s| s.iter())
            }

            pub fn iter_mut(
                &mut self,
            ) -> Result<std::slice::IterMut<$type>, std::num::TryFromIntError> {
                self.as_mut_slice().map(|s| s.iter_mut())
            }
        }
    };
}

#[repr(C)]
pub struct apk_package {
    pub hash_node: apk_hash_node,
    pub __bindgen_anon_1: apk_package__bindgen_ty_1,
    pub name: *mut apk_name,
    pub ipkg: *mut apk_installed_package,
    pub version: *mut apk_blob_t,
    pub arch: *mut apk_blob_t,
    pub license: *mut apk_blob_t,
    pub origin: *mut apk_blob_t,
    pub maintainer: *mut apk_blob_t,
    pub url: *mut ::std::os::raw::c_char,
    pub description: *mut ::std::os::raw::c_char,
    pub commit: *mut ::std::os::raw::c_char,
    pub filename: *mut ::std::os::raw::c_char,
    pub depends: *mut apk_dependency_array,
    pub install_if: *mut apk_dependency_array,
    pub provides: *mut apk_dependency_array,
    pub installed_size: usize,
    pub size: usize,
    pub build_time: time_t,
    pub provider_priority: ::std::os::raw::c_ushort,
    pub repos: u32,
    pub marked: u8,
    pub uninstallable: u8,
    pub cached_non_repository: u8,
    pub csum: apk_checksum,
}

apk_array_funcs!(apk_string_array, *mut std::os::raw::c_char);
apk_array_funcs!(apk_hash_array, hlist_head);
apk_array_funcs!(apk_xattr_array, apk_xattr);
apk_array_funcs!(apk_provider_array, apk_provider);
apk_array_funcs!(apk_dependency_array, apk_dependency);
apk_array_funcs!(apk_package_array, *mut apk_package);
apk_array_funcs!(apk_name_array, *mut apk_name);
apk_array_funcs!(apk_protected_path_array, apk_protected_path);
apk_array_funcs!(apk_change_array, apk_change);

impl Drop for apk_changeset {
    fn drop(&mut self) {
        unsafe { apk_array_resize(self.changes.cast(), 0, 0) };
    }
}

impl apk_blob_t {
    pub fn new(string: &str) -> Self {
        apk_blob_t {
            len: string.len() as std::os::raw::c_long,
            ptr: CString::new(string).unwrap().into_raw(),
        }
    }
}

#[derive(Debug)]
pub enum ApkBlobConvertError {
    NullError(String),
    UTF8Error(String),
}

impl From<IntoStringError> for ApkBlobConvertError {
    fn from(e: IntoStringError) -> Self {
        Self::UTF8Error(e.to_string())
    }
}

impl From<NulError> for ApkBlobConvertError {
    fn from(e: NulError) -> Self {
        Self::NullError(e.to_string())
    }
}

impl fmt::Display for ApkBlobConvertError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ApkBlobConvertError::NullError(e) | ApkBlobConvertError::UTF8Error(e) => {
                write!(f, "{}", e)
            }
        }
    }
}

impl Error for ApkBlobConvertError {}

impl TryFrom<&apk_blob_t> for String {
    type Error = ApkBlobConvertError;

    fn try_from(blob: &apk_blob_t) -> Result<Self, Self::Error> {
        unsafe {
            let slice = slice::from_raw_parts::<u8>(blob.ptr.cast(), blob.len as usize);
            Ok(CString::new(slice)?.into_string()?)
        }
    }
}

/*
impl Drop for apk_blob_t {
    fn drop(&mut self) {
        unsafe { CString::from_raw(self.ptr) };
    }
}
*/

impl Clone for apk_blob_t {
    fn clone(&self) -> Self {
        Self {
            len: self.len,
            ptr: self.ptr,
        }
    }
}

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
