use std::convert::{TryFrom, From};
use std::io;

#[derive(Debug, PartialEq, Eq)]
pub enum Tag {
    False,
    True,
    Float,
    Double,
    End,
    Annotation,
    Embedded,
    SmallInteger(i8),
    MediumInteger(u8),
    SignedInteger,
    String,
    ByteString,
    Symbol,
    Record,
    Sequence,
    Set,
    Dictionary,
}

#[derive(Debug, PartialEq, Eq)]
pub struct InvalidTag(u8);

impl From<InvalidTag> for io::Error {
    fn from(v: InvalidTag) -> Self {
        io::Error::new(io::ErrorKind::InvalidData, format!("Invalid Preserves tag {}", v.0))
    }
}

impl From<InvalidTag> for crate::error::Error {
    fn from(v: InvalidTag) -> Self {
        crate::error::Error::Io(v.into())
    }
}

impl TryFrom<u8> for Tag {
    type Error = InvalidTag;
    fn try_from(v: u8) -> Result<Self, Self::Error> {
        match v {
            0x80 => Ok(Self::False),
            0x81 => Ok(Self::True),
            0x82 => Ok(Self::Float),
            0x83 => Ok(Self::Double),
            0x84 => Ok(Self::End),
            0x85 => Ok(Self::Annotation),
            0x86 => Ok(Self::Embedded),
            0x90..=0x9c => Ok(Self::SmallInteger((v - 0x90) as i8)),
            0x9d..=0x9f => Ok(Self::SmallInteger((v - 0x90) as i8 - 16)),
            0xa0..=0xaf => Ok(Self::MediumInteger(v - 0xa0 + 1)),
            0xb0 => Ok(Self::SignedInteger),
            0xb1 => Ok(Self::String),
            0xb2 => Ok(Self::ByteString),
            0xb3 => Ok(Self::Symbol),
            0xb4 => Ok(Self::Record),
            0xb5 => Ok(Self::Sequence),
            0xb6 => Ok(Self::Set),
            0xb7 => Ok(Self::Dictionary),
            _ => Err(InvalidTag(v))
        }
    }
}

impl From<Tag> for u8 {
    fn from(v: Tag) -> Self {
        match v {
            Tag::False => 0x80,
            Tag::True => 0x81,
            Tag::Float => 0x82,
            Tag::Double => 0x83,
            Tag::End => 0x84,
            Tag::Annotation => 0x85,
            Tag::Embedded => 0x86,
            Tag::SmallInteger(v) => if v < 0 { (v + 16) as u8 + 0x90 } else { v as u8 + 0x90 },
            Tag::MediumInteger(count) => count - 1 + 0xa0,
            Tag::SignedInteger => 0xb0,
            Tag::String => 0xb1,
            Tag::ByteString => 0xb2,
            Tag::Symbol => 0xb3,
            Tag::Record => 0xb4,
            Tag::Sequence => 0xb5,
            Tag::Set => 0xb6,
            Tag::Dictionary => 0xb7,
        }
    }
}
