#![allow(unused_imports)]
#![allow(dead_code)]

use crust::{
    ast::{Block, Cmd, Expr, Func, Op, Span, SpanExpr},
    parse::{parse_block, parse_expr},
};

use inkwell::{
    builder::Builder,
    context::Context,
    execution_engine::{ExecutionEngine, JitFunction},
    module::Module,
    passes::PassManager,
    types::BasicTypeEnum,
    values::{BasicValueEnum, FloatValue, FunctionValue, InstructionValue, IntValue, PointerValue},
    FloatPredicate, OptimizationLevel,
};
use std::collections::HashMap;
use std::error::Error;

type ExprFunc = unsafe extern "C" fn() -> i32;

fn main() -> Result<(), Box<dyn Error>> {
    let context = Context::create();
    let module = context.create_module("expr");
    let builder = context.create_builder();
    let fpm = PassManager::create(&module);
    fpm.initialize();
    let execution_engine = module.create_jit_execution_engine(OptimizationLevel::None)?;

    match parse_block(Span::new(
        "
        {
            let abba : mut i32 = 7;
            abba = 5;
            return  2 + abba
        }
        ",
    )) {
        Ok((_, prog)) => {
            println!("ast : {:?}", &prog);
            let u32_type = context.i32_type();
            let fn_type = u32_type.fn_type(&[], false);
            let function = module.add_function("expr", fn_type, None);
            let basic_block = context.append_basic_block(&function, "entry");
            builder.position_at_end(&basic_block);

            let mut compiler = Compiler {
                context: &context,
                builder: &builder,
                module: &module,
                fn_value_opt: Some(function),
                variables: HashMap::new(),
                //&fpm,
            };
            compiler.compile_block(prog);
            let fun_expr: JitFunction<ExprFunc> =
                unsafe { execution_engine.get_function("expr").ok().unwrap() };

            unsafe {
                println!("\nexecution result : {}", fun_expr.call());
            }
        }
        _ => panic!(),
    }
    module.print_to_stderr();

    Ok(())
}

/// Compiler holds the LLVM state for the compilation
pub struct Compiler<'a> {
    pub context: &'a Context,
    pub builder: &'a Builder,
    // pub fpm: &'a PassManager<FunctionValue>,
    pub module: &'a Module,
    // pub function: &'a Func<'a>,
    variables: HashMap<String, PointerValue>,
    fn_value_opt: Option<FunctionValue>,
}

/// Compilation assumes the program to be semantically correct (well formed)
impl<'a> Compiler<'a> {
    /// Gets a defined function given its name.
    #[inline]
    fn get_function(&self, name: &str) -> Option<FunctionValue> {
        self.module.get_function(name)
    }

    /// Returns the `PointerValue` representing the variable `id`.
    #[inline]
    fn get_variable(&self, id: &str) -> &PointerValue {
        match self.variables.get(id) {
            Some(var) => var,
            None => panic!(
                "Could not find a matching variable, {} in {:?}",
                id, self.variables
            ),
        }
    }

    /// Returns the `FunctionValue` representing the function being compiled.
    #[inline]
    fn fn_value(&self) -> FunctionValue {
        self.fn_value_opt.unwrap()
    }

    /// For now, returns an IntValue
    /// Boolean is i1, single bit integers in LLVM
    /// However we might to choose to store them as i8 or i32
    fn compile_expr(&self, expr: &SpanExpr) -> IntValue {
        match expr.1.clone() {
            Expr::Id(id) => {
                let var = self.get_variable(&id);
                self.builder.build_load(*var, &id).into_int_value()
            }
            Expr::Num(i) => self.context.i32_type().const_int(i as u64, false),

            Expr::BinOp(op, l, r) => {
                let lv = self.compile_expr(&l);
                let rv = self.compile_expr(&r);
                match op {
                    Op::Add => self.builder.build_int_add(lv, rv, "sum"),
                    _ => unimplemented!(),
                }
            }
            // Todo: Binop, Unop, Booleans, Function calls
            _ => unimplemented!(),
        }
    }

    /// Creates a new stack allocation instruction in the entry block of the function.
    fn create_entry_block_alloca(&mut self, name: &str) -> PointerValue {
        let builder = self.context.create_builder();

        let entry = self.fn_value().get_first_basic_block().unwrap();

        match entry.get_first_instruction() {
            Some(first_instr) => builder.position_before(&first_instr),
            None => builder.position_at_end(&entry),
        }
        let alloca = builder.build_alloca(self.context.i32_type(), name);
        self.variables.insert(name.to_string(), alloca);
        alloca
    }

    /// Compiles a command into (InstructionValue, b:bool)
    /// `b` indicates that its a return value of the basic block
    fn compile_cmd(&mut self, cmd: &Cmd) -> (InstructionValue, bool) {
        match cmd {
            Cmd::Assign((_, lexp), rexp) => match lexp {
                Expr::Id(id) => {
                    let var = self.get_variable(id);
                    let rexp = self.compile_expr(rexp);
                    (self.builder.build_store(*var, rexp), false)
                }
                _ => panic!("we are bad"),
            },

            // -- TODO: This is code from my `old` interpreter
            //     Cmd::If(exp, then_block, opt_else) => {
            //         if get_bool(eval_expr(exp, mem, venv, fenv)) {
            //             eval_body(then_block.to_vec(), mem, venv, fenv)
            //         } else {
            //             if let Some(else_block) = opt_else {
            //                 eval_body(else_block.to_vec(), mem, venv, fenv)
            //             } else {
            //                 None
            //             }
            //         }
            //     }
            // -- TODO: While
            Cmd::Let(_, id, _, exp) => {
                let exp = self.compile_expr(exp);
                // allocate a local variable (on the stack)
                let alloca = self.create_entry_block_alloca(id.fragment);
                // store the (initial) value
                let store = self.builder.build_store(alloca, exp);
                (store, false)
            }
            Cmd::Return(exp) => {
                let expr = self.compile_expr(exp);
                (self.builder.build_return(Some(&expr)), true)
            }
            _ => unimplemented!(),
        }
    }

    pub fn compile_block(&mut self, cmds: Vec<Cmd>) -> InstructionValue {
        for c in &cmds {
            let (cmd, ret) = self.compile_cmd(c);
            // early return (e.g., inside a loop/conditional)
            if ret {
                return cmd;
            }
        }
        panic!();
    }

    // TODO, function declarations
}

// SHAMELESSLY STOLEN FROM inkwell!!!!
// // ======================================================================================
// // COMPILER =============================================================================
// // ======================================================================================

// /// Defines the `Expr` compiler.
// pub struct Compiler<'a> {
//     pub context: &'a Context,
//     pub builder: &'a Builder,
//     pub fpm: &'a PassManager<FunctionValue>,
//     pub module: &'a Module,
//     pub function: &'a Func<'a>,

//     variables: HashMap<String, PointerValue>,
//     fn_value_opt: Option<FunctionValue>,
// }

// impl<'a> Compiler<'a> {
//     /// Gets a defined function given its name.
//     #[inline]
//     fn get_function(&self, name: &str) -> Option<FunctionValue> {
//         self.module.get_function(name)
//     }

//     /// Returns the `FunctionValue` representing the function being compiled.
//     #[inline]
//     fn fn_value(&self) -> FunctionValue {
//         self.fn_value_opt.unwrap()
//     }

//     /// Creates a new stack allocation instruction in the entry block of the function.
//     fn create_entry_block_alloca(&self, name: &str) -> PointerValue {
//         let builder = self.context.create_builder();

//         let entry = self.fn_value().get_first_basic_block().unwrap();

//         match entry.get_first_instruction() {
//             Some(first_instr) => builder.position_before(&first_instr),
//             None => builder.position_at_end(&entry),
//         }

//         builder.build_alloca(self.context.f64_type(), name)
//     }

//     /// Compiles the specified `Expr` into an LLVM `FloatValue`.
//     fn compile_expr(
//         &mut self,
//         expr: &Expr,
//     ) -> Result<FloatValue, &'static str> {
//         match *expr {
//             // Expr::Num(nb) => Ok(self.context.i32_type().const_int(nb, true)),

//             // Expr::Variable(ref name) => {
//             //     match self.variables.get(name.as_str()) {
//             //         Some(var) => Ok(self
//             //             .builder
//             //             .build_load(*var, name.as_str())
//             //             .into_float_value()),
//             //         None => Err("Could not find a matching variable."),
//             //     }
//             // }

//             // Expr::VarIn {
//             //     ref variables,
//             //     ref body,
//             // } => {
//             //     let mut old_bindings = Vec::new();

//             //     for &(ref var_name, ref initializer) in variables {
//             //         let var_name = var_name.as_str();

//             //         let initial_val = match *initializer {
//             //             Some(ref init) => self.compile_expr(init)?,
//             //             None => self.context.f64_type().const_float(0.),
//             //         };

//             //         let alloca = self.create_entry_block_alloca(var_name);

//             //         self.builder.build_store(alloca, initial_val);

//             //         if let Some(old_binding) = self.variables.remove(var_name) {
//             //             old_bindings.push(old_binding);
//             //         }

//             //         self.variables.insert(var_name.to_string(), alloca);
//             //     }

//             //     let body = self.compile_expr(body)?;

//             //     for binding in old_bindings {
//             //         self.variables.insert(
//             //             binding.get_name().to_str().unwrap().to_string(),
//             //             binding,
//             //         );
//             //     }

//             //     Ok(body)
//             // }

//             // Expr::Binary {
//             //     op,
//             //     ref left,
//             //     ref right,
//             // } => {
//             //     if op == '=' {
//             //         // handle assignement
//             //         let var_name = match *left.borrow() {
//             //             Expr::Variable(ref var_name) => var_name,
//             //             _ => {
//             //                 return Err("Expected variable as left-hand operator of assignement.");
//             //             }
//             //         };

//             //         let var_val = self.compile_expr(right)?;
//             //         let var = self
//             //             .variables
//             //             .get(var_name.as_str())
//             //             .ok_or("Undefined variable.")?;

//             //         self.builder.build_store(*var, var_val);

//             //         Ok(var_val)
//             //     } else {
//             //         let lhs = self.compile_expr(left)?;
//             //         let rhs = self.compile_expr(right)?;

//             //         match op {
//             //             '+' => {
//             //                 Ok(self.builder.build_float_add(lhs, rhs, "tmpadd"))
//             //             }
//             //             '-' => {
//             //                 Ok(self.builder.build_float_sub(lhs, rhs, "tmpsub"))
//             //             }
//             //             '*' => {
//             //                 Ok(self.builder.build_float_mul(lhs, rhs, "tmpmul"))
//             //             }
//             //             '/' => {
//             //                 Ok(self.builder.build_float_div(lhs, rhs, "tmpdiv"))
//             //             }
//             //             '<' => Ok({
//             //                 let cmp = self.builder.build_float_compare(
//             //                     FloatPredicate::ULT,
//             //                     lhs,
//             //                     rhs,
//             //                     "tmpcmp",
//             //                 );

//             //                 self.builder.build_unsigned_int_to_float(
//             //                     cmp,
//             //                     self.context.f64_type(),
//             //                     "tmpbool",
//             //                 )
//             //             }),
//             //             '>' => Ok({
//             //                 let cmp = self.builder.build_float_compare(
//             //                     FloatPredicate::ULT,
//             //                     rhs,
//             //                     lhs,
//             //                     "tmpcmp",
//             //                 );

//             //                 self.builder.build_unsigned_int_to_float(
//             //                     cmp,
//             //                     self.context.f64_type(),
//             //                     "tmpbool",
//             //                 )
//             //             }),

//             //             custom => {
//             //                 let mut name = String::from("binary");

//             //                 name.push(custom);

//             //                 match self.get_function(name.as_str()) {
//             //                     Some(fun) => {
//             //                         match self
//             //                             .builder
//             //                             .build_call(
//             //                                 fun,
//             //                                 &[lhs.into(), rhs.into()],
//             //                                 "tmpbin",
//             //                             )
//             //                             .try_as_basic_value()
//             //                             .left()
//             //                         {
//             //                             Some(value) => {
//             //                                 Ok(value.into_float_value())
//             //                             }
//             //                             None => Err("Invalid call produced."),
//             //                         }
//             //                     }

//             //                     None => Err("Undefined binary operator."),
//             //                 }
//             //             }
//             //         }
//             //     }
//             // }

//             // Expr::Call {
//             //     ref fn_name,
//             //     ref args,
//             // } => match self.get_function(fn_name.as_str()) {
//             //     Some(fun) => {
//             //         let mut compiled_args = Vec::with_capacity(args.len());

//             //         for arg in args {
//             //             compiled_args.push(self.compile_expr(arg)?);
//             //         }

//             //         let argsv: Vec<BasicValueEnum> = compiled_args
//             //             .iter()
//             //             .by_ref()
//             //             .map(|&val| val.into())
//             //             .collect();

//             //         match self
//             //             .builder
//             //             .build_call(fun, argsv.as_slice(), "tmp")
//             //             .try_as_basic_value()
//             //             .left()
//             //         {
//             //             Some(value) => Ok(value.into_float_value()),
//             //             None => Err("Invalid call produced."),
//             //         }
//             //     }
//             //     None => Err("Unknown function."),
//             // },

//             // Expr::Conditional {
//             //     ref cond,
//             //     ref consequence,
//             //     ref alternative,
//             // } => {
//             //     let parent = self.fn_value();
//             //     let zero_const = self.context.f64_type().const_float(0.0);

//             //     // create condition by comparing without 0.0 and returning an int
//             //     let cond = self.compile_expr(cond)?;
//             //     let cond = self.builder.build_float_compare(
//             //         FloatPredicate::ONE,
//             //         cond,
//             //         zero_const,
//             //         "ifcond",
//             //     );

//             //     // build branch
//             //     let then_bb = self.context.append_basic_block(&parent, "then");
//             //     let else_bb = self.context.append_basic_block(&parent, "else");
//             //     let cont_bb =
//             //         self.context.append_basic_block(&parent, "ifcont");

//             //     self.builder
//             //         .build_conditional_branch(cond, &then_bb, &else_bb);

//             //     // build then block
//             //     self.builder.position_at_end(&then_bb);
//             //     let then_val = self.compile_expr(consequence)?;
//             //     self.builder.build_unconditional_branch(&cont_bb);

//             //     let then_bb = self.builder.get_insert_block().unwrap();

//             //     // build else block
//             //     self.builder.position_at_end(&else_bb);
//             //     let else_val = self.compile_expr(alternative)?;
//             //     self.builder.build_unconditional_branch(&cont_bb);

//             //     let else_bb = self.builder.get_insert_block().unwrap();

//             //     // emit merge block
//             //     self.builder.position_at_end(&cont_bb);

//             //     let phi =
//             //         self.builder.build_phi(self.context.f64_type(), "iftmp");

//             //     phi.add_incoming(&[
//             //         (&then_val, &then_bb),
//             //         (&else_val, &else_bb),
//             //     ]);

//             //     Ok(phi.as_basic_value().into_float_value())
//             // }

//             // Expr::For {
//             //     ref var_name,
//             //     ref start,
//             //     ref end,
//             //     ref step,
//             //     ref body,
//             // } => {
//             //     let parent = self.fn_value();

//             //     let start_alloca = self.create_entry_block_alloca(var_name);
//             //     let start = self.compile_expr(start)?;

//             //     self.builder.build_store(start_alloca, start);

//             //     // go from current block to loop block
//             //     let loop_bb = self.context.append_basic_block(&parent, "loop");

//             //     self.builder.build_unconditional_branch(&loop_bb);
//             //     self.builder.position_at_end(&loop_bb);

//             //     let old_val = self.variables.remove(var_name.as_str());

//             //     self.variables.insert(var_name.to_owned(), start_alloca);

//             //     // emit body
//             //     self.compile_expr(body)?;

//             //     // emit step
//             //     let step = match *step {
//             //         Some(ref step) => self.compile_expr(step)?,
//             //         None => self.context.f64_type().const_float(1.0),
//             //     };

//             //     // compile end condition
//             //     let end_cond = self.compile_expr(end)?;

//             //     let curr_var = self.builder.build_load(start_alloca, var_name);
//             //     let next_var = self.builder.build_float_add(
//             //         curr_var.into_float_value(),
//             //         step,
//             //         "nextvar",
//             //     );

//             //     self.builder.build_store(start_alloca, next_var);

//             //     let end_cond = self.builder.build_float_compare(
//             //         FloatPredicate::ONE,
//             //         end_cond,
//             //         self.context.f64_type().const_float(0.0),
//             //         "loopcond",
//             //     );
//             //     let after_bb =
//             //         self.context.append_basic_block(&parent, "afterloop");

//             //     self.builder
//             //         .build_conditional_branch(end_cond, &loop_bb, &after_bb);
//             //     self.builder.position_at_end(&after_bb);

//             //     self.variables.remove(var_name);

//             //     if let Some(val) = old_val {
//             //         self.variables.insert(var_name.to_owned(), val);
//             //     }

//             //     Ok(self.context.f64_type().const_float(0.0))
//             // }
//             _ => unimplemented!(),
//         }
//     }

//     /// Compiles the specified `Prototype` into an extern LLVM `FunctionValue`.
//     fn compile_prototype(
//         &self,
//         sig: &Sig,
//     ) -> Result<FunctionValue, &'static str> {
//         let ret_type = sig.2;
//         let args_types = std::iter::repeat(ret_type)
//             .take(sig.1.len())
//             .map(|f| f.into())
//             .collect::<Vec<BasicTypeEnum>>();
//         let args_types = args_types.as_slice();

//         let fn_type = self.context.f64_type().fn_type(args_types, false);
//         let fn_val = self.module.add_function(sig.0.as_str(), fn_type, None);

//         // set arguments names
//         for (i, arg) in fn_val.get_param_iter().enumerate() {
//             arg.into_float_value().set_name(proto.args[i].as_str());
//         }

//         // finally return built prototype
//         Ok(fn_val)
//     }

//     /// Compiles the specified `Function` into an LLVM `FunctionValue`.
//     fn compile_fn(&mut self) -> Result<FunctionValue, &'static str> {
//         let sig = &self.function.sig;
//         let function = self.compile_sig(sig)?;

//         // got external function, returning only compiled prototype
//         // if self.function.body.is_none() {
//         //     return Ok(function);
//         // }

//         let entry = self.context.append_basic_block(&function, "entry");

//         self.builder.position_at_end(&entry);

//         // update fn field
//         self.fn_value_opt = Some(function);

//         // build variables map
//         self.variables.reserve(proto.args.len());

//         for (i, arg) in function.get_param_iter().enumerate() {
//             let arg_name = proto.args[i].as_str();
//             let alloca = self.create_entry_block_alloca(arg_name);

//             self.builder.build_store(alloca, arg);

//             self.variables.insert(proto.args[i].clone(), alloca);
//         }

//         // compile body
//         let body = self.compile_expr(self.function.body.as_ref().unwrap())?;

//         self.builder.build_return(Some(&body));

//         // return the whole thing after verification and optimization
//         if function.verify(true) {
//             self.fpm.run_on(&function);

//             Ok(function)
//         } else {
//             unsafe {
//                 function.delete();
//             }

//             Err("Invalid generated function.")
//         }
//     }

//     /// Compiles the specified `Function` in the given `Context` and using the specified `Builder`, `PassManager`, and `Module`.
//     pub fn compile(
//         context: &'a Context,
//         builder: &'a Builder,
//         pass_manager: &'a PassManager<FunctionValue>,
//         module: &'a Module,
//         function: &Function,
//     ) -> Result<FunctionValue, &'static str> {
//         let mut compiler = Compiler {
//             context: context,
//             builder: builder,
//             fpm: pass_manager,
//             module: module,
//             function: function,
//             fn_value_opt: None,
//             variables: HashMap::new(),
//         };

//         compiler.compile_fn()
//     }
// }