//! A thin wrapper around [`nvtiffStatus_t`] providing [Result]s with [`NvTiffError`].
#![warn(missing_docs)]
use thiserror::Error;

use crate::nvtiffStatus_t;

/// Result of an nvTIFF API call
pub type NvTiffResult<T> = Result<T, NvTiffError>;

/// Errors from an nvTIFF API call
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum NvTiffError {
    /// An error occured while decoding the TIFF image.
    #[error("Status error: {0}")]
    StatusError(NvTiffStatusError),
}

/// nvTIFF Decode API non-zero return status codes
///
/// Based on
/// <https://docs.nvidia.com/cuda/nvtiff/userguide.html#decode-api-return-status-codes>
#[derive(Debug, Eq, Error, PartialEq)]
#[non_exhaustive]
pub enum NvTiffStatusError {
    /// The library handle was not initialized.
    #[error("The library handle was not initialized.")]
    NotInitialized, // 1
    /// Wrong parameter was passed. For example, a null pointer as input data, or an
    /// invalid enum value.
    #[error("Wrong parameter was passed.")]
    InvalidParameter, // 2
    /// Cannot parse the TIFF stream. Likely due to a corruption that cannot be handled.
    #[error("Cannot parse the TIFF stream.")]
    BadTiff, // 3
    /// Attempting to decode a TIFF stream that is not supported by the nvTIFF library.
    #[error("Attempting to decode a TIFF stream that is not supported by the nvTIFF library.")]
    TiffNotSupported, // 4
    /// The user-provided allocator functions, for either memory allocation or for
    /// releasing the memory, returned a non-zero code.
    #[error("The user-provided allocator functions returned a non-zero code.")]
    AllocatorFailure, // 5
    /// Error during the execution of the device tasks.
    #[error("Error during the execution of the device tasks.")]
    ExecutionFailed, // 6
    /// The device capabilities are not enough for the set of input parameters provided.
    #[error("The device capabilities are not enough for the set of input parameters provided.")]
    ArchMismatch, // 7
    /// Unknown error occured in the library.
    #[error("Unknown error occured in the library.")]
    InternalError, // 8
    /// nvTiff is unable to load the nvcomp library.
    #[error("nvTiff is unable to load the nvCOMP library.")]
    NvcompNotFound, // 9
    /// nvTiff is unable to load the nvjpeg library.
    #[error("nvTiff is unable to load the nvJPEG library.")]
    NvjpegNotFound, // 10
    /// nvTiff is unable to find information about the provided tag.
    #[error("nvTiff is unable to find information about the provided tag.")]
    TagNotFound, // 11
    /// Provided parameter is outside the range of possible values.
    #[error("Provided parameter is outside the range of possible values.")]
    ParameterOutOfBounds, // 12
    /// nvTiff is unable to load the nvJPEG2000 library.
    #[error("nvTiff is unable to load the nvJPEG2000 library.")]
    Nvjpeg2kNotFound, // 13

    /// A custom (or unimplemented) error that does not fall under any other nvTIFF
    /// status error kind.
    #[error("Unknown nvTiff error with status code {0}")]
    Other(u32),
}

/// Trait for checking nvTIFF API return status codes
pub trait NvTiffResultCheck {
    /// Check if the nvTIFF API call has finished successfully.
    ///
    /// # Errors
    /// Will return [`NvTiffError`] if the status code is non-zero.
    fn result(self) -> NvTiffResult<()>;
}

impl NvTiffResultCheck for nvtiffStatus_t::Type {
    /// Check if the nvTIFF Decode API call has finished successfully. Note that many of
    /// the calls are asynchronous and some of the errors may be seen only after
    /// synchronization.
    ///
    /// # Errors
    /// Will return [`NvTiffError::StatusError`] if the status code is non-zero.
    fn result(self) -> NvTiffResult<()> {
        match self {
            nvtiffStatus_t::NVTIFF_STATUS_SUCCESS => Ok(()),
            nvtiffStatus_t::NVTIFF_STATUS_NOT_INITIALIZED => {
                Err(NvTiffError::StatusError(NvTiffStatusError::NotInitialized))
            }
            nvtiffStatus_t::NVTIFF_STATUS_INVALID_PARAMETER => Err(NvTiffError::StatusError(
                NvTiffStatusError::InvalidParameter,
            )),
            nvtiffStatus_t::NVTIFF_STATUS_BAD_TIFF => {
                Err(NvTiffError::StatusError(NvTiffStatusError::BadTiff))
            }
            nvtiffStatus_t::NVTIFF_STATUS_TIFF_NOT_SUPPORTED => Err(NvTiffError::StatusError(
                NvTiffStatusError::TiffNotSupported,
            )),
            nvtiffStatus_t::NVTIFF_STATUS_ALLOCATOR_FAILURE => Err(NvTiffError::StatusError(
                NvTiffStatusError::AllocatorFailure,
            )),
            nvtiffStatus_t::NVTIFF_STATUS_EXECUTION_FAILED => {
                Err(NvTiffError::StatusError(NvTiffStatusError::ExecutionFailed))
            }
            nvtiffStatus_t::NVTIFF_STATUS_ARCH_MISMATCH => {
                Err(NvTiffError::StatusError(NvTiffStatusError::ArchMismatch))
            }
            nvtiffStatus_t::NVTIFF_STATUS_INTERNAL_ERROR => {
                Err(NvTiffError::StatusError(NvTiffStatusError::InternalError))
            }
            nvtiffStatus_t::NVTIFF_STATUS_NVCOMP_NOT_FOUND => {
                Err(NvTiffError::StatusError(NvTiffStatusError::NvcompNotFound))
            }
            nvtiffStatus_t::NVTIFF_STATUS_NVJPEG_NOT_FOUND => {
                Err(NvTiffError::StatusError(NvTiffStatusError::NvjpegNotFound))
            }
            nvtiffStatus_t::NVTIFF_STATUS_TAG_NOT_FOUND => {
                Err(NvTiffError::StatusError(NvTiffStatusError::TagNotFound))
            }
            nvtiffStatus_t::NVTIFF_STATUS_PARAMETER_OUT_OF_BOUNDS => Err(NvTiffError::StatusError(
                NvTiffStatusError::ParameterOutOfBounds,
            )),
            nvtiffStatus_t::NVTIFF_STATUS_NVJPEG2K_NOT_FOUND => Err(NvTiffError::StatusError(
                NvTiffStatusError::Nvjpeg2kNotFound,
            )),
            // Unknown nvTIFF decode API status code
            status_code => Err(NvTiffError::StatusError(NvTiffStatusError::Other(
                status_code,
            ))),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::ffi::CString;

    use crate::{
        NvTiffResultCheck, nvtiffStatus_t, nvtiffStream, nvtiffStreamCreate,
        nvtiffStreamGetNumImages, nvtiffStreamParseFromFile,
    };

    #[test]
    fn test_nvtiff_status_result() {
        let mut stream = std::mem::MaybeUninit::uninit();
        let mut tiff_stream: *mut nvtiffStream = stream.as_mut_ptr();

        // Check return code = 0 success
        let status_create: nvtiffStatus_t::Type =
            unsafe { nvtiffStreamCreate(&raw mut tiff_stream) };
        dbg!(status_create); // should be 0=SUCCESS
        assert!(status_create.result().is_ok());

        let tiff_cstr = CString::new("images/invalid.tif").unwrap();
        let tiff_path: *const std::os::raw::c_char = tiff_cstr.as_ptr();

        // Check return code >= 1 failure
        let status_parse: nvtiffStatus_t::Type =
            unsafe { nvtiffStreamParseFromFile(tiff_path, tiff_stream) };
        dbg!(status_parse); // should be 2=NVTIFF_STATUS_INVALID_PARAMETER
        assert!(status_parse.result().is_err());
    }

    #[test]
    fn test_nvtiff_other_error() {
        let status_unknown: nvtiffStatus_t::Type = 42; // mock unimplemented status code
        dbg!(status_unknown); // should be 42=?
        assert!(status_unknown.result().is_err());
    }

    #[test]
    fn nvtiff_status_to_string() {
        let mut host_stream = std::mem::MaybeUninit::uninit();
        let tiff_stream: *mut nvtiffStream = host_stream.as_mut_ptr();

        // Set up empty tiff stream
        let mut num_images: u32 = 0;
        let status_numimages: u32 =
            unsafe { nvtiffStreamGetNumImages(tiff_stream, &raw mut num_images) };
        dbg!(status_numimages);

        // Check that errors can be turned into string form
        assert_eq!(
            status_numimages.result().unwrap_err().to_string(),
            "Status error: Wrong parameter was passed.".to_string()
        );
    }
}
