// an implementation of Ludus

// curently left undone (and not adding for a while yet):
// * sets
// * interpolated strings & string patterns
// * pkgs, namespaces, imports, `use` forms
// * with forms
// * test forms
// * ignored words

// todo:
// * [x] rewrite fn parser to use chumsky::Recursive::declare/define
//   - [x] do this to extract/simplify/DRY things like tuple patterns, fn clauses, etc.
// * [x] Work around chumsky::Stream::from_iter().spanned disappearing in most recent version
// * [x] investigate using labels (which is behind a compiler flag, somehow)
// * [ ] write parsing errors
// * [ ] wire up Ariadne parsing errors
// * [x] add stack traces and code locations to panics
// * [x] validation
// * [x] break this out into multiple files
// * [x] write a tree-walk VM
//   - [x] learn how to deal with lifetimes
//   - [x] with stack mechanics and refcounting
//   - [ ] with tail-call optimization (nb: this may not be possible w/ a TW-VM)
//   - [ ] with all the necessary forms for current Ludus
//     * [x] guards in match clauses
//     * [x] `as` patterns
//     * [x] splat patterns in tuples, lists, dicts
//     * [x] splats in list and dict literals
//     * [x] `loop` and `recur`
//     * [x] string patterns
//     * [x] string interpolation
//     * [x] docstrings
// * [x] write `base` in Rust
// * [ ] turn this into a library function
// * [ ] compile this into WASM
// * [ ] perf testing

use chumsky::{input::Stream, prelude::*};
use rust_embed::Embed;

mod spans;

mod lexer;
use crate::lexer::*;

mod value;
use crate::value::*;

mod parser;
use crate::parser::*;

mod base;
use crate::base::*;

mod validator;
use crate::validator::*;

mod process;
use crate::process::*;

mod errors;
use crate::errors::*;

mod byte_values;
mod compiler;
mod memory_sandbox;

#[derive(Embed)]
#[folder = "assets/"]
struct Asset;

pub fn prelude<'src>() -> (
    Vec<(String, Value<'src>)>,
    std::collections::HashMap<*const Ast, FnInfo>,
) {
    let prelude = Asset::get("prelude.ld").unwrap().data.into_owned();
    // we know for sure Prelude should live through the whole run of the program
    let leaked = Box::leak(Box::new(prelude));
    let prelude = std::str::from_utf8(leaked).unwrap();

    let (ptoks, perrs) = lexer().parse(prelude).into_output_errors();
    if !perrs.is_empty() {
        println!("Errors lexing Prelude");
        println!("{:?}", perrs);
        panic!();
    }

    let ptoks = ptoks.unwrap();

    let (p_ast, perrs) = parser()
        .parse(Stream::from_iter(ptoks).map((0..prelude.len()).into(), |(t, s)| (t, s)))
        .into_output_errors();
    if !perrs.is_empty() {
        println!("Errors parsing Prelude");
        println!("{:?}", perrs);
        panic!();
    }

    let prelude_parsed = Box::leak(Box::new(p_ast.unwrap()));
    let base_pkg = base();

    let mut v6or = Validator::new(
        &prelude_parsed.0,
        prelude_parsed.1,
        "prelude",
        prelude,
        &base_pkg,
    );
    v6or.validate();

    if !v6or.errors.is_empty() {
        report_invalidation(v6or.errors);
        panic!("interal Ludus error: invalid prelude")
    }

    let mut base_ctx = Process::<'src> {
        input: "prelude",
        src: prelude,
        locals: base_pkg.clone(),
        ast: &prelude_parsed.0,
        span: prelude_parsed.1,
        prelude: vec![],
        fn_info: v6or.fn_info,
    };

    let prelude = base_ctx.eval();

    let mut p_ctx = vec![];

    match prelude {
        Ok(Value::Dict(p_dict)) => {
            for (key, value) in p_dict.iter() {
                p_ctx.push((key.to_string(), value.clone()))
            }
        }
        Ok(_) => {
            println!("Bad Prelude export");
            panic!();
        }
        Err(LErr { msg, .. }) => {
            println!("Error running Prelude");
            println!("{:?}", msg);
            panic!();
        }
    };

    (p_ctx, base_ctx.fn_info)
}

pub fn run(src: &'static str) {
    let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
    if !lex_errs.is_empty() {
        println!("{:?}", lex_errs);
        return;
    }
    let tokens = tokens.unwrap();
    let to_parse = tokens.clone();

    let (parse_result, parse_errors) = parser()
        .parse(Stream::from_iter(to_parse).map((0..src.len()).into(), |(t, s)| (t, s)))
        .into_output_errors();
    if !parse_errors.is_empty() {
        println!("{:?}", parse_errors);
        return;
    }

    let parsed = parse_result.unwrap();

    let (prelude_ctx, mut prelude_fn_info) = prelude();

    let mut v6or = Validator::new(&parsed.0, parsed.1, "script", src, &prelude_ctx);

    v6or.validate();

    if !v6or.errors.is_empty() {
        report_invalidation(v6or.errors);
        return;
    }

    prelude_fn_info.extend(&mut v6or.fn_info.into_iter());

    let mut proc = Process {
        input: "script",
        src,
        locals: vec![],
        prelude: prelude_ctx,
        ast: &parsed.0,
        span: parsed.1,
        fn_info: prelude_fn_info,
    };

    let result = proc.eval();

    match result {
        Ok(result) => println!("{}", result),
        Err(err) => report_panic(err),
    }
}

pub fn main() {
    let src = "
loop (100000, 1) with {
    (1, acc) -> acc
    (n, acc) -> recur (dec (n), add (n, acc))
}
    ";
    run(src);
    // struct_scalpel::print_dissection_info::<value::Value>()
    // struct_scalpel::print_dissection_info::<parser::Ast>();
    // println!("{}", std::mem::size_of::<parser::Ast>())
}
