use std::fmt;
use std::path::PathBuf;

use pdfium_bind_sys as ffi;

/// Errors returned by pdfium-bind operations.
#[derive(Debug)]
pub enum Error {
    /// The file path contains invalid characters (interior NUL byte or non-UTF-8).
    InvalidPath(PathBuf),

    /// Failed to open a PDF document.
    OpenFailed {
        path: PathBuf,
        cause: OpenError,
    },

    /// The requested page index is out of range.
    PageOutOfRange {
        index: u32,
        count: u32,
    },

    /// Failed to allocate a bitmap for rendering.
    BitmapCreateFailed,
}

/// Reason why a PDF document could not be opened.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpenError {
    /// Unknown error.
    Unknown,
    /// File not found or could not be read.
    File,
    /// The file is not a valid PDF or is corrupted.
    Format,
    /// The PDF is password-protected and no (or wrong) password was supplied.
    Password,
    /// The PDF uses an unsupported security scheme.
    Security,
    /// A page-level error occurred during loading.
    Page,
}

impl OpenError {
    pub(crate) fn from_code(code: std::os::raw::c_ulong) -> Self {
        #[allow(non_upper_case_globals)]
        match code as u32 {
            ffi::FPDF_ERR_FILE => Self::File,
            ffi::FPDF_ERR_FORMAT => Self::Format,
            ffi::FPDF_ERR_PASSWORD => Self::Password,
            ffi::FPDF_ERR_SECURITY => Self::Security,
            ffi::FPDF_ERR_PAGE => Self::Page,
            _ => Self::Unknown,
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidPath(p) => write!(f, "invalid path: {}", p.display()),
            Self::OpenFailed { path, cause } => {
                write!(f, "failed to open {}: {cause}", path.display())
            }
            Self::PageOutOfRange { index, count } => {
                write!(f, "page index {index} out of range (document has {count} pages)")
            }
            Self::BitmapCreateFailed => write!(f, "failed to create rendering bitmap"),
        }
    }
}

impl fmt::Display for OpenError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let msg = match self {
            Self::Unknown => "unknown error",
            Self::File => "file not found or unreadable",
            Self::Format => "invalid PDF format",
            Self::Password => "password required or incorrect",
            Self::Security => "unsupported security scheme",
            Self::Page => "page error",
        };
        f.write_str(msg)
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::OpenFailed { cause, .. } => Some(cause),
            _ => None,
        }
    }
}

impl std::error::Error for OpenError {}
