use crate::gameobject::{ Node, Transform };

extern crate mlua;
use mlua::Lua as LuaContext;
use mlua::Error::RuntimeError as LuaError;
use mlua::chunk;

use std::rc::Rc;
use std::cell::RefCell;

pub trait ScriptLang {
    fn init(&mut self);
    fn update(&mut self, delta: f32);
}

pub struct Lua {
    ctx: LuaContext,
}

impl mlua::UserData for Node {
    fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
	methods.add_method_mut("find_node", |_, this, name: String| {
	    let child = this.find_node(&name);
	    match child {
		Some(child) => Ok(child),
		None => Err(mlua::Error::RuntimeError("Could not find child".to_string())),
	    }
	});
	methods.add_method("get_transform", |_, this, _: ()| {
	    match this.gameobject.get_transform() {
		Ok(transform) => Ok(transform),
		Err(msg) => Err(LuaError(msg)),
	    }
	});
	methods.add_method_mut("set_transform", |_, this, transform: Transform| {
	    match this.gameobject.set_transform(transform) {
		Ok(()) => Ok(()),
		Err(msg) => Err(LuaError(msg)),
	    }
	});
    }
}

impl<'lua> mlua::ToLua<'lua> for Transform {
    fn to_lua(self, ctx: &'lua LuaContext) -> mlua::Result<mlua::Value<'lua>> {
	let table = ctx.create_table().unwrap();
	table.set("position", <[f32; 3]>::from(self.position))?;
	table.set("rotation", <[f32; 3]>::from(self.rotation))?;
	table.set("scale", <[f32; 3]>::from(self.scale))?;
	mlua::Result::Ok(mlua::Value::Table(table))
    }
}

impl<'lua> mlua::FromLua<'lua> for Transform {
    fn from_lua(lua_value: mlua::Value<'lua>, _: &'lua LuaContext) -> mlua::Result<Self> {
	match lua_value {
	    mlua::Value::Table(table) => {
		let position: [f32; 3] = table.get("position")?;
		let rotation: [f32; 3] = table.get("rotation")?;
		let scale: [f32; 3] = table.get("scale")?;
		Ok(Transform {
		    position: glm::Vec3::from(position),
		    rotation: glm::Vec3::from(rotation),
		    scale: glm::Vec3::from(scale),
		})
	    }
	    _ => Err(LuaError("Could not convert to Transform".to_string())),
	}
    }
}

impl Lua {
    pub fn new(root: Rc<RefCell<Node>>) -> Self {
	// Create a lua context, load std libraries
	let ctx = LuaContext::new();
	// Create the ``couch'' api
	let couch = ctx.create_table().unwrap();
	couch.set("root", ctx.create_userdata(root).unwrap()).unwrap();
	couch.set("debug", ctx.create_function(debug).unwrap()).unwrap();
	couch.set("say_hello", ctx.create_function(say_hello).unwrap()).unwrap();
	// Hook in to globals
	ctx.globals().set("couch", couch).unwrap();
	let path = std::path::Path::new("main.lua");
	let buf = std::fs::read(&path).expect("Could not find main.lua");
	ctx.load(&buf).exec().unwrap();

	Lua {
	    ctx,
	}
    }
}

impl ScriptLang for Lua {
    fn init(&mut self) {
	self.ctx.load(&"init()").exec().ok();
    }

    fn update(&mut self, delta: f32) {
	self.ctx.load(chunk! {
	    update($delta)
	}).exec().ok();
    }
}

macro_rules! luafuncs {
    ($($name:ident($($arg:ident: $type: ty),*) $body:block),*) => {
	$(
	    fn $name(_: &LuaContext, ($($arg),*,): ($($type),*,)) -> Result<(), mlua::Error> $body
	)*
    };
}


luafuncs!{
    debug(msg: String) {
	println!("Couch Debug Message: {}", msg);
	Ok(())
    },
    say_hello(name: String, msg: String) {
	println!("Couch says {} to {}", msg, name);
	Ok(())
    }
}
