use crate::error::CoreError;
use crate::error::CoreResult;

/// q attribute attached to vectors, lists, and tables.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Attribute {
    #[default]
    None,
    Sorted,
    Unique,
    Parted,
    Grouped,
}

impl From<Attribute> for i8 {
    fn from(value: Attribute) -> Self {
        match value {
            Attribute::None => 0,
            Attribute::Sorted => 1,
            Attribute::Unique => 2,
            Attribute::Parted => 3,
            Attribute::Grouped => 4,
        }
    }
}

impl TryFrom<i8> for Attribute {
    type Error = CoreError;

    fn try_from(value: i8) -> CoreResult<Self> {
        match value {
            0 => Ok(Self::None),
            1 => Ok(Self::Sorted),
            2 => Ok(Self::Unique),
            3 => Ok(Self::Parted),
            4 => Ok(Self::Grouped),
            _ => Err(CoreError::InvalidAttribute(value)),
        }
    }
}

/// q primitive domain shared by atoms and homogeneous vectors.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Primitive {
    Boolean,
    Guid,
    Byte,
    Short,
    Int,
    Long,
    Real,
    Float,
    Char,
    Symbol,
    Timestamp,
    Month,
    Date,
    Datetime,
    Timespan,
    Minute,
    Second,
    Time,
    Mixed,
}

impl Primitive {
    /// Fixed-width byte width for primitives that have one on the wire.
    pub fn width(self) -> Option<usize> {
        match self {
            Self::Boolean | Self::Byte | Self::Char => Some(1),
            Self::Short => Some(2),
            Self::Int
            | Self::Real
            | Self::Month
            | Self::Date
            | Self::Minute
            | Self::Second
            | Self::Time => Some(4),
            Self::Long | Self::Float | Self::Timestamp | Self::Datetime | Self::Timespan => Some(8),
            Self::Guid => Some(16),
            Self::Symbol | Self::Mixed => None,
        }
    }
}

/// Top-level q structural shape.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Shape {
    Atom,
    Vector,
    List,
    Dictionary,
    Table,
    UnaryPrimitive,
    Error,
}

/// Complete q type descriptor for a decoded value.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ValueType {
    pub primitive: Option<Primitive>,
    pub shape: Shape,
    pub attribute: Option<Attribute>,
    pub sorted: Option<bool>,
}

impl ValueType {
    pub fn atom(primitive: Primitive) -> Self {
        Self {
            primitive: Some(primitive),
            shape: Shape::Atom,
            attribute: None,
            sorted: None,
        }
    }

    pub fn vector(primitive: Primitive, attribute: Attribute) -> Self {
        Self {
            primitive: Some(primitive),
            shape: Shape::Vector,
            attribute: Some(attribute),
            sorted: None,
        }
    }

    pub fn list(attribute: Attribute) -> Self {
        Self {
            primitive: Some(Primitive::Mixed),
            shape: Shape::List,
            attribute: Some(attribute),
            sorted: None,
        }
    }

    pub fn dictionary(sorted: bool) -> Self {
        Self {
            primitive: None,
            shape: Shape::Dictionary,
            attribute: None,
            sorted: Some(sorted),
        }
    }

    pub fn table(attribute: Attribute) -> Self {
        Self {
            primitive: None,
            shape: Shape::Table,
            attribute: Some(attribute),
            sorted: None,
        }
    }

    pub fn unary_primitive() -> Self {
        Self {
            primitive: None,
            shape: Shape::UnaryPrimitive,
            attribute: None,
            sorted: None,
        }
    }
}

/// Raw q IPC type code.
#[repr(i8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TypeCode {
    GeneralList = 0,
    BooleanVector = 1,
    GuidVector = 2,
    ByteVector = 4,
    ShortVector = 5,
    IntVector = 6,
    LongVector = 7,
    RealVector = 8,
    FloatVector = 9,
    CharVector = 10,
    SymbolVector = 11,
    TimestampVector = 12,
    MonthVector = 13,
    DateVector = 14,
    DatetimeVector = 15,
    TimespanVector = 16,
    MinuteVector = 17,
    SecondVector = 18,
    TimeVector = 19,
    Table = 98,
    Dictionary = 99,
    UnaryPrimitive = 101,
    SortedDictionary = 127,
    BooleanAtom = -1,
    GuidAtom = -2,
    ByteAtom = -4,
    ShortAtom = -5,
    IntAtom = -6,
    LongAtom = -7,
    RealAtom = -8,
    FloatAtom = -9,
    CharAtom = -10,
    SymbolAtom = -11,
    TimestampAtom = -12,
    MonthAtom = -13,
    DateAtom = -14,
    DatetimeAtom = -15,
    TimespanAtom = -16,
    MinuteAtom = -17,
    SecondAtom = -18,
    TimeAtom = -19,
    ErrorCode = -128,
}

impl TypeCode {
    pub fn primitive(self) -> Option<Primitive> {
        match self {
            Self::BooleanAtom | Self::BooleanVector => Some(Primitive::Boolean),
            Self::GuidAtom | Self::GuidVector => Some(Primitive::Guid),
            Self::ByteAtom | Self::ByteVector => Some(Primitive::Byte),
            Self::ShortAtom | Self::ShortVector => Some(Primitive::Short),
            Self::IntAtom | Self::IntVector => Some(Primitive::Int),
            Self::LongAtom | Self::LongVector => Some(Primitive::Long),
            Self::RealAtom | Self::RealVector => Some(Primitive::Real),
            Self::FloatAtom | Self::FloatVector => Some(Primitive::Float),
            Self::CharAtom | Self::CharVector => Some(Primitive::Char),
            Self::SymbolAtom | Self::SymbolVector => Some(Primitive::Symbol),
            Self::TimestampAtom | Self::TimestampVector => Some(Primitive::Timestamp),
            Self::MonthAtom | Self::MonthVector => Some(Primitive::Month),
            Self::DateAtom | Self::DateVector => Some(Primitive::Date),
            Self::DatetimeAtom | Self::DatetimeVector => Some(Primitive::Datetime),
            Self::TimespanAtom | Self::TimespanVector => Some(Primitive::Timespan),
            Self::MinuteAtom | Self::MinuteVector => Some(Primitive::Minute),
            Self::SecondAtom | Self::SecondVector => Some(Primitive::Second),
            Self::TimeAtom | Self::TimeVector => Some(Primitive::Time),
            Self::GeneralList
            | Self::Table
            | Self::Dictionary
            | Self::UnaryPrimitive
            | Self::SortedDictionary
            | Self::ErrorCode => None,
        }
    }

    pub fn shape(self) -> Shape {
        match self {
            Self::BooleanAtom
            | Self::GuidAtom
            | Self::ByteAtom
            | Self::ShortAtom
            | Self::IntAtom
            | Self::LongAtom
            | Self::RealAtom
            | Self::FloatAtom
            | Self::CharAtom
            | Self::SymbolAtom
            | Self::TimestampAtom
            | Self::MonthAtom
            | Self::DateAtom
            | Self::DatetimeAtom
            | Self::TimespanAtom
            | Self::MinuteAtom
            | Self::SecondAtom
            | Self::TimeAtom => Shape::Atom,
            Self::BooleanVector
            | Self::GuidVector
            | Self::ByteVector
            | Self::ShortVector
            | Self::IntVector
            | Self::LongVector
            | Self::RealVector
            | Self::FloatVector
            | Self::CharVector
            | Self::SymbolVector
            | Self::TimestampVector
            | Self::MonthVector
            | Self::DateVector
            | Self::DatetimeVector
            | Self::TimespanVector
            | Self::MinuteVector
            | Self::SecondVector
            | Self::TimeVector => Shape::Vector,
            Self::GeneralList => Shape::List,
            Self::Dictionary | Self::SortedDictionary => Shape::Dictionary,
            Self::Table => Shape::Table,
            Self::UnaryPrimitive => Shape::UnaryPrimitive,
            Self::ErrorCode => Shape::Error,
        }
    }
}

impl From<TypeCode> for i8 {
    fn from(value: TypeCode) -> Self {
        value as i8
    }
}

impl TryFrom<i8> for TypeCode {
    type Error = CoreError;

    fn try_from(value: i8) -> CoreResult<Self> {
        match value {
            0 => Ok(Self::GeneralList),
            1 => Ok(Self::BooleanVector),
            2 => Ok(Self::GuidVector),
            4 => Ok(Self::ByteVector),
            5 => Ok(Self::ShortVector),
            6 => Ok(Self::IntVector),
            7 => Ok(Self::LongVector),
            8 => Ok(Self::RealVector),
            9 => Ok(Self::FloatVector),
            10 => Ok(Self::CharVector),
            11 => Ok(Self::SymbolVector),
            12 => Ok(Self::TimestampVector),
            13 => Ok(Self::MonthVector),
            14 => Ok(Self::DateVector),
            15 => Ok(Self::DatetimeVector),
            16 => Ok(Self::TimespanVector),
            17 => Ok(Self::MinuteVector),
            18 => Ok(Self::SecondVector),
            19 => Ok(Self::TimeVector),
            98 => Ok(Self::Table),
            99 => Ok(Self::Dictionary),
            101 => Ok(Self::UnaryPrimitive),
            127 => Ok(Self::SortedDictionary),
            -1 => Ok(Self::BooleanAtom),
            -2 => Ok(Self::GuidAtom),
            -4 => Ok(Self::ByteAtom),
            -5 => Ok(Self::ShortAtom),
            -6 => Ok(Self::IntAtom),
            -7 => Ok(Self::LongAtom),
            -8 => Ok(Self::RealAtom),
            -9 => Ok(Self::FloatAtom),
            -10 => Ok(Self::CharAtom),
            -11 => Ok(Self::SymbolAtom),
            -12 => Ok(Self::TimestampAtom),
            -13 => Ok(Self::MonthAtom),
            -14 => Ok(Self::DateAtom),
            -15 => Ok(Self::DatetimeAtom),
            -16 => Ok(Self::TimespanAtom),
            -17 => Ok(Self::MinuteAtom),
            -18 => Ok(Self::SecondAtom),
            -19 => Ok(Self::TimeAtom),
            -128 => Ok(Self::ErrorCode),
            _ => Err(CoreError::InvalidTypeCode(value)),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn attribute_round_trips() {
        assert_eq!(Attribute::try_from(0).unwrap(), Attribute::None);
        assert_eq!(Attribute::try_from(4).unwrap(), Attribute::Grouped);
        assert!(matches!(
            Attribute::try_from(9),
            Err(CoreError::InvalidAttribute(9))
        ));
    }

    #[test]
    fn type_code_maps_to_expected_shape_and_primitive() {
        let atom = TypeCode::IntAtom;
        let vector = TypeCode::SymbolVector;
        let list = TypeCode::GeneralList;

        assert_eq!(atom.shape(), Shape::Atom);
        assert_eq!(atom.primitive(), Some(Primitive::Int));
        assert_eq!(vector.shape(), Shape::Vector);
        assert_eq!(vector.primitive(), Some(Primitive::Symbol));
        assert_eq!(list.shape(), Shape::List);
        assert_eq!(list.primitive(), None);
    }
}
