use std::{cell::RefCell, fmt::Display, rc::Rc};

use super::{
    class::{Class, ClassRef, Instance},
    functions::Function,
    native_fn::NativeFunction,
    Callable,
};

/// A value being handled by the interpreter.
#[derive(Debug, Clone)]
pub enum Value {
    Nil,
    Boolean(bool),
    String(String),
    Number(f64),
    Object(Rc<RefCell<Object>>),
}

#[derive(Debug, Clone)]
pub enum Object {
    NativeFunction(NativeFunction),
    LoxFunction(Function),
    Class(ClassRef),
    Instance(Instance),
}

/* -------------------- *
 * Value implementation *
 * -------------------- */

impl Value {
    /// Check whether a value is truthy or not.
    pub fn is_truthy(&self) -> bool {
        match self {
            Self::Nil => false,
            Self::Boolean(b) => *b,
            _ => true,
        }
    }

    /// Run some code using the callable object wrapped inside a value.
    /// If the value is not callable, an error function will be called
    /// instead.
    pub fn with_callable<Fok, Ferr, Rt>(&self, fok: Fok, ferr: Ferr) -> Rt
    where
        Fok: FnOnce(&dyn Callable) -> Rt,
        Ferr: FnOnce() -> Rt,
    {
        let obj = match self {
            Value::Object(obj_ref) => obj_ref.borrow(),
            _ => return ferr(),
        };
        match &*obj {
            Object::NativeFunction(func) => fok(func),
            Object::LoxFunction(func) => fok(func),
            Object::Class(class) => fok(class),
            Object::Instance(_) => ferr(),
        }
    }

    /// Run some code against an instance value. If the value does not
    /// contain an instance, an error function will be called instead.
    pub fn with_instance<Fok, Ferr, Rt>(&self, fok: Fok, ferr: Ferr) -> Rt
    where
        Fok: FnOnce(&Instance) -> Rt,
        Ferr: FnOnce() -> Rt,
    {
        let obj = match self {
            Value::Object(obj_ref) => obj_ref.borrow(),
            _ => return ferr(),
        };
        match &*obj {
            Object::Instance(inst) => fok(inst),
            _ => ferr(),
        }
    }

    /// Run some code against a mutable instance value. If the value does
    /// not contain an instance, an error function will be called instead.
    pub fn with_instance_mut<Fok, Ferr, Rt>(&self, fok: Fok, ferr: Ferr) -> Rt
    where
        Fok: FnOnce(&mut Instance) -> Rt,
        Ferr: FnOnce() -> Rt,
    {
        let mut obj = match self {
            Value::Object(obj_ref) => obj_ref.borrow_mut(),
            _ => return ferr(),
        };
        match &mut *obj {
            Object::Instance(inst) => fok(inst),
            _ => ferr(),
        }
    }
}

impl PartialEq for Value {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Nil, Self::Nil) => true,
            (Self::Boolean(a), Self::Boolean(b)) => a == b,
            (Self::String(a), Self::String(b)) => a == b,
            (Self::Number(a), Self::Number(b)) => a == b,
            _ => false,
        }
    }
}

impl Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Value::Nil => f.write_str("nil"),
            Value::Boolean(b) => b.fmt(f),
            Value::String(s) => s.fmt(f),
            Value::Number(n) => n.fmt(f),
            Value::Object(obj) => obj.borrow().fmt(f),
        }
    }
}

impl From<NativeFunction> for Value {
    fn from(value: NativeFunction) -> Self {
        Value::Object(Rc::new(RefCell::new(Object::NativeFunction(value))))
    }
}

impl From<Function> for Value {
    fn from(value: Function) -> Self {
        Value::Object(Rc::new(RefCell::new(Object::LoxFunction(value))))
    }
}

impl From<Class> for Value {
    fn from(value: Class) -> Self {
        Value::from(Rc::new(RefCell::new(value)))
    }
}

impl From<ClassRef> for Value {
    fn from(value: ClassRef) -> Self {
        Value::Object(Rc::new(RefCell::new(Object::Class(value))))
    }
}

impl From<Instance> for Value {
    fn from(value: Instance) -> Self {
        Value::Object(Rc::new(RefCell::new(Object::Instance(value))))
    }
}

/* --------------------- *
 * Object implementation *
 * --------------------- */

impl Display for Object {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Object::NativeFunction(func) => func.fmt(f),
            Object::LoxFunction(func) => func.fmt(f),
            Object::Class(cls) => cls.borrow().fmt(f),
            Object::Instance(inst) => inst.fmt(f),
        }
    }
}