use crate::base::*;
use crate::parser::*;
use crate::spans::*;
use imbl::*;
use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;
use struct_scalpel::Dissectible;

#[derive(Clone, Debug)]
pub struct Fn<'src> {
    pub name: String,
    pub body: &'src Vec<Spanned<Ast>>,
    pub doc: Option<String>,
    pub enclosing: Vec<(String, Value<'src>)>,
    pub has_run: bool,
    pub input: &'static str,
    pub src: &'static str,
}

#[derive(Debug, Dissectible)]
pub enum Value<'src> {
    Nil,
    Placeholder,
    Boolean(bool),
    Number(f64),
    Keyword(&'static str),
    InternedString(&'static str),
    AllocatedString(Rc<String>),
    // on the heap for now
    Tuple(Rc<Vec<Self>>),
    Args(Rc<Vec<Self>>),
    List(Vector<Self>),
    Dict(HashMap<&'static str, Self>),
    Box(&'static str, Rc<RefCell<Self>>),
    Fn(Rc<RefCell<Fn<'src>>>),
    FnDecl(&'static str),
    Base(BaseFn<'src>),
    Recur(Vec<Self>),
    // Set(HashSet<Self>),
    // Sets are hard
    // Sets require Eq
    // Eq is not implemented on f64, because NaNs
    // We could use ordered_float::NotNan
    // Let's defer that
    // We're not really using sets in Ludus

    // Other things we're not implementing yet:
    // pkgs, nses, tests
}

impl<'src> Clone for Value<'src> {
    fn clone(&self) -> Value<'src> {
        match self {
            Value::Nil => Value::Nil,
            Value::Boolean(b) => Value::Boolean(*b),
            Value::InternedString(s) => Value::InternedString(s),
            Value::AllocatedString(s) => Value::AllocatedString(s.clone()),
            Value::Keyword(s) => Value::Keyword(s),
            Value::Number(n) => Value::Number(*n),
            Value::Tuple(t) => Value::Tuple(t.clone()),
            Value::Args(a) => Value::Args(a.clone()),
            Value::Fn(f) => Value::Fn(f.clone()),
            Value::FnDecl(name) => Value::FnDecl(name),
            Value::List(l) => Value::List(l.clone()),
            Value::Dict(d) => Value::Dict(d.clone()),
            Value::Box(name, b) => Value::Box(name, b.clone()),
            Value::Placeholder => Value::Placeholder,
            Value::Base(b) => Value::Base(b.clone()),
            Value::Recur(..) => unreachable!(),
        }
    }
}

impl fmt::Display for Value<'_> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Value::Nil => write!(f, "nil"),
            Value::Boolean(b) => write!(f, "{b}"),
            Value::Number(n) => write!(f, "{n}"),
            Value::Keyword(k) => write!(f, ":{k}"),
            Value::InternedString(s) => write!(f, "\"{s}\""),
            Value::AllocatedString(s) => write!(f, "\"{s}\""),
            Value::Fn(fun) => write!(f, "fn {}", fun.borrow().name),
            Value::FnDecl(name) => write!(f, "fn {name}"),
            Value::Tuple(t) | Value::Args(t) => write!(
                f,
                "({})",
                t.iter()
                    .map(|x| x.to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            Value::List(l) => write!(
                f,
                "[{}]",
                l.iter()
                    .map(|x| x.to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            Value::Dict(d) => write!(
                f,
                "#{{{}}}",
                d.iter()
                    .map(|(k, v)| format!(":{k} {v}"))
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            Value::Box(name, value) => {
                write!(
                    f,
                    "box {}: [{}]",
                    name,
                    &value.try_borrow().unwrap().to_string()
                )
            }
            Value::Placeholder => write!(f, "_"),
            Value::Base(..) => unreachable!(),
            Value::Recur(..) => unreachable!(),
        }
    }
}

impl Value<'_> {
    pub fn bool(&self) -> bool {
        !matches!(self, Value::Nil | Value::Boolean(false))
    }
}

impl<'src> PartialEq for Value<'src> {
    fn eq(&self, other: &Value<'src>) -> bool {
        match (self, other) {
            // value equality types
            (Value::Nil, Value::Nil) => true,
            (Value::Boolean(x), Value::Boolean(y)) => x == y,
            (Value::Number(x), Value::Number(y)) => x == y,
            (Value::InternedString(x), Value::InternedString(y)) => x == y,
            (Value::AllocatedString(x), Value::AllocatedString(y)) => x == y,
            (Value::InternedString(x), Value::AllocatedString(y)) => *x == **y,
            (Value::AllocatedString(x), Value::InternedString(y)) => **x == *y,
            (Value::Keyword(x), Value::Keyword(y)) => x == y,
            (Value::Tuple(x), Value::Tuple(y)) => x == y,
            (Value::List(x), Value::List(y)) => x == y,
            (Value::Dict(x), Value::Dict(y)) => x == y,
            // reference equality types
            (Value::Fn(x), Value::Fn(y)) => {
                Rc::<RefCell<Fn<'_>>>::as_ptr(x) == Rc::<RefCell<Fn<'_>>>::as_ptr(y)
            }
            (Value::Box(_, x), Value::Box(_, y)) => {
                Rc::<RefCell<Value<'_>>>::as_ptr(x) == Rc::<RefCell<Value<'_>>>::as_ptr(y)
            }
            _ => false,
        }
    }
}

impl Eq for Value<'_> {}

impl Value<'_> {
    pub fn interpolate(&self) -> String {
        match self {
            Value::Nil => String::new(),
            Value::Boolean(b) => format!("{b}"),
            Value::Number(n) => format!("{n}"),
            Value::Keyword(k) => format!(":{k}"),
            Value::AllocatedString(s) => format!("{s}"),
            Value::InternedString(s) => s.to_string(),
            Value::Box(_, x) => x.borrow().interpolate(),
            Value::Tuple(xs) => xs
                .iter()
                .map(|x| x.interpolate())
                .collect::<Vec<_>>()
                .join(", "),
            Value::List(xs) => xs
                .iter()
                .map(|x| x.interpolate())
                .collect::<Vec<_>>()
                .join(", "),
            Value::Dict(xs) => xs
                .iter()
                .map(|(k, v)| format!(":{} {}", k, v.interpolate()))
                .collect::<Vec<_>>()
                .join(", "),
            Value::Fn(x) => format!("fn {}", x.borrow().name),
            Value::FnDecl(name) => format!("fn {name}"),
            Value::Placeholder => unreachable!(),
            Value::Args(_) => unreachable!(),
            Value::Recur(_) => unreachable!(),
            Value::Base(_) => unreachable!(),
        }
    }
}
