use crate::os::win::ComPtr;
use winapi::shared::{ntdef::HRESULT, winerror::SUCCEEDED};


/// invokes a windows hr-style function, returning `T` on success or an `std::io::Error` on failure.
///
/// # remarks.
///
/// a hr-style function is a function that is logically:
///     function() -> Result<T, _>
///
/// but is implemented as:
///     function(..., out_value: *mut T) -> status_code
///
/// where `function` takes a zero-initialized `T` and fills it on success (and returns a status code indicating success
/// or failure).
///
/// # return value.
///
/// if `function(..., *mut T)` was:
///     - success: returns T
///     - failure: returns std::io::Error (GetLastError)
pub fn call<TFunction, TFunctionValue, T>(function: TFunction) -> Result<T, std::io::Error>
where
    TFunction: FnOnce(*mut T) -> TFunctionValue,
    TFunctionValue: HrStyleFunction,
{
    hr_call(function).map_err(TFunctionValue::error)
}

/// invokes a windows hr-style function, returning `T` on success or the raw return value on failure.
///
/// # remarks.
///
/// a hr-style function is a function that is logically:
///     function() -> Result<T, _>
///
/// but is implemented as:
///     function(..., out_value: *mut T) -> status_code
///
/// where `function` takes a zero-initialized `T` and fills it on success (and returns a status code indicating success
/// or failure).
///
/// # return value.
///
/// if `function(..., *mut T)` was:
///     - success: returns T
///     - failure: returns status_code (the return value from invoking `function`)
pub fn hr_call<TFunction, TFunctionValue, T>(function: TFunction) -> Result<T, TFunctionValue>
where
    TFunction: FnOnce(*mut T) -> TFunctionValue,
    TFunctionValue: HrStyleFunction,
{
    let mut value = unsafe { std::mem::zeroed::<T>() };
    let returned = function(&mut value);

    match HrStyleFunction::ok(returned) {
        true => Ok(value),
        false => Err(returned),
    }
}


/// invokes a windows hr-style function (that creates a com pointer), returning a `ComPtr<T>` on success or an
/// `std::io::Error` on failure.
///
/// this is a simple variation of `hina::os::win::hr::call(...)` above, except that it returns a `ComPtr<T>`.
///
/// # remarks.
///
/// `T` must be a com type that inherits `IUnknown`.
///
/// see further remarks for `hina::os::win::hr::call(...)`.
///
/// # return value.
///
/// if `function(..., *mut T)` was:
///     - success: returns ComPtr<T>
///     - failure: returns std::io::Error (GetLastError)
pub fn com_call<TFunction, TFunctionValue, T>(function: TFunction) -> Result<ComPtr<T>, std::io::Error>
where
    TFunction: FnOnce(*mut *mut T) -> TFunctionValue,
    TFunctionValue: HrStyleFunction,
{
    com_hr_call(function).map_err(TFunctionValue::error)
}

/// invokes a windows hr-style function (that creates a com pointer), returning a `ComPtr<T>` on success or the raw
/// return value on failure.
///
/// this is a simple variation of `hina::os::win::hr::hr_call(...)` above, except that it returns a `ComPtr<T>`.
///
/// # remarks.
///
/// `T` must be a com type that inherits `IUnknown`.
///
/// see further remarks for `hina::os::win::hr::call(...)`.
///
/// # return value.
///
/// if `function(..., *mut T)` was:
///     - success: returns ComPtr<T>
///     - failure: returns status_code (the return value from invoking `function`)
pub fn com_hr_call<TFunction, TFunctionValue, T>(function: TFunction) -> Result<ComPtr<T>, TFunctionValue>
where
    TFunction: FnOnce(*mut *mut T) -> TFunctionValue,
    TFunctionValue: HrStyleFunction,
{
    let mut value = std::ptr::null_mut();
    let returned = function(&mut value);

    match HrStyleFunction::ok(returned) {
        true => Ok(unsafe { ComPtr::new(value) }),
        false => Err(returned),
    }
}


/// a trait that describes how we treat return values in a hr-style function call.
pub trait HrStyleFunction: Copy {
    fn ok(self: Self) -> bool;
    fn error(self: Self) -> std::io::Error;
}

impl HrStyleFunction for HRESULT {
    fn ok(self: HRESULT) -> bool {
        SUCCEEDED(self)
    }

    fn error(self: HRESULT) -> std::io::Error {
        std::io::Error::from_raw_os_error(self)
    }
}

impl HrStyleFunction for bool {
    fn ok(self: bool) -> bool {
        self
    }

    fn error(self: bool) -> std::io::Error {
        std::io::Error::last_os_error()
    }
}

impl HrStyleFunction for () {
    fn ok(self: ()) -> bool {
        true
    }

    fn error(self: ()) -> std::io::Error {
        unreachable!();
    }
}