use mlua::FromLua;

use super::number::Number;
use super::vector3::Vector3;

#[derive(Clone,Copy)]
pub struct CFrame(glam::Affine3A);

impl CFrame{
	pub fn new(
		x:f32,y:f32,z:f32,
		xx:f32,yx:f32,zx:f32,
		xy:f32,yy:f32,zy:f32,
		xz:f32,yz:f32,zz:f32,
	)->Self{
		Self(glam::Affine3A::from_mat3_translation(
			glam::mat3(
				glam::vec3(xx,yx,zx),
				glam::vec3(xy,yy,zy),
				glam::vec3(xz,yz,zz)
			),
			glam::vec3(x,y,z)
		))
	}
	pub fn point(x:f32,y:f32,z:f32)->Self{
		Self(glam::Affine3A::from_translation(glam::vec3(x,y,z)))
	}
	pub fn angles(x:f32,y:f32,z:f32)->Self{
		Self(glam::Affine3A::from_mat3(glam::Mat3::from_euler(glam::EulerRot::YXZ,y,x,z)))
	}
}

fn vec3_to_glam(v:glam::Vec3A)->rbx_types::Vector3{
	rbx_types::Vector3::new(v.x,v.y,v.z)
}
fn vec3_from_glam(v:rbx_types::Vector3)->glam::Vec3A{
	glam::vec3a(v.x,v.y,v.z)
}

impl From<CFrame> for rbx_types::CFrame{
	fn from(CFrame(cf):CFrame)->rbx_types::CFrame{
		rbx_types::CFrame::new(
			vec3_to_glam(cf.translation),
			rbx_types::Matrix3::new(
				vec3_to_glam(cf.matrix3.x_axis),
				vec3_to_glam(cf.matrix3.y_axis),
				vec3_to_glam(cf.matrix3.z_axis),
			)
		)
	}
}
impl From<rbx_types::CFrame> for CFrame{
	fn from(value:rbx_types::CFrame)->Self{
		CFrame(glam::Affine3A{
			matrix3:glam::mat3a(
				vec3_from_glam(value.orientation.x),
				vec3_from_glam(value.orientation.y),
				vec3_from_glam(value.orientation.z),
			),
			translation:vec3_from_glam(value.position)
		})
	}
}

pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
	let table=lua.create_table()?;

	//CFrame.new
	table.raw_set("new",
		lua.create_function(|lua,tuple:(
			mlua::Value,mlua::Value,Option<Number>,
			Option<Number>,Option<Number>,Option<Number>,
			Option<Number>,Option<Number>,Option<Number>,
			Option<Number>,Option<Number>,Option<Number>,
		)|match tuple{
			//CFrame.new()
			(
				mlua::Value::Nil,mlua::Value::Nil,None,
				None,None,None,
				None,None,None,
				None,None,None,
			)=>{
				Ok(CFrame(glam::Affine3A::IDENTITY))
			},
			//CFrame.new(pos)
			(
				mlua::Value::UserData(pos),mlua::Value::Nil,None,
				None,None,None,
				None,None,None,
				None,None,None,
			)=>{
				let Vector3(pos):&Vector3=&*pos.borrow()?;
				Ok(CFrame::point(pos.x,pos.y,pos.z))
			},
			//TODO: CFrame.new(pos,look)
			(
				mlua::Value::UserData(pos),mlua::Value::UserData(look),None,
				None,None,None,
				None,None,None,
				None,None,None,
			)=>{
				let _pos:&Vector3=&*pos.borrow()?;
				let _look:&Vector3=&*look.borrow()?;
				Err(mlua::Error::runtime("Not yet implemented"))
			},
			//CFrame.new(x,y,z)
			(
				x,y,Some(z),
				None,None,None,
				None,None,None,
				None,None,None,
			)=>Ok(CFrame::point(Number::from_lua(x,lua)?.into(),Number::from_lua(y,lua)?.into(),z.into())),
			//CFrame.new(x,y,z,xx,yx,zx,xy,yy,zy,xz,yz,zz)
			(
				x,y,Some(z),
				Some(xx),Some(yx),Some(zx),
				Some(xy),Some(yy),Some(zy),
				Some(xz),Some(yz),Some(zz),
			)=>Ok(CFrame::new(Number::from_lua(x,lua)?.into(),Number::from_lua(y,lua)?.into(),z.into(),
				xx.into(),yx.into(),zx.into(),
				xy.into(),yy.into(),zy.into(),
				xz.into(),yz.into(),zz.into(),
			)),
			_=>Err(mlua::Error::runtime("Invalid arguments"))
		})?
	)?;

	//CFrame.Angles
	let from_euler_angles=lua.create_function(|_,(x,y,z):(Number,Number,Number)|
		Ok(CFrame::angles(x.into(),y.into(),z.into()))
	)?;
	table.raw_set("Angles",from_euler_angles.clone())?;
	table.raw_set("fromEulerAnglesXYZ",from_euler_angles.clone())?;
	table.raw_set("FromEulerAnglesXYZ",from_euler_angles)?;

	globals.set("CFrame",table)?;

	Ok(())
}

impl mlua::UserData for CFrame{
	fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
		fields.add_field_method_get("p",|_,CFrame(this)|Ok(Vector3(this.translation)));
		fields.add_field_method_get("x",|_,CFrame(this)|Ok(this.translation.x));
		fields.add_field_method_get("X",|_,CFrame(this)|Ok(this.translation.x));
		fields.add_field_method_get("y",|_,CFrame(this)|Ok(this.translation.y));
		fields.add_field_method_get("Y",|_,CFrame(this)|Ok(this.translation.y));
		fields.add_field_method_get("z",|_,CFrame(this)|Ok(this.translation.z));
		fields.add_field_method_get("Z",|_,CFrame(this)|Ok(this.translation.z));
		fields.add_field_method_get("rightVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.x_axis)));
		fields.add_field_method_get("RightVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.x_axis)));
		fields.add_field_method_get("upVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.y_axis)));
		fields.add_field_method_get("UpVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.y_axis)));
		fields.add_field_method_get("lookVector",|_,CFrame(this)|Ok(Vector3(-this.matrix3.z_axis)));
		fields.add_field_method_get("LookVector",|_,CFrame(this)|Ok(Vector3(-this.matrix3.z_axis)));
		fields.add_field_method_get("XVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.row(0))));
		fields.add_field_method_get("YVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.row(1))));
		fields.add_field_method_get("ZVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.row(2))));
	}

	fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
		methods.add_method("components",|_,CFrame(this),()|Ok((
			this.translation.x,
			this.translation.y,
			this.translation.z,
			this.matrix3.x_axis.x,
			this.matrix3.y_axis.x,
			this.matrix3.z_axis.x,
			this.matrix3.x_axis.y,
			this.matrix3.y_axis.y,
			this.matrix3.z_axis.y,
			this.matrix3.x_axis.z,
			this.matrix3.y_axis.z,
			this.matrix3.z_axis.z,
		)));
		methods.add_method("VectorToWorldSpace",|_,CFrame(this),Vector3(v):Vector3|
			Ok(Vector3(this.transform_vector3a(v)))
		);

		methods.add_meta_function(mlua::MetaMethod::Mul,|_,(CFrame(this),CFrame(val)):(Self,Self)|Ok(Self(this*val)));
		methods.add_meta_function(mlua::MetaMethod::ToString,|_,CFrame(this):Self|
			Ok(format!("CFrame.new({},{},{},{},{},{},{},{},{},{},{},{})",
				this.translation.x,
				this.translation.y,
				this.translation.z,
				this.matrix3.x_axis.x,
				this.matrix3.y_axis.x,
				this.matrix3.z_axis.x,
				this.matrix3.x_axis.y,
				this.matrix3.y_axis.y,
				this.matrix3.z_axis.y,
				this.matrix3.x_axis.z,
				this.matrix3.y_axis.z,
				this.matrix3.z_axis.z,
			))
		);
	}
}

type_from_lua_userdata!(CFrame);
