use std::io::Write;
use std::{fs::read_to_string, io, path::Path};

use crate::{
    context::Context,
    parser::{Command, CommandType},
    repl::repl,
    scope::Scope,
};

fn number(name: &str, ctx: &mut Context, scope: &mut Scope) -> Result<f64, String> {
    let evaluate = arg(name, ctx, scope)?;
    get_number(
        evaluate,
        format!(
            "for parameter \"{}\" of \"{}\"",
            name,
            ctx.last_call.as_ref().unwrap()
        ),
    )
}

fn string(name: &str, ctx: &mut Context, scope: &mut Scope) -> Result<String, String> {
    let evaluate = arg(name, ctx, scope)?;
    match evaluate.get(0) {
        Some(v) => Ok(v.clone()),
        None => Err(format!(
            "Expected value for parameter \"{}\" of \"{}\"",
            name,
            ctx.last_call.as_ref().unwrap()
        )),
    }
}

fn get_number(val: Vec<String>, at: String) -> Result<f64, String> {
    let Some(a) = val.get(0) else {
        return Err(format!("Expected number {at}"));
    };
    a.parse::<f64>()
        .map_err(|_| format!("Couldn't parse number {at}"))
}

fn unevaluated_arg(name: &str, ctx: &mut Context, scope: &mut Scope) -> Result<Command, String> {
    {
        if let Some(arg) = scope.get_arg() {
            if let Command {
                kind: CommandType::Assign(to),
                start: _,
                end: _,
            } = arg
            {
                Err(format!(", found assignment to \"{}\"", to))
            } else {
                Ok(arg)
            }
        } else {
            Err("".to_string())
        }
    }
    .map_err(|e| {
        format!(
            "Expected parameter \"{}\" for built-in \"{}\"",
            name,
            ctx.last_call.as_ref().unwrap()
        ) + &e
    })
}

fn arg(name: &str, ctx: &mut Context, scope: &mut Scope) -> Result<Vec<String>, String> {
    let arg = unevaluated_arg(name, ctx, scope)?;
    ctx.evaluate(&mut scope.child(vec![arg]))
}

impl Default for Context {
    fn default() -> Context {
        let mut ctx = Context::new();
        ctx.builtin("join", |_, e| {
            e.list = vec![e.list.join("")];
            Ok(())
        });
        ctx.builtin("joinwith", |c, e| {
            let with = &arg("sep", c, e)?[0];
            e.list = vec![e.list.join(with)];
            Ok(())
        });
        ctx.builtin("debug", |ctx, e| {
            repl(ctx, &mut e.clone());
            Ok(())
        });
        ctx.builtin("sum", |_, e| {
            let res = e
                .as_numbers()?
                .into_iter()
                .reduce(|a, b| a + b)
                .map(|a| a.to_string());
            e.list.clear();
            if let Some(a) = res {
                e.list.push(a)
            }
            Ok(())
        });
        ctx.builtin("max", |_, e| {
            let res = e
                .as_numbers()?
                .into_iter()
                .reduce(|a, b| a.max(b))
                .map(|a| a.to_string());
            e.list.clear();
            if let Some(a) = res {
                e.list.push(a)
            }
            Ok(())
        });
        ctx.builtin("sort", |_, e| {
            e.list.sort();
            Ok(())
        });
        ctx.builtin("repeat", |ctx, e| {
            let to_append = &e.list.clone();
            for _ in 1..(number("amount", ctx, e)? as usize) {
                e.list.append(&mut to_append.clone())
            }
            Ok(())
        });
        ctx.builtin("void", |_, e| void(e));
        ctx.builtin("clear", |_, e| void(e));
        ctx.builtin("just", |_, e| void(e));
        ctx.builtin("empty", |_, e| void(e));
        ctx.builtin("false", |_, e| void(e));
        ctx.builtin("only", |_, e| void(e));
        ctx.builtin("none", |_, e| void(e));
        ctx.builtin("words", |_, e| {
            e.list = e
                .list
                .iter()
                .flat_map(|e| e.split(' ').map(|s| s.to_string()).collect::<Vec<String>>())
                .collect();
            Ok(())
        });
        ctx.builtin("chars", |_, e| {
            e.list = e
                .list
                .iter()
                .flat_map(|e| e.chars().map(|c| c.to_string()).collect::<Vec<String>>())
                .collect();
            Ok(())
        });
        ctx.builtin("input", |_, e| {
            let mut s = String::new();
            io::stdout().flush().unwrap();
            io::stdin().read_line(&mut s).unwrap();
            s = s.trim().to_string();
            e.list.push(s);
            Ok(())
        });
        ctx.builtin("error", |ctx, e| Err(arg("msg", ctx, e)?[0].clone()));
        ctx.builtin("on-error", |ctx, e| {
            let subs = arg("pattern", ctx, e)?;
            let res = arg("callback", ctx, e);
            let then = unevaluated_arg("then", ctx, e)?;
            if let Err(msg) = res {
                if msg.contains(&subs[0]) {
                    let mut child = e.child(vec![then]);
                    child.list = vec![format!("{}", msg)];
                    ctx.evaluate(&mut child)?;
                }
            };
            Ok(())
        });
        ctx.builtin("read", |ctx, env| {
            let path = arg("file", ctx, env)?;
            env.list.append(
                &mut path
                    .iter()
                    .map(|p| match read_to_string(Path::new(p)) {
                        Ok(e) => Ok(e.split('\n').map(|e| e.to_string()).collect()),
                        Err(e) => Err(format!("Can't read \"{}\": {}", p, e)),
                    })
                    .collect::<Result<Vec<Vec<String>>, String>>()?
                    .into_iter()
                    .flatten()
                    .collect::<Vec<String>>(),
            );
            Ok(())
        });
        ctx.builtin("to", |ctx, e| {
            let to = number("to", ctx, e)? as i64 + 1;
            let from_numbers = match e.list.pop() {
                Some(e) => vec![e],
                None => Vec::new(),
            };
            let from = get_number(from_numbers, "to go from".to_string())?;
            e.list
                .append(&mut ((from as i64)..to).map(|i| i.to_string()).collect());
            Ok(())
        });
        ctx.builtin("reverse", |_, env| {
            env.list.reverse();
            Ok(())
        });
        ctx.builtin("the", |ctx, env| {
            let object = arg("object", ctx, env)?;
            let key = string("key", ctx, env)?;
            let Some(at) = object.iter().position(|e| e == &key) else {
                env.list.clear();
                return Ok(())
            };
            let Some(size) = object.get(at + 1) else {
                return Err(format!("Invalid object, couldn't find size of key {}", key))
            };
            let size = get_number(vec![size.clone()], format!("size of key {}", key))?;
            env.list = object[at + 2..at + 2 + size as usize].to_vec();
            Ok(())
        });
        ctx.builtin("find", |ctx, env| {
            let to_find = arg("list", ctx, env)?;
            if to_find.is_empty() {
                return Err("Can't search list with empty value".to_string());
            }
            let mut found = Vec::new();
            for (position, window) in env.list.windows(to_find.len()).enumerate() {
                if window == to_find {
                    found.push((position + 1).to_string());
                    break;
                }
            }
            env.list = found;
            Ok(())
        });
        ctx.builtin("numbered", |_ctx, env| {
            env.list = env
                .list
                .iter()
                .enumerate()
                .flat_map(|(i, v)| vec![(i + 1).to_string(), v.clone()])
                .collect();
            Ok(())
        });
        ctx.builtin("entry", |ctx, env| {
            let other = arg("name", ctx, env)?;
            env.list.reverse();
            todo!();
            Ok(())
        });
        ctx.builtin("entries", |ctx, env| {
            let other = arg("name", ctx, env)?;
            env.list.reverse();
            todo!();
            Ok(())
        });
        ctx.builtin("/", |ctx, env| {
            let other = number("dividend", ctx, env)?;
            env.list = env
                .as_numbers()?
                .iter()
                .map(|i| (*i / other).to_string())
                .collect();
            Ok(())
        });
        ctx.builtin("*", |ctx, env| {
            let other = number("factor", ctx, env)?;
            env.list = env
                .as_numbers()?
                .iter()
                .map(|i| (i * other).to_string())
                .collect();
            Ok(())
        });
        ctx.builtin("-", |ctx, env| {
            let other = number("factor", ctx, env)?;
            env.list = env
                .as_numbers()?
                .iter()
                .map(|i| (i - other).to_string())
                .collect();
            Ok(())
        });
        ctx.builtin("+", |ctx, env| {
            let other = number("factor", ctx, env)?;
            env.list = env
                .as_numbers()?
                .iter()
                .map(|i| (i + other).to_string())
                .collect();
            Ok(())
        });
        ctx.builtin("mod", |ctx, env| {
            let other = number("num", ctx, env)?;
            env.list = env
                .as_numbers()?
                .iter()
                .map(|i| (i % other).to_string())
                .collect();
            Ok(())
        });
        ctx.builtin("then", |ctx, env| {
            let body = unevaluated_arg("body", ctx, env)?;
            if env.list.is_empty() {
                env.list.clear()
            } else {
                let mut child = env.child(vec![body]);
                env.list = ctx.evaluate(&mut child)?;
            }
            Ok(())
        });
        ctx.builtin("else", |ctx, env| {
            let body = unevaluated_arg("body", ctx, env)?;
            if env.list.is_empty() {
                let mut child = env.child(vec![body]);
                env.list = ctx.evaluate(&mut child)?;
            }
            Ok(())
        });
        ctx.builtin("is", |ctx, env| {
            let other = arg("other", ctx, env)?;
            if env.list != other {
                env.list.clear();
            } else if other.is_empty() {
                env.list.push("true".to_string());
            }
            Ok(())
        });
        ctx.builtin(">", |ctx, env| {
            if env.list.last().unwrap().parse::<f64>().unwrap() <= number("other", ctx, env)? {
                env.list.clear();
            };
            Ok(())
        });
        ctx.builtin("<", |ctx, env| {
            if env.list.last().unwrap().parse::<f64>().unwrap() >= number("other", ctx, env)? {
                env.list.clear();
            };
            Ok(())
        });
        ctx.builtin("loop", |ctx, env| {
            let body = unevaluated_arg("body", ctx, env)?;
            loop {
                if ctx.exit {
                    ctx.exit = false;
                    break;
                }
                ctx.evaluate(&mut env.child(vec![body.clone()]))?;
            }
            Ok(())
        });
        ctx.builtin("write", |_, env| {
            for line in env.list.iter() {
                println!("{}", line)
            }
            Ok(())
        });
        ctx.builtin("print", |ctx, env| {
            println!("{}", arg("text", ctx, env)?.join("\n"));
            Ok(())
        });
        ctx.builtin("out", |_, env| {
            print!("{}", env.list.join("\n"));
            Ok(())
        });
        ctx.builtin("each", |ctx, env| {
            let body = unevaluated_arg("body", ctx, env)?;
            env.list = env
                .list
                .iter()
                .map(|v| {
                    let mut scope = env.child(vec![body.clone()]);
                    scope.list = vec![v.to_owned()];
                    ctx.evaluate(&mut scope)
                })
                .collect::<Result<Vec<Vec<String>>, String>>()?
                .into_iter()
                .flatten()
                .collect();
            Ok(())
        });
        ctx.builtin("while", |ctx, env| {
            let condition = unevaluated_arg("condition", ctx, env)?;
            let body = unevaluated_arg("body", ctx, env)?;
            let mut result = Vec::new();
            while !ctx.exit {
                if ctx.exit {
                    ctx.exit = false;
                    break;
                }
                let mut scope = env.child(vec![condition.clone()]);
                if ctx.evaluate(&mut scope)?.is_empty() {
                    break;
                };
                let mut scope = env.child(vec![body.clone()]);
                result.append(&mut ctx.evaluate(&mut scope)?);
            }
            env.list.append(&mut result);
            Ok(())
        });
        ctx.builtin("until", |ctx, env| {
            let condition = unevaluated_arg("condition", ctx, env)?;
            let body = unevaluated_arg("body", ctx, env)?;
            let mut result = Vec::new();
            loop {
                if ctx.exit {
                    ctx.exit = false;
                    break;
                }
                let mut scope = env.child(vec![condition.clone()]);
                if !ctx.evaluate(&mut scope)?.is_empty() {
                    break;
                };
                let mut scope = env.child(vec![body.clone()]);
                result.append(&mut ctx.evaluate(&mut scope)?);
            }
            env.list.append(&mut result);
            Ok(())
        });
        ctx.builtin("exit", |ctx, _env| {
            ctx.exit = true;
            Ok(())
        });
        ctx.builtin("seperated-by", |ctx, env| {
            let sep = arg("sep", ctx, env)?;
            let body = unevaluated_arg("body", ctx, env)?;
            let iter = env.list.clone().into_iter().chain(sep.clone().into_iter());
            env.list.clear();
            let mut chunk = Vec::new();
            for v in iter {
                chunk.push(v);
                if chunk.len() >= sep.len() && chunk[chunk.len() - sep.len()..] == sep {
                    for _ in 0..sep.len() {
                        chunk.pop();
                    }
                    let mut child = env.child(vec![body.clone()]);
                    child.list = chunk.clone();
                    chunk.clear();
                    // TODO: This doesn't seem to work.
                    env.list.append(&mut ctx.evaluate(&mut child)?);
                }
            }
            Ok(())
        });
        ctx.builtin("every", |ctx, env| {
            let nth = (arg("step", ctx, env)?[0])
                .parse::<i64>()
                .map_err(|_| "Couldn't parse number".to_string())?;
            env.list = env
                .list
                .iter()
                .enumerate()
                .filter_map(|(i, v)| {
                    if i % (nth as usize) == 0 {
                        Some(v.to_owned())
                    } else {
                        None
                    }
                })
                .collect();
            Ok(())
        });
        ctx.builtin("size", |_, env| {
            env.list = vec![env.list.len().to_string()];
            Ok(())
        });
        ctx.builtin("at", |ctx, env| {
            let mut vals = Vec::new();
            for i in arg("indices", ctx, env)?.into_iter() {
                if let Ok(index) = TryInto::<usize>::try_into(
                    i.parse::<i64>()
                        .map_err(|_| "Couldn't parse index".to_string())?
                        - 1,
                ) {
                    if let Some(v) = env.list.get(index) {
                        vals.push(v.to_string())
                    }
                }
            }
            env.list = vals;
            Ok(())
        });
        ctx.builtin("take", |ctx, env| {
            let mut indices = arg("indices", ctx, env)?
                .into_iter()
                .map(|s| {
                    s.parse::<usize>()
                        .map_err(|_| "Couldn't parse index".to_string())
                })
                .collect::<Result<Vec<usize>, String>>()?;
            indices.sort();
            indices.reverse();
            for i in indices {
                let i = i
                    .checked_sub(1)
                    .ok_or_else(|| "Invalid index".to_string())?;
                if env.list.get(i).is_some() {
                    env.list.remove(i);
                }
            }
            Ok(())
        });
        ctx.builtin("packs", |ctx, env| {
            let mut res = Vec::new();
            let step = arg("size", ctx, env)?
                .get(0)
                .unwrap()
                .parse::<i64>()
                .map_err(|_| "Can't parse number".to_string())?;
            let call = unevaluated_arg("body", ctx, env)?;
            for i in 0..(env.list.len() as f64 / step as f64).floor() as i64 {
                let child = &mut env.child(vec![call.clone()]);
                child.list = (env.list[(i * step) as usize..(i * step + step) as usize]).to_owned();
                res.append(&mut ctx.evaluate(child)?)
            }
            env.list = res;
            Ok(())
        });
        ctx.info("packs", vec!["size", "body"], "Call a body with each pack")
            .unwrap();
        ctx
    }
}

fn void(e: &mut Scope) -> Result<(), String> {
    e.list.clear();
    Ok(())
}