/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 gnosygnu@gmail.com

XOWA is licensed under the terms of the General Public License (GPL) Version 3,
or alternatively under the terms of the Apache License Version 2.0.

You may use XOWA according to either of these licenses as is most appropriate
for your project on a case-by-case basis.

The terms of each license can be found in the source code repository:

GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
*/
package gplx.xowa.xtns.scribunto.engines.luaj; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.scribunto.*; import gplx.xowa.xtns.scribunto.engines.*;
import java.util.*;
import org.luaj.vm2.*;
class Luaj_value_ {
	public static String Get_val_as_str(LuaTable owner, String key) {
		return ((LuaString)owner.get(key)).tojstring();		
	}
	public static LuaTable Get_val_as_lua_table(LuaTable owner, String key) {
		return (LuaTable)owner.get(key);
	}
	public static Keyval[] Get_val_as_kv_ary(Luaj_server server, LuaTable owner, String key) {
		LuaTable table = (LuaTable)owner.get(key);
		return Luaj_value_.Lua_tbl_to_kv_ary(server, table);
	}
	public static Keyval[] Lua_tbl_to_kv_ary(Luaj_server server, LuaTable tbl) {
		// init
		int tbl_len = tbl.length();	// gets length of tbl.array; note no way of getting length of tbl.hash; note that tbl.length is same as len; getn; rawlen; maxn;
		Keyval[] rv_ary = tbl_len == 0 ? Keyval_.Ary_empty : new Keyval[tbl_len];	// guess rv_ary dimensions 
		List_adp rv_list = null;
		int rv_idx = 0;
		LuaValue cur = LuaValue.NIL;	// needed for luaj iterator; tbl.next(cur);
		
		// loop over pairs in tbl; no direct way to get kvs
		while (true) {
			// get next itm
			Varargs itm = tbl.next(cur);
			if (itm == LuaValue.NIL) break;	// no more pairs; stop
			
			// extract luaj key / val from itm; note itm.arg(1) is key and itm.arg(2) is val
			LuaValue itm_key = itm.arg(1);
			LuaValue itm_val = itm.arg(2);
			Object itm_val_obj = Lua_val_to_obj(server, itm_val);

			// transform to xowa kv
			Keyval kv = null;
			// val is function
			if (itm_val.type() == LuaValue.TFUNCTION) {
				String func_key = itm_key.tojstring();
				int func_id = Int_.cast(itm_val_obj);
				Scrib_lua_proc lua_func = new Scrib_lua_proc(func_key, func_id);
				if (itm_key.type() == LuaValue.TSTRING)			// most functions are named
					kv = Keyval_.new_(func_key, lua_func);
				else											// some are not; particularly "anonymous" functions in Module for gsub_function; these will have a kv of int,int; note that key must be int; if string, lua will not be able to match it back to int later
					kv = Keyval_.int_(((LuaInteger)itm_key).v, lua_func);
			}
			// val is number or string
			else {
				switch (itm_key.type()) {
					case LuaValue.TNUMBER:
						int key_int = ((LuaNumber)itm_key).toint();
						kv = Keyval_.int_(key_int, itm_val_obj);
						break;
					case LuaValue.TSTRING:
						kv = Keyval_.new_(((LuaString)itm_key).tojstring(), itm_val_obj);
						break;
					default:
						throw Err_.new_unhandled(itm_key.type());
				}
			}

			// store kv in rv
			// still enough space in array
			if (rv_idx < tbl_len) {
				rv_ary[rv_idx] = kv;
			}
			// exceeded rv_ary; store in list
			else {
				if (rv_idx == tbl_len) {
					rv_list = List_adp_.New();
					if (tbl_len > 0) {	// don't bother entering for loop if rv_ary was 0
						for (int i = 0; i < tbl_len; i++) {
							rv_list.Add(rv_ary[i]);
						}
					}
				}
				rv_list.Add(kv);
			}
			
			// increment list and rv_idx
			cur = itm_key;
			++rv_idx;
		}
		return rv_list == null ? rv_ary : (Keyval[])rv_list.To_ary_and_clear(Keyval.class);
	}
	private static Object Lua_val_to_obj(Luaj_server server, LuaValue v) {
		switch (v.type()) {
			case LuaValue.TNIL: 				return null;
			case LuaValue.TBOOLEAN:				return ((LuaBoolean)v).toboolean();
			case LuaValue.TSTRING:				return ((LuaString)v).tojstring();
			case LuaValue.TNUMBER:
				LuaNumber v_num = (LuaNumber)v;
				if (v_num.isint())
					return v_num.toint();
				else
					return v_num.todouble();
			case LuaValue.TTABLE:				return Lua_tbl_to_kv_ary(server, (LuaTable)v);
			case LuaValue.TFUNCTION:			return server.Get_id_by_closure(v);
			default:							throw Err_.new_unhandled(v.type());
		}		
	}
	public static LuaValue Obj_to_lua_val(Luaj_server server, Object o) {
		if (o == null) return LuaValue.NIL;
		Class<?> c = Type_adp_.ClassOf_obj(o);
		if		(Object_.Eq(c, Bool_.Cls_ref_type))			return LuaValue.valueOf((Boolean)o);
		else if	(Object_.Eq(c, Byte_.Cls_ref_type))			return LuaValue.valueOf((Byte)o);
		else if	(Object_.Eq(c, Int_.Cls_ref_type))			return LuaValue.valueOf((Integer)o);
		else if	(Object_.Eq(c, String_.Cls_ref_type))		return LuaValue.valueOf((String)o);
		else if	(Object_.Eq(c, Double_.Cls_ref_type))		return LuaValue.valueOf((Double)o);
		else if	(Object_.Eq(c, byte[].class))				return LuaValue.valueOf(String_.new_u8((byte[])o));
		else if	(Object_.Eq(c, Keyval.class))				return Kv_ary_to_lua_tbl(server, (Keyval)o);
		else if	(Object_.Eq(c, Keyval[].class))				return Kv_ary_to_lua_tbl(server, (Keyval[])o);
		else if	(Object_.Eq(c, Long_.Cls_ref_type))			return LuaValue.valueOf((Long)o);
		else if	(Object_.Eq(c, Scrib_lua_proc.class))		return server.Get_closure_by_id(((Scrib_lua_proc)o).Id());
		else if	(Object_.Eq(c, Float_.Cls_ref_type))		return LuaValue.valueOf((Float)o);
		else if	(Object_.Eq(c, Char_.Cls_ref_type))			return LuaValue.valueOf((Character)o);
		else if	(Object_.Eq(c, Short_.Cls_ref_type))		return LuaValue.valueOf((Short)o);
		else if	(Object_.Eq(c, Decimal_adp.class))			return LuaValue.valueOf(((Decimal_adp)o).To_double());	// DATE:2016-08-01
		else return LuaValue.NIL; 
	}
	private static LuaTable Kv_ary_to_lua_tbl(Luaj_server server, Keyval... ary) {
		LuaTable rv = LuaValue.tableOf();
		int len = ary.length;
		for (int i = 0; i < len; i++) {
			Keyval itm = ary[i];
			LuaValue itm_val = Obj_to_lua_val(server, itm.Val());
			switch (itm.Key_tid()) {
				case Type_adp_.Tid__int:
					rv.set(Int_.cast(itm.Key_as_obj()), itm_val);
					break;
				case Type_adp_.Tid__str:
				case Type_adp_.Tid__obj:
					rv.set(itm.Key(), itm_val);
					break;
			}				
		}
		return rv;
	}	
}
