use crate::base::*;
use crate::parser::*;
use crate::value::*;
use imbl::HashMap;
use imbl::Vector;
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Clone, Debug)]
pub struct LudusError {
    pub msg: String,
}

// oy
// lifetimes are a mess
// I need 'src kind of everywhere
// But (maybe) using 'src in eval
// for ctx
// means I can't borrow it mutably
// I guess the question is how to get
// the branches for Ast::Block and Ast::If
// to work with a mutable borrow of ctx

// pub struct Ctx<'src> {
//     pub locals: Vec<(&'src str, Value<'src>)>,
//     // pub names: Vec<&'src str>,
//     // pub values: Vec<Value<'src>>,
// }

// impl<'src> Ctx<'src> {
//     pub fn resolve(&self, name: &'src str) -> Value {
//         if let Some((_, val)) = self.locals.iter().rev().find(|(bound, _)| *bound == name) {
//             val.clone()
//         } else {
//             unreachable!()
//         }
//     }

//     pub fn store(&mut self, name: &'src str, value: Value<'src>) {
//         self.locals.push((name, value));
//     }
// }

type Context<'src> = Vec<(String, Value<'src>)>;

pub fn match_eq<T, U>(x: T, y: T, z: U) -> Option<U>
where
    T: PartialEq,
{
    if x == y {
        Some(z)
    } else {
        None
    }
}

pub fn match_pattern<'src, 'a>(
    patt: &Pattern,
    val: &Value<'src>,
    ctx: &'a mut Context<'src>,
) -> Option<&'a mut Context<'src>> {
    match (patt, val) {
        (Pattern::Nil, Value::Nil) => Some(ctx),
        (Pattern::Placeholder, _) => Some(ctx),
        (Pattern::Number(x), Value::Number(y)) => match_eq(x, y, ctx),
        (Pattern::Boolean(x), Value::Boolean(y)) => match_eq(x, y, ctx),
        (Pattern::Keyword(x), Value::Keyword(y)) => match_eq(x, y, ctx),
        (Pattern::String(x), Value::InternedString(y)) => match_eq(x, y, ctx),
        (Pattern::String(x), Value::AllocatedString(y)) => match_eq(&x.to_string(), y, ctx),
        (Pattern::Interpolated(_, StringMatcher(matcher)), Value::InternedString(y)) => {
            match matcher(y.to_string()) {
                Some(matches) => {
                    let mut matches = matches
                        .iter()
                        .map(|(word, string)| {
                            (
                                word.clone(),
                                Value::AllocatedString(Rc::new(string.clone())),
                            )
                        })
                        .collect::<Vec<_>>();
                    ctx.append(&mut matches);
                    Some(ctx)
                }
                None => None,
            }
        }
        (Pattern::Word(w), val) => {
            ctx.push((w.to_string(), val.clone()));
            Some(ctx)
        }
        (Pattern::As(word, type_str), value) => {
            let ludus_type = r#type(value);
            let type_kw = Value::Keyword(type_str);
            if type_kw == ludus_type {
                ctx.push((word.to_string(), value.clone()));
                Some(ctx)
            } else {
                None
            }
        }
        // todo: add splats to these match clauses
        (Pattern::Tuple(x), Value::Tuple(y)) => {
            let has_splat = x
                .iter()
                .any(|patt| matches!(patt, (Pattern::Splattern(_), _)));
            if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
                return None;
            };
            let to = ctx.len();
            for i in 0..x.len() {
                if let Pattern::Splattern(patt) = &x[i].0 {
                    let mut list = Vector::new();
                    for i in i..y.len() {
                        list.push_back(y[i].clone())
                    }
                    let list = Value::List(list);
                    match_pattern(&patt.0, &list, ctx);
                } else if match_pattern(&x[i].0, &y[i], ctx).is_none() {
                    while ctx.len() > to {
                        ctx.pop();
                    }
                    return None;
                }
            }
            Some(ctx)
        }
        (Pattern::List(x), Value::List(y)) => {
            let has_splat = x
                .iter()
                .any(|patt| matches!(patt, (Pattern::Splattern(_), _)));
            if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
                return None;
            };
            let to = ctx.len();
            for (i, (patt, _)) in x.iter().enumerate() {
                if let Pattern::Splattern(patt) = &patt {
                    let list = Value::List(y.skip(i));
                    match_pattern(&patt.0, &list, ctx);
                } else if match_pattern(patt, y.get(i).unwrap(), ctx).is_none() {
                    while ctx.len() > to {
                        ctx.pop();
                    }
                    return None;
                }
            }
            Some(ctx)
        }
        // TODO: optimize this on several levels
        // - [ ] opportunistic mutation
        // - [ ] get rid of all the pointer indirection in word splats
        (Pattern::Dict(x), Value::Dict(y)) => {
            let has_splat = x
                .iter()
                .any(|patt| matches!(patt, (Pattern::Splattern(_), _)));
            if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
                return None;
            };
            let to = ctx.len();
            let mut matched = vec![];
            for (pattern, _) in x {
                match pattern {
                    Pattern::Pair(key, patt) => {
                        if let Some(val) = y.get(key) {
                            if match_pattern(&patt.0, val, ctx).is_none() {
                                while ctx.len() > to {
                                    ctx.pop();
                                }
                                return None;
                            } else {
                                matched.push(key);
                            }
                        } else {
                            return None;
                        };
                    }
                    Pattern::Splattern(pattern) => match pattern.0 {
                        Pattern::Word(w) => {
                            // TODO: find a way to take ownership
                            // this will ALWAYS make structural changes, because of this clone
                            // we want opportunistic mutation if possible
                            let mut unmatched = y.clone();
                            for key in matched.iter() {
                                unmatched.remove(*key);
                            }
                            ctx.push((w.to_string(), Value::Dict(unmatched)));
                        }
                        Pattern::Placeholder => (),
                        _ => unreachable!(),
                    },
                    _ => unreachable!(),
                }
            }
            Some(ctx)
        }
        _ => None,
    }
}

pub fn match_clauses<'src>(
    value: &Value<'src>,
    clauses: &'src [MatchClause],
    ctx: &mut Context<'src>,
) -> Result<Value<'src>, LudusError> {
    let to = ctx.len();
    for MatchClause { patt, body, guard } in clauses.iter() {
        if let Some(ctx) = match_pattern(&patt.0, value, ctx) {
            let pass_guard = match guard {
                None => true,
                Some((ast, _)) => {
                    let guard_res = eval(ast, ctx);
                    match &guard_res {
                        Err(_) => return guard_res,
                        Ok(val) => val.bool(),
                    }
                }
            };
            if !pass_guard {
                while ctx.len() > to {
                    ctx.pop();
                }
                continue;
            }
            let res = eval(&body.0, ctx);
            while ctx.len() > to {
                ctx.pop();
            }
            return res;
        }
    }
    Err(LudusError {
        msg: "no match".to_string(),
    })
}

pub fn apply<'src>(
    callee: Value<'src>,
    caller: Value<'src>,
    ctx: &mut Context,
) -> Result<Value<'src>, LudusError> {
    match (callee, caller) {
        (Value::Keyword(kw), Value::Dict(dict)) => {
            if let Some(val) = dict.get(kw) {
                Ok(val.clone())
            } else {
                Ok(Value::Nil)
            }
        }
        (Value::Dict(dict), Value::Keyword(kw)) => {
            if let Some(val) = dict.get(kw) {
                Ok(val.clone())
            } else {
                Ok(Value::Nil)
            }
        }
        (Value::Fn(f), Value::Tuple(args)) => {
            let args = Value::Tuple(args);
            match_clauses(&args, f.body, ctx)
        }
        (Value::Fn(_f), Value::Args(_args)) => todo!(),
        (_, Value::Keyword(_)) => Ok(Value::Nil),
        (_, Value::Args(_)) => Err(LudusError {
            msg: "you may only call a function".to_string(),
        }),
        (Value::Base(f), Value::Tuple(args)) => match f {
            Base::Nullary(f) => {
                if args.len() != 0 {
                    Err(LudusError {
                        msg: "wrong arity: expected 0 arguments".to_string(),
                    })
                } else {
                    Ok(f())
                }
            }
            Base::Unary(f) => {
                if args.len() != 1 {
                    Err(LudusError {
                        msg: "wrong arity: expected 1 argument".to_string(),
                    })
                } else {
                    Ok(f(&args[0]))
                }
            }
            Base::Binary(r#fn) => {
                if args.len() != 2 {
                    Err(LudusError {
                        msg: "wrong arity: expected 2 arguments".to_string(),
                    })
                } else {
                    Ok(r#fn(&args[0], &args[1]))
                }
            }
            Base::Ternary(f) => {
                if args.len() != 3 {
                    Err(LudusError {
                        msg: "wrong arity: expected 3 arguments".to_string(),
                    })
                } else {
                    Ok(f(&args[0], &args[1], &args[2]))
                }
            }
        },
        _ => unreachable!(),
    }
}

pub fn eval<'src, 'a>(
    ast: &'src Ast,
    ctx: &'a mut Vec<(String, Value<'src>)>,
) -> Result<Value<'src>, LudusError> {
    match ast {
        Ast::Nil => Ok(Value::Nil),
        Ast::Boolean(b) => Ok(Value::Boolean(*b)),
        Ast::Number(n) => Ok(Value::Number(*n)),
        Ast::Keyword(k) => Ok(Value::Keyword(k)),
        Ast::String(s) => Ok(Value::InternedString(s)),
        Ast::Interpolated(parts) => {
            let mut interpolated = String::new();
            for part in parts {
                match &part.0 {
                    StringPart::Data(s) => interpolated.push_str(s.as_str()),
                    StringPart::Word(w) => {
                        let val = if let Some((_, value)) =
                            ctx.iter().rev().find(|(name, _)| w == name)
                        {
                            value.clone()
                        } else {
                            return Err(LudusError {
                                msg: format!("unbound name {w}"),
                            });
                        };
                        interpolated.push_str(val.interpolate().as_str())
                    }
                    StringPart::Inline(_) => unreachable!(),
                }
            }
            Ok(Value::AllocatedString(Rc::new(interpolated)))
        }
        Ast::Block(exprs) => {
            let to = ctx.len();
            let mut result = Value::Nil;
            for (expr, _) in exprs {
                result = eval(expr, ctx)?;
            }
            while ctx.len() > to {
                ctx.pop();
            }
            Ok(result)
        }
        Ast::If(cond, if_true, if_false) => {
            let truthy = eval(&cond.0, ctx)?.bool();
            if truthy {
                eval(&if_true.0, ctx)
            } else {
                eval(&if_false.0, ctx)
            }
        }
        Ast::List(members) => {
            let mut vect = Vector::new();
            for member in members {
                if let Ast::Splat(_) = member.0 {
                    let to_splat = eval(&member.0, ctx)?;
                    match to_splat {
                        Value::List(list) => vect.append(list),
                        _ => {
                            return Err(LudusError {
                                msg: "only lists may be splatted into lists".to_string(),
                            })
                        }
                    }
                } else {
                    vect.push_back(eval(&member.0, ctx)?)
                }
            }
            Ok(Value::List(vect))
        }
        Ast::Tuple(members) => {
            let mut vect = Vec::new();
            for member in members {
                vect.push(eval(&member.0, ctx)?);
            }
            Ok(Value::Tuple(Rc::new(vect)))
        }
        Ast::Word(w) | Ast::Splat(w) => {
            let val = if let Some((_, value)) = ctx.iter().rev().find(|(name, _)| w == name) {
                value.clone()
            } else {
                return Err(LudusError {
                    msg: format!("unbound name {w}"),
                });
            };
            Ok(val)
        }
        Ast::Let(patt, expr) => {
            let val = eval(&expr.0, ctx)?;
            match match_pattern(&patt.0, &val, ctx) {
                Some(_) => Ok(val),
                None => Err(LudusError {
                    msg: "No match".to_string(),
                }),
            }
        }
        Ast::Placeholder => Ok(Value::Placeholder),
        Ast::Error => unreachable!(),
        Ast::Arguments(a) => {
            let mut args = vec![];
            for (arg, _) in a.iter() {
                let arg = eval(arg, ctx)?;
                args.push(arg);
            }
            if args.iter().any(|arg| matches!(arg, Value::Placeholder)) {
                Ok(Value::Args(Rc::new(args)))
            } else {
                Ok(Value::Tuple(Rc::new(args)))
            }
        }
        Ast::Dict(terms) => {
            let mut dict = HashMap::new();
            for term in terms {
                let (term, _) = term;
                match term {
                    Ast::Pair(key, value) => {
                        let value = eval(&value.0, ctx)?;
                        dict.insert(*key, value);
                    }
                    Ast::Splat(_) => {
                        let resolved = eval(term, ctx)?;
                        let Value::Dict(to_splat) = resolved else {
                            return Err(LudusError {
                                msg: "cannot splat non-dict into dict".to_string(),
                            });
                        };
                        dict = to_splat.union(dict);
                    }
                    _ => unreachable!(),
                }
            }
            Ok(Value::Dict(dict))
        }
        Ast::Box(name, expr) => {
            let val = eval(&expr.0, ctx)?;
            let boxed = Value::Box(name, Rc::new(RefCell::new(val)));
            ctx.push((name.to_string(), boxed.clone()));
            Ok(boxed)
        }
        Ast::Synthetic(root, first, rest) => {
            let root = eval(&root.0, ctx)?;
            let first = eval(&first.0, ctx)?;
            let mut curr = apply(root, first, ctx)?;
            for term in rest.iter() {
                let next = eval(&term.0, ctx)?;
                curr = apply(curr, next, ctx)?;
            }
            Ok(curr)
        }
        Ast::When(clauses) => {
            for clause in clauses.iter() {
                let WhenClause { cond, body } = &clause.0;
                if eval(&cond.0, ctx)?.bool() {
                    return eval(&body.0, ctx);
                };
            }
            Err(LudusError {
                msg: "no match".to_string(),
            })
        }
        Ast::Match(value, clauses) => {
            let value = eval(&value.0, ctx)?;
            match_clauses(&value, clauses, ctx)
        }
        Ast::Fn(name, clauses, doc) => {
            let doc = doc.map(|s| s.to_string());
            let the_fn = Value::Fn::<'src>(Rc::new(Fn::<'src> {
                name: name.to_string(),
                body: clauses,
                doc,
            }));
            ctx.push((name.to_string(), the_fn.clone()));
            Ok(the_fn)
        }
        Ast::FnDeclaration(_name) => todo!(),
        Ast::Panic(msg) => {
            let msg = eval(&msg.0, ctx)?;
            Err(LudusError {
                msg: msg.to_string(),
            })
        }
        Ast::Repeat(times, body) => {
            let times_num = match eval(&times.0, ctx) {
                Ok(Value::Number(n)) => n as usize,
                _ => {
                    return Err(LudusError {
                        msg: "repeat may only take numbers".to_string(),
                    })
                }
            };
            for _ in 0..times_num {
                eval(&body.0, ctx)?;
            }
            Ok(Value::Nil)
        }
        Ast::Do(terms) => {
            let mut result = eval(&terms[0].0, ctx)?;
            for (term, _) in terms.iter().skip(1) {
                let next = eval(term, ctx)?;
                let arg = Value::Tuple(Rc::new(vec![result]));
                result = apply(next, arg, ctx)?;
            }
            Ok(result)
        }
        Ast::Pair(..) => {
            unreachable!()
        }
        Ast::Loop(init, clauses) => {
            let mut args = eval(&init.0, ctx)?;
            loop {
                let result = match_clauses(&args, clauses, ctx)?;
                if let Value::Recur(recur_args) = result {
                    args = Value::Tuple(Rc::new(recur_args));
                } else {
                    return Ok(result);
                }
            }
        }
        Ast::Recur(args) => {
            let mut vect = Vec::new();
            for arg in args {
                vect.push(eval(&arg.0, ctx)?);
            }
            Ok(Value::Recur(vect))
        }
    }
}
