// Copyright (C) 2021 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: Apache-2.0 or MIT

use core::fmt;
#[cfg(feature = "std")]
use std::error;

use crate::{channel::Channel, packet::PacketType};

/// Error code returned by the device for a failed CTAPHID transaction.
///
/// See [§ 11.2.9.1.6 of the CTAP specification][spec].
///
/// [spec]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#usb-hid-error
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum DeviceError {
    /// The command in the request is invalid.
    InvalidCommand,
    /// The parameter(s) in the request is invalid.
    InvalidParameter,
    /// The length field (BCNT) is invalid for the request.
    InvalidLength,
    /// The sequence does not match expected value.
    InvalidSequence,
    /// The message has timed out.
    MessageTimeout,
    /// The device is busy for the requesting channel.
    ChannelBusy,
    /// Command requires channel lock.
    LockRequired,
    /// CID is not valid.
    InvalidChannel,
    /// Unspecified error.
    Other,
    /// Unknown error code.
    Unknown(u8),
}

impl From<u8> for DeviceError {
    fn from(error: u8) -> Self {
        match error {
            0x01 => Self::InvalidCommand,
            0x02 => Self::InvalidParameter,
            0x03 => Self::InvalidLength,
            0x04 => Self::InvalidSequence,
            0x05 => Self::MessageTimeout,
            0x06 => Self::ChannelBusy,
            0x0a => Self::LockRequired,
            0x0b => Self::InvalidChannel,
            0x7f => Self::Other,
            error => Self::Unknown(error),
        }
    }
}

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

impl fmt::Display for DeviceError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidCommand => "the command in the request is invalid".fmt(f),
            Self::InvalidParameter => "the parameter(s) in the request is invalid".fmt(f),
            Self::InvalidLength => "the length field is invalid for the request".fmt(f),
            Self::InvalidSequence => "the sequence does not match expected value".fmt(f),
            Self::MessageTimeout => "the message has timed out".fmt(f),
            Self::ChannelBusy => "the device is busy for the requesting channel".fmt(f),
            Self::LockRequired => "command requires channel lock".fmt(f),
            Self::InvalidChannel => "CID is not valid".fmt(f),
            Self::Other => "unspecified error".fmt(f),
            Self::Unknown(value) => write!(f, "unknown error code 0x{:x}", value),
        }
    }
}

/// An error that occured while assembling a CTAPHID message from packets.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum DefragmentationError {
    /// The packet has an invalid channel.
    InvalidChannel {
        /// The expected channel.
        expected: Channel,
        /// The actual channel.
        actual: Channel,
    },
    /// The packet has an invalid sequence.
    InvalidSequence {
        /// The epxected sequence.
        expected: u8,
        /// The actual sequence.
        actual: u8,
    },
}

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

impl fmt::Display for DefragmentationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidChannel { expected, actual } => {
                write!(
                    f,
                    "expected a message with channel {} but got {}",
                    expected, actual
                )
            }
            Self::InvalidSequence { expected, actual } => {
                write!(
                    f,
                    "expected a message with sequence {} but got {}",
                    expected, actual
                )
            }
        }
    }
}

/// An error that occured while fragmenting a CTAPHID message into packets.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum FragmentationError {
    /// The message data is too long to be expressed in CTAPHID packets.
    DataTooLong,
    /// The packet size is too small to fit CTAPHID packet.
    PacketSizeTooSmall,
}

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

impl fmt::Display for FragmentationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::DataTooLong => "the message data is too long to be expressed in CTAPHID packets",
            Self::PacketSizeTooSmall => "the packet size is too small to fit a CTAPHID packet",
        }
        .fmt(f)
    }
}

/// An error that occured while parsing a CTAPHID packet.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum ParseError {
    /// Not enough data for a CTAPHID packet.
    NotEnoughData,
    /// The packet has an invalid type.
    InvalidPacketType {
        /// The expected packet type.
        expected: PacketType,
        /// The actual packet type.
        actual: PacketType,
    },
    /// Failed to create a buffer for storing the CTAPHID packet.
    BufferCreationFailed,
}

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

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::NotEnoughData => "there is not enough data for a CTAPHID packet".fmt(f),
            Self::InvalidPacketType { expected, actual } => {
                write!(
                    f,
                    "expected a {:?} packet but received a {:?} packet",
                    expected, actual
                )
            }
            Self::BufferCreationFailed => "failed to create a buffer for the CTAPHID packet".fmt(f),
        }
    }
}

/// An error that occured while serializing a CTAPHID packet.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum SerializationError {
    /// The provided buffer is too small for the CTAPHID packet.
    BufferTooSmall,
}

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

impl fmt::Display for SerializationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::BufferTooSmall => "the buffer for serializing a CTAPHID packet is too small",
        }
        .fmt(f)
    }
}
