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

/// A CTAPHID command.
///
/// See [§ 11.2.9 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-commands
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum Command {
    /// The message command.
    Message,
    /// The CBOR command.
    Cbor,
    /// The init command.
    Init,
    /// The ping command.
    Ping,
    /// The cancel command.
    Cancel,
    /// The error command.
    Error,
    /// The keep alive command.
    KeepAlive,
    /// The wink command.
    Wink,
    /// The lock command.
    Lock,
    /// A vendor command.
    Vendor(VendorCommand),
    /// An unknown command.
    Unknown(u8),
}

impl From<Command> for u8 {
    fn from(command: Command) -> Self {
        match command {
            Command::Message => 0x03,
            Command::Cbor => 0x10,
            Command::Init => 0x06,
            Command::Ping => 0x01,
            Command::Cancel => 0x11,
            Command::Error => 0x3f,
            Command::KeepAlive => 0x3b,
            Command::Wink => 0x08,
            Command::Lock => 0x04,
            Command::Vendor(command) => command.into(),
            Command::Unknown(command) => command,
        }
    }
}

impl From<u8> for Command {
    fn from(value: u8) -> Self {
        match value {
            0x01 => Self::Ping,
            0x03 => Self::Message,
            0x04 => Self::Lock,
            0x06 => Self::Init,
            0x08 => Self::Wink,
            0x10 => Self::Cbor,
            0x11 => Self::Cancel,
            0x3b => Self::KeepAlive,
            0x3f => Self::Error,
            _ => VendorCommand::new(value).map_or(Command::Unknown(value), Command::Vendor),
        }
    }
}

/// A CTAPHID vendor command.
///
/// See [§ 11.2.9.3 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-vendor-specific-commands
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct VendorCommand(u8);

impl VendorCommand {
    const FIRST: u8 = 0x40;
    const LAST: u8 = 0x7f;

    /// The range for valid vendor command IDs.
    pub const RANGE: core::ops::RangeInclusive<u8> = (Self::FIRST..=Self::LAST);

    /// The vendor-specific command with the ID 0x40.
    pub const H40: VendorCommand = VendorCommand(0x40);
    /// The vendor-specific command with the ID 0x41.
    pub const H41: VendorCommand = VendorCommand(0x41);
    /// The vendor-specific command with the ID 0x42.
    pub const H42: VendorCommand = VendorCommand(0x42);
    /// The vendor-specific command with the ID 0x43.
    pub const H43: VendorCommand = VendorCommand(0x43);
    /// The vendor-specific command with the ID 0x44.
    pub const H44: VendorCommand = VendorCommand(0x44);
    /// The vendor-specific command with the ID 0x45.
    pub const H45: VendorCommand = VendorCommand(0x45);
    /// The vendor-specific command with the ID 0x46.
    pub const H46: VendorCommand = VendorCommand(0x46);
    /// The vendor-specific command with the ID 0x47.
    pub const H47: VendorCommand = VendorCommand(0x47);
    /// The vendor-specific command with the ID 0x48.
    pub const H48: VendorCommand = VendorCommand(0x48);
    /// The vendor-specific command with the ID 0x49.
    pub const H49: VendorCommand = VendorCommand(0x49);
    /// The vendor-specific command with the ID 0x4a.
    pub const H4A: VendorCommand = VendorCommand(0x4a);
    /// The vendor-specific command with the ID 0x4b.
    pub const H4B: VendorCommand = VendorCommand(0x4b);
    /// The vendor-specific command with the ID 0x4c.
    pub const H4C: VendorCommand = VendorCommand(0x4c);
    /// The vendor-specific command with the ID 0x4d.
    pub const H4D: VendorCommand = VendorCommand(0x4d);
    /// The vendor-specific command with the ID 0x4e.
    pub const H4E: VendorCommand = VendorCommand(0x4e);
    /// The vendor-specific command with the ID 0x4f.
    pub const H4F: VendorCommand = VendorCommand(0x4f);
    /// The vendor-specific command with the ID 0x50.
    pub const H50: VendorCommand = VendorCommand(0x50);
    /// The vendor-specific command with the ID 0x51.
    pub const H51: VendorCommand = VendorCommand(0x51);
    /// The vendor-specific command with the ID 0x52.
    pub const H52: VendorCommand = VendorCommand(0x52);
    /// The vendor-specific command with the ID 0x53.
    pub const H53: VendorCommand = VendorCommand(0x53);
    /// The vendor-specific command with the ID 0x54.
    pub const H54: VendorCommand = VendorCommand(0x54);
    /// The vendor-specific command with the ID 0x55.
    pub const H55: VendorCommand = VendorCommand(0x55);
    /// The vendor-specific command with the ID 0x56.
    pub const H56: VendorCommand = VendorCommand(0x56);
    /// The vendor-specific command with the ID 0x57.
    pub const H57: VendorCommand = VendorCommand(0x57);
    /// The vendor-specific command with the ID 0x58.
    pub const H58: VendorCommand = VendorCommand(0x58);
    /// The vendor-specific command with the ID 0x59.
    pub const H59: VendorCommand = VendorCommand(0x59);
    /// The vendor-specific command with the ID 0x5a.
    pub const H5A: VendorCommand = VendorCommand(0x5a);
    /// The vendor-specific command with the ID 0x5b.
    pub const H5B: VendorCommand = VendorCommand(0x5b);
    /// The vendor-specific command with the ID 0x5c.
    pub const H5C: VendorCommand = VendorCommand(0x5c);
    /// The vendor-specific command with the ID 0x5d.
    pub const H5D: VendorCommand = VendorCommand(0x5d);
    /// The vendor-specific command with the ID 0x5e.
    pub const H5E: VendorCommand = VendorCommand(0x5e);
    /// The vendor-specific command with the ID 0x5f.
    pub const H5F: VendorCommand = VendorCommand(0x5f);
    /// The vendor-specific command with the ID 0x60.
    pub const H60: VendorCommand = VendorCommand(0x60);
    /// The vendor-specific command with the ID 0x61.
    pub const H61: VendorCommand = VendorCommand(0x61);
    /// The vendor-specific command with the ID 0x62.
    pub const H62: VendorCommand = VendorCommand(0x62);
    /// The vendor-specific command with the ID 0x63.
    pub const H63: VendorCommand = VendorCommand(0x63);
    /// The vendor-specific command with the ID 0x64.
    pub const H64: VendorCommand = VendorCommand(0x64);
    /// The vendor-specific command with the ID 0x65.
    pub const H65: VendorCommand = VendorCommand(0x65);
    /// The vendor-specific command with the ID 0x66.
    pub const H66: VendorCommand = VendorCommand(0x66);
    /// The vendor-specific command with the ID 0x67.
    pub const H67: VendorCommand = VendorCommand(0x67);
    /// The vendor-specific command with the ID 0x68.
    pub const H68: VendorCommand = VendorCommand(0x68);
    /// The vendor-specific command with the ID 0x69.
    pub const H69: VendorCommand = VendorCommand(0x69);
    /// The vendor-specific command with the ID 0x6a.
    pub const H6A: VendorCommand = VendorCommand(0x6a);
    /// The vendor-specific command with the ID 0x6b.
    pub const H6B: VendorCommand = VendorCommand(0x6b);
    /// The vendor-specific command with the ID 0x6c.
    pub const H6C: VendorCommand = VendorCommand(0x6c);
    /// The vendor-specific command with the ID 0x6d.
    pub const H6D: VendorCommand = VendorCommand(0x6d);
    /// The vendor-specific command with the ID 0x6e.
    pub const H6E: VendorCommand = VendorCommand(0x6e);
    /// The vendor-specific command with the ID 0x6f.
    pub const H6F: VendorCommand = VendorCommand(0x6f);
    /// The vendor-specific command with the ID 0x70.
    pub const H70: VendorCommand = VendorCommand(0x70);
    /// The vendor-specific command with the ID 0x71.
    pub const H71: VendorCommand = VendorCommand(0x71);
    /// The vendor-specific command with the ID 0x72.
    pub const H72: VendorCommand = VendorCommand(0x72);
    /// The vendor-specific command with the ID 0x73.
    pub const H73: VendorCommand = VendorCommand(0x73);
    /// The vendor-specific command with the ID 0x74.
    pub const H74: VendorCommand = VendorCommand(0x74);
    /// The vendor-specific command with the ID 0x75.
    pub const H75: VendorCommand = VendorCommand(0x75);
    /// The vendor-specific command with the ID 0x76.
    pub const H76: VendorCommand = VendorCommand(0x76);
    /// The vendor-specific command with the ID 0x77.
    pub const H77: VendorCommand = VendorCommand(0x77);
    /// The vendor-specific command with the ID 0x78.
    pub const H78: VendorCommand = VendorCommand(0x78);
    /// The vendor-specific command with the ID 0x79.
    pub const H79: VendorCommand = VendorCommand(0x79);
    /// The vendor-specific command with the ID 0x7a.
    pub const H7A: VendorCommand = VendorCommand(0x7a);
    /// The vendor-specific command with the ID 0x7b.
    pub const H7B: VendorCommand = VendorCommand(0x7b);
    /// The vendor-specific command with the ID 0x7c.
    pub const H7C: VendorCommand = VendorCommand(0x7c);
    /// The vendor-specific command with the ID 0x7d.
    pub const H7D: VendorCommand = VendorCommand(0x7d);
    /// The vendor-specific command with the ID 0x7e.
    pub const H7E: VendorCommand = VendorCommand(0x7e);
    /// The vendor-specific command with the ID 0x7f.
    pub const H7F: VendorCommand = VendorCommand(0x7f);

    /// Creates a new `VendorCommand` from the given command ID if it is within [`Self::RANGE`][].
    pub fn new(id: u8) -> Option<Self> {
        if Self::RANGE.contains(&id) {
            Some(Self(id))
        } else {
            None
        }
    }
}

impl From<VendorCommand> for u8 {
    fn from(command: VendorCommand) -> u8 {
        command.0
    }
}
