use crate::type_container::TypeContainer;
use crate::type_parser::{TypeParserError, TypeParserErrorSeverity, TypeParserResult};
use crate::{
    architecture::{Architecture, CoreArchitecture},
    calling_convention::CoreCallingConvention,
    rc::*,
    string::*,
    type_library::TypeLibrary,
    types::QualifiedNameAndType,
};
use binaryninjacore_sys::*;
use std::fmt::Debug;
use std::ptr::NonNull;
use std::{borrow::Borrow, ffi, ptr};
#[derive(PartialEq, Eq, Hash)]
pub struct Platform {
    pub(crate) handle: *mut BNPlatform,
}
unsafe impl Send for Platform {}
unsafe impl Sync for Platform {}
macro_rules! cc_func {
    ($get_name:ident, $get_api:ident, $set_name:ident, $set_api:ident) => {
        pub fn $get_name(&self) -> Option<Ref<CoreCallingConvention>> {
            let arch = self.arch();
            unsafe {
                let cc = $get_api(self.handle);
                if cc.is_null() {
                    None
                } else {
                    Some(CoreCallingConvention::ref_from_raw(
                        cc,
                        arch.as_ref().handle(),
                    ))
                }
            }
        }
        pub fn $set_name(&self, cc: &CoreCallingConvention) {
            let arch = self.arch();
            assert!(
                cc.arch_handle.borrow().as_ref().handle == arch.handle,
                "use of calling convention with non-matching Platform architecture!"
            );
            unsafe {
                $set_api(self.handle, cc.handle);
            }
        }
    };
}
impl Platform {
    pub unsafe fn from_raw(handle: *mut BNPlatform) -> Self {
        debug_assert!(!handle.is_null());
        Self { handle }
    }
    pub(crate) unsafe fn ref_from_raw(handle: *mut BNPlatform) -> Ref<Self> {
        debug_assert!(!handle.is_null());
        Ref::new(Self { handle })
    }
    pub fn by_name(name: &str) -> Option<Ref<Self>> {
        let raw_name = name.to_cstr();
        unsafe {
            let res = BNGetPlatformByName(raw_name.as_ptr());
            if res.is_null() {
                None
            } else {
                Some(Self::ref_from_raw(res))
            }
        }
    }
    pub fn list_all() -> Array<Platform> {
        unsafe {
            let mut count = 0;
            let handles = BNGetPlatformList(&mut count);
            Array::new(handles, count, ())
        }
    }
    pub fn list_by_arch(arch: &CoreArchitecture) -> Array<Platform> {
        unsafe {
            let mut count = 0;
            let handles = BNGetPlatformListByArchitecture(arch.handle, &mut count);
            Array::new(handles, count, ())
        }
    }
    pub fn list_by_os(name: &str) -> Array<Platform> {
        let raw_name = name.to_cstr();
        unsafe {
            let mut count = 0;
            let handles = BNGetPlatformListByOS(raw_name.as_ptr(), &mut count);
            Array::new(handles, count, ())
        }
    }
    pub fn list_by_os_and_arch(name: &str, arch: &CoreArchitecture) -> Array<Platform> {
        let raw_name = name.to_cstr();
        unsafe {
            let mut count = 0;
            let handles =
                BNGetPlatformListByOSAndArchitecture(raw_name.as_ptr(), arch.handle, &mut count);
            Array::new(handles, count, ())
        }
    }
    pub fn list_available_os() -> Array<BnString> {
        unsafe {
            let mut count = 0;
            let list = BNGetPlatformOSList(&mut count);
            Array::new(list, count, ())
        }
    }
    pub fn new<A: Architecture>(arch: &A, name: &str) -> Ref<Self> {
        let name = name.to_cstr();
        unsafe {
            let handle = BNCreatePlatform(arch.as_ref().handle, name.as_ptr());
            assert!(!handle.is_null());
            Ref::new(Self { handle })
        }
    }
    pub fn name(&self) -> String {
        unsafe {
            let raw_name = BNGetPlatformName(self.handle);
            BnString::into_string(raw_name)
        }
    }
    pub fn arch(&self) -> CoreArchitecture {
        unsafe { CoreArchitecture::from_raw(BNGetPlatformArchitecture(self.handle)) }
    }
    pub fn type_container(&self) -> TypeContainer {
        let type_container_ptr = NonNull::new(unsafe { BNGetPlatformTypeContainer(self.handle) });
        unsafe { TypeContainer::from_raw(type_container_ptr.unwrap()) }
    }
    pub fn get_type_libraries_by_name(&self, name: &str) -> Array<TypeLibrary> {
        let mut count = 0;
        let name = name.to_cstr();
        let result =
            unsafe { BNGetPlatformTypeLibrariesByName(self.handle, name.as_ptr(), &mut count) };
        assert!(!result.is_null());
        unsafe { Array::new(result, count, ()) }
    }
    pub fn get_type_library_by_name(&self, name: &str) -> Option<Ref<TypeLibrary>> {
        let libraries = self.get_type_libraries_by_name(name);
        libraries
            .iter()
            .find(|lib| lib.name() == name)
            .map(|lib| lib.to_owned())
    }
    pub fn register_os(&self, os: &str) {
        let os = os.to_cstr();
        unsafe {
            BNRegisterPlatform(os.as_ptr(), self.handle);
        }
    }
    cc_func!(
        get_default_calling_convention,
        BNGetPlatformDefaultCallingConvention,
        set_default_calling_convention,
        BNRegisterPlatformDefaultCallingConvention
    );
    cc_func!(
        get_cdecl_calling_convention,
        BNGetPlatformCdeclCallingConvention,
        set_cdecl_calling_convention,
        BNRegisterPlatformCdeclCallingConvention
    );
    cc_func!(
        get_stdcall_calling_convention,
        BNGetPlatformStdcallCallingConvention,
        set_stdcall_calling_convention,
        BNRegisterPlatformStdcallCallingConvention
    );
    cc_func!(
        get_fastcall_calling_convention,
        BNGetPlatformFastcallCallingConvention,
        set_fastcall_calling_convention,
        BNRegisterPlatformFastcallCallingConvention
    );
    cc_func!(
        get_syscall_convention,
        BNGetPlatformSystemCallConvention,
        set_syscall_convention,
        BNSetPlatformSystemCallConvention
    );
    pub fn calling_conventions(&self) -> Array<CoreCallingConvention> {
        unsafe {
            let mut count = 0;
            let handles = BNGetPlatformCallingConventions(self.handle, &mut count);
            Array::new(handles, count, self.arch())
        }
    }
    pub fn types(&self) -> Array<QualifiedNameAndType> {
        unsafe {
            let mut count = 0;
            let handles = BNGetPlatformTypes(self.handle, &mut count);
            Array::new(handles, count, ())
        }
    }
    pub fn variables(&self) -> Array<QualifiedNameAndType> {
        unsafe {
            let mut count = 0;
            let handles = BNGetPlatformVariables(self.handle, &mut count);
            Array::new(handles, count, ())
        }
    }
    pub fn functions(&self) -> Array<QualifiedNameAndType> {
        unsafe {
            let mut count = 0;
            let handles = BNGetPlatformFunctions(self.handle, &mut count);
            Array::new(handles, count, ())
        }
    }
    pub fn preprocess_source(
        &self,
        source: &str,
        file_name: &str,
        include_dirs: &[BnString],
    ) -> Result<BnString, TypeParserError> {
        let source_cstr = BnString::new(source);
        let file_name_cstr = BnString::new(file_name);
        let mut result = ptr::null_mut();
        let mut error_string = ptr::null_mut();
        let success = unsafe {
            BNPreprocessSource(
                source_cstr.as_ptr(),
                file_name_cstr.as_ptr(),
                &mut result,
                &mut error_string,
                include_dirs.as_ptr() as *mut *const ffi::c_char,
                include_dirs.len(),
            )
        };
        if success {
            assert!(!result.is_null());
            Ok(unsafe { BnString::from_raw(result) })
        } else {
            assert!(!error_string.is_null());
            Err(TypeParserError::new(
                TypeParserErrorSeverity::FatalSeverity,
                unsafe { BnString::into_string(error_string) },
                file_name.to_string(),
                0,
                0,
            ))
        }
    }
    pub fn parse_types_from_source(
        &self,
        src: &str,
        filename: &str,
        include_dirs: &[BnString],
        auto_type_source: &str,
    ) -> Result<TypeParserResult, TypeParserError> {
        let source_cstr = BnString::new(src);
        let file_name_cstr = BnString::new(filename);
        let auto_type_source = BnString::new(auto_type_source);
        let mut raw_result = BNTypeParserResult::default();
        let mut error_string = ptr::null_mut();
        let success = unsafe {
            BNParseTypesFromSource(
                self.handle,
                source_cstr.as_ptr(),
                file_name_cstr.as_ptr(),
                &mut raw_result,
                &mut error_string,
                include_dirs.as_ptr() as *mut *const ffi::c_char,
                include_dirs.len(),
                auto_type_source.as_ptr(),
            )
        };
        if success {
            let result = TypeParserResult::from_raw(&raw_result);
            TypeParserResult::free_raw(raw_result);
            Ok(result)
        } else {
            assert!(!error_string.is_null());
            Err(TypeParserError::new(
                TypeParserErrorSeverity::FatalSeverity,
                unsafe { BnString::into_string(error_string) },
                filename.to_string(),
                0,
                0,
            ))
        }
    }
    pub fn parse_types_from_source_file(
        &self,
        filename: &str,
        include_dirs: &[BnString],
        auto_type_source: &str,
    ) -> Result<TypeParserResult, TypeParserError> {
        let file_name_cstr = BnString::new(filename);
        let auto_type_source = BnString::new(auto_type_source);
        let mut raw_result = BNTypeParserResult::default();
        let mut error_string = ptr::null_mut();
        let success = unsafe {
            BNParseTypesFromSourceFile(
                self.handle,
                file_name_cstr.as_ptr(),
                &mut raw_result,
                &mut error_string,
                include_dirs.as_ptr() as *mut *const ffi::c_char,
                include_dirs.len(),
                auto_type_source.as_ptr(),
            )
        };
        if success {
            let result = TypeParserResult::from_raw(&raw_result);
            TypeParserResult::free_raw(raw_result);
            Ok(result)
        } else {
            assert!(!error_string.is_null());
            Err(TypeParserError::new(
                TypeParserErrorSeverity::FatalSeverity,
                unsafe { BnString::into_string(error_string) },
                filename.to_string(),
                0,
                0,
            ))
        }
    }
}
impl Debug for Platform {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Platform")
            .field("name", &self.name())
            .field("arch", &self.arch().name())
            .finish()
    }
}
impl ToOwned for Platform {
    type Owned = Ref<Self>;
    fn to_owned(&self) -> Self::Owned {
        unsafe { RefCountable::inc_ref(self) }
    }
}
unsafe impl RefCountable for Platform {
    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
        Ref::new(Self {
            handle: BNNewPlatformReference(handle.handle),
        })
    }
    unsafe fn dec_ref(handle: &Self) {
        BNFreePlatform(handle.handle);
    }
}
impl CoreArrayProvider for Platform {
    type Raw = *mut BNPlatform;
    type Context = ();
    type Wrapped<'a> = Guard<'a, Platform>;
}
unsafe impl CoreArrayProviderInner for Platform {
    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
        BNFreePlatformList(raw, count);
    }
    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
        debug_assert!(!raw.is_null());
        Guard::new(Self::from_raw(*raw), context)
    }
}