use crate::base::BaseFn;
use crate::chunk::Chunk;
use imbl::{HashMap, Vector};
use ordered_float::NotNan;
use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Clone, Debug)]
pub enum LFn {
    Declared {
        name: &'static str,
    },
    Defined {
        name: &'static str,
        doc: Option<&'static str>,
        arities: Vec<u8>,
        chunks: Vec<Chunk>,
        splat: u8,
        closed: RefCell<Vec<Value>>,
        patterns: Vec<String>,
    },
}

impl LFn {
    pub fn close(&self, value: Value) {
        match self {
            LFn::Declared { .. } => unreachable!(),
            LFn::Defined { closed, .. } => {
                let shown = value.show();
                closed.borrow_mut().push(value);
                let pos = closed.borrow().len();
                if crate::DEBUG_SCRIPT_RUN {
                    println!("closing over in {} at {pos}: {shown}", self.name(),);
                }
            }
        }
    }

    pub fn patterns(&self) -> Value {
        match self {
            LFn::Declared { .. } => unreachable!(),
            LFn::Defined { patterns, .. } => Value::from_string(
                patterns
                    .iter()
                    .map(|pattern| format!("  {pattern}"))
                    .collect::<Vec<_>>()
                    .join("\n"),
            ),
        }
    }

    pub fn doc(&self) -> Value {
        match self {
            LFn::Declared { name } => {
                Value::String(Rc::new(format!("fn {name}: undefined function")))
            }
            LFn::Defined { doc: Some(doc), .. } => Value::String(Rc::new(doc.to_string())),
            LFn::Defined { .. } => Value::String(Rc::new("no documentation found".to_string())),
        }
    }

    pub fn accepts(&self, arity: u8) -> bool {
        match self {
            LFn::Defined { arities, splat, .. } => {
                if arities.contains(&arity) {
                    return true;
                }
                if *splat == 0 {
                    return false;
                }
                let max_arity = arities.iter().fold(0, |a, b| a.max(*b));
                arity > max_arity
            }
            LFn::Declared { .. } => unreachable!(),
        }
    }

    pub fn splat_arity(&self) -> u8 {
        match self {
            LFn::Defined { splat, .. } => *splat,
            LFn::Declared { .. } => unreachable!(),
        }
    }

    pub fn name(&self) -> &'static str {
        match self {
            LFn::Declared { name } | LFn::Defined { name, .. } => name,
        }
    }

    pub fn chunk(&self, arity: u8) -> &Chunk {
        match self {
            LFn::Declared { .. } => unreachable!(),
            LFn::Defined {
                arities,
                splat,
                chunks,
                ..
            } => {
                let chunk_pos = arities.iter().position(|a| arity == *a);
                match chunk_pos {
                    Some(pos) => &chunks[pos],
                    None => &chunks[*splat as usize],
                }
            }
        }
    }

    pub fn upvalue(&self, idx: u8) -> Value {
        match self {
            LFn::Declared { .. } => unreachable!(),
            LFn::Defined { closed, .. } => closed.borrow()[idx as usize].clone(),
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct Partial {
    pub args: Vec<Value>,
    pub name: &'static str,
    pub function: Value,
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Key {
    Keyword(&'static str),
    Interned(&'static str),
    String(Rc<String>),
}

impl std::fmt::Display for Key {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Key::Keyword(s) => write!(f, ":{s}"),
            Key::Interned(s) => write!(f, "\"{s}\""),
            Key::String(s) => write!(f, "\"{s}\""),
        }
    }
}

impl Serialize for Key {
    fn serialize<S>(&self, srlzr: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            Key::Keyword(s) => srlzr.serialize_str(s),
            Key::Interned(s) => srlzr.serialize_str(s),
            Key::String(s) => srlzr.serialize_str(s.as_str()),
        }
    }
}

impl Key {
    pub fn to_value(&self) -> Value {
        match self {
            Key::Keyword(s) => Value::Keyword(s),
            Key::Interned(s) => Value::Interned(s),
            Key::String(s) => Value::String(s.clone()),
        }
    }

    pub fn from_value(value: Value) -> Key {
        match value {
            Value::Keyword(s) => Key::Keyword(s),
            Value::Interned(s) => Key::Interned(s),
            Value::String(s) => Key::String(s.clone()),
            _ => unreachable!("dict keys must be keywords or strings"),
        }
    }
}

#[derive(Clone, Debug)]
pub enum Value {
    Nothing,
    Nil,
    True,
    False,
    Keyword(&'static str),
    Interned(&'static str),
    String(Rc<String>),
    Number(NotNan<f64>),
    Tuple(Rc<Vec<Value>>),
    List(Box<Vector<Value>>),
    Dict(Box<HashMap<Key, Value>>), // not hashable b/c why?
    Box(Rc<RefCell<Value>>),        // not hashable b/c refcell
    Fn(Rc<LFn>),                    // not hashable b/c refcell
    BaseFn(Box<BaseFn>),
    Partial(Rc<Partial>),
    Process,
}

impl PartialEq for Value {
    fn eq(&self, other: &Value) -> bool {
        use Value::*;
        match (self, other) {
            (Nothing, Nothing) | (Nil, Nil) | (True, True) | (False, False) => true,
            (Keyword(str1), Keyword(str2)) | (Interned(str1), Interned(str2)) => str1 == str2,
            (String(x), String(y)) => x == y,
            (String(x), Interned(y)) => x.as_ref() == y,
            (Interned(x), String(y)) => x == y.as_ref(),
            (Number(x), Number(y)) => x == y,
            (Tuple(x), Tuple(y)) => x == y,
            (List(x), List(y)) => x == y,
            (Dict(x), Dict(y)) => x == y,
            (Box(x), Box(y)) => std::ptr::eq(x.as_ref().as_ptr(), y.as_ref().as_ptr()),
            (Fn(x), Fn(y)) => std::ptr::eq(x, y),
            (BaseFn(x), BaseFn(y)) => std::ptr::eq(x, y),
            (Partial(x), Partial(y)) => x == y,
            _ => false,
        }
    }
}

impl std::fmt::Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        use Value::*;
        match self {
            Nothing => write!(f, "_"),
            Nil => write!(f, "nil"),
            True => write!(f, "true"),
            False => write!(f, "false"),
            Keyword(str) => write!(f, ":{str}"),
            Interned(str) => write!(f, "\"{str}\""),
            String(str) => write!(f, "\"{str}\""),
            Number(n) => write!(f, "{n}"),
            Process => write!(f, "Process"),
            Tuple(members) => write!(
                f,
                "({})",
                members
                    .iter()
                    .map(|x| x.to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            List(members) => write!(
                f,
                "[{}]",
                members
                    .iter()
                    .map(|x| x.to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            Dict(members) => write!(
                f,
                "#{{{}}}",
                members
                    .iter()
                    .map(|(k, v)| format!("{k} {v}"))
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            Box(value) => write!(f, "box {{ {} }}", value.as_ref().borrow()),
            Fn(lfn) => write!(f, "fn {}", lfn.name()),
            BaseFn(inner) => {
                let name = match **inner {
                    crate::base::BaseFn::Nullary(name, _)
                    | crate::base::BaseFn::Unary(name, _)
                    | crate::base::BaseFn::Binary(name, _)
                    | crate::base::BaseFn::Ternary(name, _) => name,
                };
                write!(f, "fn {name}/base")
            }
            Partial(partial) => write!(f, "fn {}/partial", partial.name),
        }
    }
}

impl Serialize for Value {
    fn serialize<S>(&self, srlzr: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        use Value::*;
        match self {
            Nil => srlzr.serialize_none(),
            True => srlzr.serialize_bool(true),
            False => srlzr.serialize_bool(false),
            Number(n) => srlzr.serialize_f64(f64::from(*n)),
            Interned(s) => srlzr.serialize_str(s),
            Keyword(k) => srlzr.serialize_str(k),
            String(s) => srlzr.serialize_str(s.as_str()),
            Tuple(t) => {
                let mut seq = srlzr.serialize_seq(Some(t.len()))?;
                for e in t.iter() {
                    seq.serialize_element(e)?;
                }
                seq.end()
            }
            List(l) => {
                let mut seq = srlzr.serialize_seq(Some(l.len()))?;
                for e in l.iter() {
                    seq.serialize_element(e)?;
                }
                seq.end()
            }
            Dict(d) => {
                let mut map = srlzr.serialize_map(Some(d.len()))?;
                for (k, v) in d.iter() {
                    map.serialize_entry(k, v)?;
                }
                map.end()
            }
            Box(b) => {
                let boxed = b.borrow();
                (*boxed).serialize(srlzr)
            }
            Fn(..) | BaseFn(..) | Partial(..) => unreachable!(),
            Process | Nothing => unreachable!(),
        }
    }
}

impl Value {
    pub fn show(&self) -> String {
        use Value::*;
        let mut out = match &self {
            Process => "Process".to_string(),
            Nil => "nil".to_string(),
            True => "true".to_string(),
            False => "false".to_string(),
            Number(n) => format!("{n}"),
            Interned(str) => format!("\"{str}\""),
            String(str) => format!("\"{str}\""),
            Keyword(str) => format!(":{str}"),
            Tuple(t) => {
                let members = t.iter().map(|e| e.show()).collect::<Vec<_>>().join(", ");
                format!("({members})")
            }
            List(l) => {
                let members = l.iter().map(|e| e.show()).collect::<Vec<_>>().join(", ");
                format!("[{members}]")
            }
            Dict(d) => {
                let members = d
                    .iter()
                    .map(|(k, v)| {
                        let value_show = v.show();
                        format!("{k} {value_show}")
                    })
                    .collect::<Vec<_>>()
                    .join(", ");
                format!("#{{{members}}}")
            }
            Box(x) => format!("box {{ {} }}", x.as_ref().borrow().show()),
            Fn(lfn) => format!("fn {}", lfn.name()),
            Partial(partial) => format!("fn {}/partial", partial.name),
            BaseFn(_) => format!("{self}"),
            Nothing => "_".to_string(),
        };
        if out.len() > 1000 {
            out.truncate(997);
            format!("{out}...")
        } else {
            out
        }
    }

    pub fn stringify(&self) -> String {
        use Value::*;
        match &self {
            Process => "process".to_string(),
            Nil => "nil".to_string(),
            True => "true".to_string(),
            False => "false".to_string(),
            Number(n) => format!("{n}"),
            Interned(str) => str.to_string(),
            Keyword(str) => format!(":{str}"),
            Tuple(t) => {
                let members = t
                    .iter()
                    .map(|e| e.stringify())
                    .collect::<Vec<_>>()
                    .join(", ");
                format!("({members})")
            }
            List(l) => {
                let members = l
                    .iter()
                    .map(|e| e.stringify())
                    .collect::<Vec<_>>()
                    .join(", ");
                format!("[{members}]")
            }
            Dict(d) => {
                let members = d
                    .iter()
                    .map(|(k, v)| {
                        let value_show = v.stringify();
                        format!("{k} {value_show}")
                    })
                    .collect::<Vec<_>>()
                    .join(", ");
                format!("#{{{members}}}")
            }
            String(s) => s.as_ref().clone(),
            Box(x) => x.as_ref().borrow().stringify(),
            Fn(lfn) => format!("fn {}", lfn.name()),
            Partial(partial) => format!("fn {}/partial", partial.name),
            BaseFn(_) => format!("{self}"),
            Nothing => unreachable!(),
        }
    }

    pub fn type_of(&self) -> &'static str {
        use Value::*;
        match self {
            Nothing => unreachable!(),
            Nil => "nil",
            True => "bool",
            False => "bool",
            Keyword(..) => "keyword",
            Interned(..) => "string",
            String(..) => "string",
            Number(..) => "number",
            Tuple(..) => "tuple",
            List(..) => "list",
            Dict(..) => "dict",
            Box(..) => "box",
            Fn(..) => "fn",
            BaseFn(..) => "fn",
            Partial(..) => "fn",
            Process => "process",
        }
    }

    pub fn as_fn(&self) -> &LFn {
        match self {
            Value::Fn(ref inner) => inner,
            _ => unreachable!("expected value to be fn"),
        }
    }

    pub fn as_list(&self) -> &Vector<Value> {
        match self {
            Value::List(ref inner) => inner,
            _ => unreachable!("expected value to be list"),
        }
    }

    pub fn as_box(&self) -> Rc<RefCell<Value>> {
        match self {
            Value::Box(inner) => inner.clone(),
            _ => unreachable!("expected value to be a box"),
        }
    }

    pub fn as_string(&self) -> Rc<String> {
        match self {
            Value::String(str) => str.clone(),
            Value::Interned(str) => Rc::new(str.to_string()),
            _ => unreachable!("expected value to be a string"),
        }
    }

    pub fn as_tuple(&self) -> Rc<Vec<Value>> {
        match self {
            Value::Tuple(members) => members.clone(),
            _ => unreachable!("expected value to be a tuple"),
        }
    }

    pub fn from_string(str: String) -> Value {
        Value::String(Rc::new(str))
    }

    pub fn list(list: Vector<Value>) -> Value {
        Value::List(Box::new(list))
    }

    pub fn new_list() -> Value {
        Value::list(Vector::new())
    }

    pub fn r#box(value: Value) -> Value {
        Value::Box(Rc::new(RefCell::new(value)))
    }

    pub fn tuple(vec: Vec<Value>) -> Value {
        Value::Tuple(Rc::new(vec))
    }

    pub fn from_f64(f: f64) -> Value {
        Value::Number(NotNan::new(f).unwrap())
    }

    pub fn from_usize(n: usize) -> Value {
        Value::Number(NotNan::new(n as f64).unwrap())
    }

    pub fn as_f64(&self) -> f64 {
        match self {
            Value::Number(n) => f64::from(*n),
            _ => unreachable!("expected value to be a number"),
        }
    }

    // pub fn get_shared_box(&self, name: &'static str) -> Value {
    //     match self {
    //         Value::Dict(dict) => dict
    //             .get(name)
    //             .expect("expected dict to have requested value")
    //             .clone(),
    //         _ => unreachable!("expected dict"),
    //     }
    // }
}
