use std::{
    fmt::{self, Write},
    rc::Rc,
};

mod boolean;
mod closure;
mod cons;
mod function;
mod identifier;
mod iter;
mod keyword;
mod native;
mod number;
mod string;
mod vector;

pub mod convert;

pub use boolean::BooleanValue;
pub use closure::{ClosureValue, UpvalueValue};
pub use cons::ConsCell;
pub use function::BytecodeFunction;
pub use identifier::IdentifierValue;
pub use keyword::Keyword;
pub use native::{NativeFunction, NativeObject, NativeValue};
pub use number::NumberValue;
pub use string::StringValue;
pub use vector::Vector;

pub use iter::ProperListIter;

use crate::{
    compile::syntax::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
    error::ValueConversionError,
};

#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    // Syntactic
    Nil,
    Cons(Rc<ConsCell>),
    Vector(Rc<Vector>),
    Number(NumberValue),
    Boolean(BooleanValue),
    Keyword(Keyword),
    Identifier(IdentifierValue),
    String(StringValue),
    Quasi(Rc<Value>),
    Quote(Rc<Value>),
    Unquote(Rc<Value>),
    UnquoteSplice(Rc<Value>),
    // Semantic
    Closure(ClosureValue),
    Function(Rc<BytecodeFunction>),
    // Native
    NativeFunction(NativeFunction),
    NativeValue(NativeValue),
}

impl Value {
    pub fn is_nil(&self) -> bool {
        matches!(self, Self::Nil)
    }

    pub fn unquote(&self) -> Result<Value, ValueConversionError> {
        match self {
            Self::Quote(value) => Ok(value.as_ref().clone()),
            _ => Err(ValueConversionError {
                expected: "quoted value".into(),
                got: self.clone(),
            }),
        }
    }

    pub fn uncons_ref(&self) -> Option<(&Value, &Value)> {
        match self {
            Self::Cons(cons) => Some((&cons.0, &cons.1)),
            _ => None,
        }
    }

    pub fn quote(self) -> Value {
        Value::Quote(self.into())
    }

    pub fn type_id(&self) -> Value {
        match self {
            Self::Nil | Self::Cons(_) => Value::Identifier("list".into()),
            Self::Identifier(_) => Value::Identifier("identifier".into()),
            Self::Number(_) => Value::Identifier("number".into()),
            Self::Boolean(_) => Value::Identifier("boolean".into()),
            Self::Quote(value) => {
                Value::list_or_nil([Self::Identifier("quote".into()), value.type_id()])
            }
            Self::Quasi(value) => {
                Value::list_or_nil([Self::Identifier("quasi".into()), value.type_id()])
            }
            Self::Unquote(value) => {
                Value::list_or_nil([Self::Identifier("unquote".into()), value.type_id()])
            }
            Self::UnquoteSplice(value) => {
                Value::list_or_nil([Self::Identifier("unquote-splice".into()), value.type_id()])
            }
            Self::Vector(_) => Self::Identifier("vector".into()),
            Self::Keyword(_) => Self::Identifier("keyword".into()),
            Self::String(_) => Self::Identifier("string".into()),
            Self::NativeValue(_) => Self::Identifier("native".into()),
            Self::NativeFunction(_) | Self::Function(_) | Self::Closure(_) => {
                Self::Identifier("function".into())
            }
        }
    }

    pub fn is_trueish(&self) -> bool {
        match self {
            Self::Nil => false,
            Self::Cons(_) => true,
            Self::Vector(vector) => !vector.is_empty(),
            Self::Number(value) => value.is_trueish(),
            Self::String(value) => !value.is_empty(),
            Self::Boolean(BooleanValue(value)) => *value,
            Self::Keyword(_)
            | Self::Identifier(_)
            | Self::Quasi(_)
            | Self::Quote(_)
            | Self::Unquote(_)
            | Self::UnquoteSplice(_)
            | Self::Closure(_)
            | Self::NativeFunction(_)
            | Self::NativeValue(_)
            | Self::Function(_) => true,
        }
    }

    pub fn cons(self, cdr: Value) -> Self {
        Self::Cons(Rc::new(ConsCell(self, cdr)))
    }

    pub fn try_list_or_nil<E, I: IntoIterator<Item = Result<Self, E>>>(items: I) -> Result<Self, E>
    where
        I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
    {
        let iter = items.into_iter();
        let mut result = Value::Nil;
        for element in iter.rev() {
            result = element?.cons(result);
        }
        Ok(result)
    }

    pub fn list_or_nil<I: IntoIterator<Item = Self>>(items: I) -> Self
    where
        I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
    {
        let iter = items.into_iter();
        let mut result = Value::Nil;
        for element in iter.rev() {
            result = element.cons(result);
        }
        result
    }

    pub fn proper_iter<E>(&self, error: E) -> ProperListIter<'_, E> {
        ProperListIter {
            head: Some(self),
            error: Some(error),
        }
    }

    pub fn syntax_iter(&self, location: ExpectedWhere) -> ProperListIter<'_, ParseError> {
        self.proper_iter(ParseError {
            input: self.clone(),
            error: ParseErrorKind::Expected(ExpectedWhat::ProperList, location),
        })
    }

    pub fn as_proper_list(&self) -> Option<Vec<Self>> {
        self.proper_iter(())
            .map(|x| x.cloned())
            .collect::<Result<_, _>>()
            .ok()
    }

    pub fn as_native<T: NativeObject>(&self) -> Result<Rc<T>, ValueConversionError> {
        match self {
            Self::NativeValue(value) if let Some(value) = value.cast_rc() => Ok(value),
            _ => Err(ValueConversionError {
                expected: "native value".into(),
                got: self.clone(),
            }),
        }
    }

    pub fn as_native_ref<T: NativeObject>(&self) -> Result<&T, ValueConversionError> {
        match self {
            Self::NativeValue(value) if let Some(value) = value.cast_ref() => Ok(value),
            _ => Err(ValueConversionError {
                expected: "native value".into(),
                got: self.clone(),
            }),
        }
    }
}

impl fmt::Display for Value {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Nil => write!(f, "NIL"),
            Self::Cons(cons) => {
                f.write_char('(')?;
                fmt::Display::fmt(cons, f)?;
                f.write_char(')')
            }
            Self::Vector(vector) => {
                f.write_str("#[")?;
                fmt::Display::fmt(vector, f)?;
                f.write_char(']')
            }
            Self::Keyword(keyword) => write!(f, "{keyword}"),
            Self::Identifier(identifier) => write!(f, "{identifier}"),
            Self::String(value) => fmt::Display::fmt(value, f),
            Self::Quote(value) => {
                f.write_char('\'')?;
                fmt::Display::fmt(value, f)
            }
            Self::Quasi(value) => {
                f.write_char('`')?;
                fmt::Display::fmt(value, f)
            }
            Self::Unquote(value) => {
                f.write_char(',')?;
                fmt::Display::fmt(value, f)
            }
            Self::UnquoteSplice(value) => {
                f.write_str(",@")?;
                fmt::Display::fmt(value, f)
            }
            Self::Number(value) => fmt::Display::fmt(value, f),
            Self::Boolean(value) => fmt::Display::fmt(value, f),
            Self::Closure(value) => fmt::Display::fmt(value, f),
            Self::Function(value) => fmt::Display::fmt(value, f),
            Self::NativeValue(value) => fmt::Display::fmt(value, f),
            Self::NativeFunction(value) => fmt::Display::fmt(value, f),
        }
    }
}
