use crate::{Class, Tag};
use alloc::str;
use alloc::string;
use alloc::string::String;
use displaydoc::Display;
use nom::error::{ErrorKind, FromExternalError, ParseError};
use nom::IResult;
#[cfg(feature = "std")]
use std::io;
#[cfg(feature = "std")]
use thiserror::Error;

#[cfg(feature = "std")]
impl std::error::Error for DerConstraint {}

#[derive(Clone, Copy, Debug, Display, PartialEq, Eq)]
/// Error types for DER constraints
pub enum DerConstraint {
    /// Indefinite length not allowed
    IndefiniteLength,
    /// Object must not be constructed
    Constructed,
    /// Object must be constructed
    NotConstructed,
    /// DateTime object is missing timezone
    MissingTimeZone,
    /// DateTime object is missing seconds
    MissingSeconds,
    /// Bitstring unused bits must be set to zero
    UnusedBitsNotZero,
    /// Boolean value must be 0x00 of 0xff
    InvalidBoolean,
    /// Integer must not be empty
    IntegerEmpty,
    /// Leading zeroes in Integer encoding
    IntegerLeadingZeroes,
    /// Leading 0xff in negative Integer encoding
    IntegerLeadingFF,
}

// XXX
// thiserror does not work in no_std
// see https://github.com/dtolnay/thiserror/pull/64

#[cfg(feature = "std")]
impl std::error::Error for Error {}

/// The error type for operations of the [`FromBer`](crate::FromBer),
/// [`FromDer`](crate::FromDer), and associated traits.
#[derive(Clone, Debug, Display, PartialEq, Eq)]
// #[cfg_attr(feature = "std", derive(Error))]
pub enum Error {
    /// BER object does not have the expected type
    BerTypeError,
    /// BER object does not have the expected value
    BerValueError,
    /// Invalid Length
    InvalidLength,
    /// Invalid Value when parsing object with tag {tag:?} {msg:}
    InvalidValue { tag: Tag, msg: String },
    /// Invalid Tag
    InvalidTag,
    /// Unknown tag: {0:?}
    UnknownTag(u32),
    /// Unexpected Tag (expected: {expected:?}, actual: {actual:?})
    UnexpectedTag { expected: Option<Tag>, actual: Tag },
    /// Unexpected Class (expected: {expected:?}, actual: {actual:?})
    UnexpectedClass {
        expected: Option<Class>,
        actual: Class,
    },

    /// Indefinite length not allowed
    IndefiniteLengthUnexpected,

    /// DER object was expected to be constructed (and found to be primitive)
    ConstructExpected,
    /// DER object was expected to be primitive (and found to be constructed)
    ConstructUnexpected,

    /// Integer too large to fit requested type
    IntegerTooLarge,
    /// BER integer is negative, while an unsigned integer was requested
    IntegerNegative,
    /// BER recursive parsing reached maximum depth
    BerMaxDepth,

    /// Invalid encoding or forbidden characters in string
    StringInvalidCharset,
    /// Invalid Date or Time
    InvalidDateTime,

    /// DER Failed constraint
    DerConstraintFailed(DerConstraint),

    /// Requesting borrowed data from a temporary object
    LifetimeError,
    /// Feature is not yet implemented
    Unsupported,

    /// incomplete data, missing: {0:?}
    Incomplete(nom::Needed),

    /// nom error: {0:?}
    NomError(ErrorKind),
}

impl Error {
    /// Build an error from the provided invalid value
    #[inline]
    pub const fn invalid_value(tag: Tag, msg: String) -> Self {
        Self::InvalidValue { tag, msg }
    }

    /// Build an error from the provided unexpected class
    #[inline]
    pub const fn unexpected_class(expected: Option<Class>, actual: Class) -> Self {
        Self::UnexpectedClass { expected, actual }
    }

    /// Build an error from the provided unexpected tag
    #[inline]
    pub const fn unexpected_tag(expected: Option<Tag>, actual: Tag) -> Self {
        Self::UnexpectedTag { expected, actual }
    }
}

impl<'a> ParseError<&'a [u8]> for Error {
    fn from_error_kind(_input: &'a [u8], kind: ErrorKind) -> Self {
        Error::NomError(kind)
    }
    fn append(_input: &'a [u8], kind: ErrorKind, _other: Self) -> Self {
        Error::NomError(kind)
    }
}

impl From<Error> for nom::Err<Error> {
    fn from(e: Error) -> Self {
        nom::Err::Error(e)
    }
}

impl From<str::Utf8Error> for Error {
    fn from(_: str::Utf8Error) -> Self {
        Error::StringInvalidCharset
    }
}

impl From<string::FromUtf8Error> for Error {
    fn from(_: string::FromUtf8Error) -> Self {
        Error::StringInvalidCharset
    }
}

impl From<string::FromUtf16Error> for Error {
    fn from(_: string::FromUtf16Error) -> Self {
        Error::StringInvalidCharset
    }
}

impl From<nom::Err<Error>> for Error {
    fn from(e: nom::Err<Error>) -> Self {
        match e {
            nom::Err::Incomplete(n) => Self::Incomplete(n),
            nom::Err::Error(e) | nom::Err::Failure(e) => e,
        }
    }
}

impl<I, E> FromExternalError<I, E> for Error {
    fn from_external_error(_input: I, kind: ErrorKind, _e: E) -> Error {
        Error::NomError(kind)
    }
}

/// Holds the result of BER/DER serialization functions
pub type ParseResult<'a, T, E = Error> = IResult<&'a [u8], T, E>;

/// A specialized `Result` type for all operations from this crate.
pub type Result<T, E = Error> = core::result::Result<T, E>;

/// The error type for serialization operations of the [`ToDer`](crate::ToDer) trait.
#[cfg(feature = "std")]
#[derive(Debug, Error)]
pub enum SerializeError {
    #[error("ASN.1 error: {0:?}")]
    ASN1Error(#[from] Error),

    #[error("Invalid Class {class:}")]
    InvalidClass { class: u8 },

    #[error("Invalid Length")]
    InvalidLength,

    #[error("I/O error: {0:?}")]
    IOError(#[from] io::Error),
}

#[cfg(feature = "std")]
/// Holds the result of BER/DER encoding functions
pub type SerializeResult<T> = std::result::Result<T, SerializeError>;
