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

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package gplx.xowa.xtns.scribunto; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*;
import gplx.core.envs.*;
import gplx.xowa.langs.*;
import gplx.xowa.xtns.scribunto.libs.*; import gplx.xowa.xtns.scribunto.engines.*;
import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*;
import gplx.xowa.xtns.scribunto.procs.*;
public class Scrib_core {
	private Hash_adp_bry mods = Hash_adp_bry.cs();
	private int expensive_function_count;
	public Scrib_core(Xoae_app app, Xop_ctx ctx) {// NOTE: ctx needed for language reg
		this.app = app; this.ctx = ctx;
		this.wiki = ctx.Wiki(); this.page = ctx.Page();	// NOTE: wiki / page needed for title reg; DATE:2014-02-05
		this.lang = wiki.Lang();
		this.Engine_(Scrib_engine_type.Type_lua, false);	// TEST: default to lua
		fsys_mgr.Root_dir_(app.Fsys_mgr().Bin_xtns_dir().GenSubDir_nest("Scribunto"));
		lib_mw = new Scrib_lib_mw(this);
		lib_uri = new Scrib_lib_uri(this); 
		lib_ustring = new Scrib_lib_ustring(this);
		lib_language = new Scrib_lib_language(this);
 		lib_site = new Scrib_lib_site(this);
 		lib_title = new Scrib_lib_title(this);
 		lib_message = new Scrib_lib_message(this);
 		lib_text = new Scrib_lib_text(this);
		lib_html = new Scrib_lib_html(this);
		lib_wikibase = new Scrib_lib_wikibase(this);
		lib_wikibase_entity = new Scrib_lib_wikibase_entity(this);
	}
	public Xoae_app App() {return app;} private Xoae_app app;
	public Xowe_wiki Wiki() {return wiki;} private Xowe_wiki wiki;
	public Xol_lang_itm Lang() {return lang;} private Xol_lang_itm lang;
	@gplx.Internal protected void Wiki_(Xowe_wiki v) {this.wiki = v;} // TEST:
	public Xoae_page Page() {return page;} private Xoae_page page;
	public boolean Enabled() {return enabled;} private boolean enabled = true;
	public void Engine_(Scrib_engine v) {this.engine = v;}
	private void Engine_(byte type, boolean luaj_debug_enabled) {
		if		(type == Scrib_engine_type.Type_lua)
			engine = new gplx.xowa.xtns.scribunto.engines.process.Process_engine(app, wiki, this);
		else if (type == Scrib_engine_type.Type_luaj)
			engine = new gplx.xowa.xtns.scribunto.engines.luaj.Luaj_engine(app, this, luaj_debug_enabled);
	}
	public Scrib_fsys_mgr Fsys_mgr() {return fsys_mgr;} private Scrib_fsys_mgr fsys_mgr = new Scrib_fsys_mgr();
	public Scrib_proc_mgr Proc_mgr() {return proc_mgr;} private Scrib_proc_mgr proc_mgr = new Scrib_proc_mgr();
	public Scrib_engine Interpreter() {return engine;} private Scrib_engine engine;
	public Scrib_lib_mw Lib_mw() {return lib_mw;} private Scrib_lib_mw lib_mw;
	public Scrib_lib_uri Lib_uri() {return lib_uri;} private Scrib_lib_uri lib_uri;
	public Scrib_lib_ustring Lib_ustring() {return lib_ustring;} private Scrib_lib_ustring lib_ustring;
	public Scrib_lib_language Lib_language() {return lib_language;} private Scrib_lib_language lib_language;
	public Scrib_lib_site Lib_site() {return lib_site;} private Scrib_lib_site lib_site;
	public Scrib_lib_title Lib_title() {return lib_title;} private Scrib_lib_title lib_title;
	public Scrib_lib_message Lib_message() {return lib_message;} private Scrib_lib_message lib_message;
	public Scrib_lib_text Lib_text() {return lib_text;} private Scrib_lib_text lib_text;
	public Scrib_lib_html Lib_html() {return lib_html;} private Scrib_lib_html lib_html;
	public Scrib_lib_wikibase Lib_wikibase() {return lib_wikibase;} private Scrib_lib_wikibase lib_wikibase;
	public Scrib_lib_wikibase_entity Lib_wikibase_entity() {return lib_wikibase_entity;} private Scrib_lib_wikibase_entity lib_wikibase_entity;
	public Scrib_core Init() {	// REF:LuaCommon.php!Load
		Scrib_xtn_mgr xtn_mgr = (Scrib_xtn_mgr)wiki.Xtn_mgr().Get_or_fail(Scrib_xtn_mgr.XTN_KEY);
		Engine_(xtn_mgr.Engine_type(), xtn_mgr.Luaj_debug_enabled());
		engine.Server().Server_timeout_(xtn_mgr.Lua_timeout()).Server_timeout_polling_(xtn_mgr.Lua_timeout_polling()).Server_timeout_busy_wait_(xtn_mgr.Lua_timeout_busy_wait());
		enabled = xtn_mgr.Enabled();
		Io_url root_dir = fsys_mgr.Root_dir(), script_dir = fsys_mgr.Script_dir();
		engine.Server().Init
		(	app.Prog_mgr().App_lua().Exe_url().Raw()
		,	root_dir.GenSubFil_nest("engines", "LuaStandalone", "mw_main.lua").Raw()
		,	root_dir.Raw()
		);
		Init_register(script_dir, lib_mw, lib_uri, lib_ustring, lib_language, lib_site, lib_title, lib_text, lib_html, lib_message, lib_wikibase, lib_wikibase_entity);
		xtn_mgr.Lib_mgr().Init_for_core(this, script_dir);
		return this;
	}
	private void Init_register(Io_url script_dir, Scrib_lib... ary) {
		int len = ary.length;
		for (int i = 0; i < len; i++)
			ary[i].Register(this, script_dir);
	}
	public void Term() {engine.Server().Term(); mods.Clear();}
	public void When_page_changed(Xoae_page page) {
		mods.Clear();	// clear any loaded modules
		Xow_wiki wiki = page.Wiki();
		this.page = page;
		byte[] new_wiki = wiki.Domain_bry();
		if (!Bry_.Eq(cur_wiki, new_wiki)) {
			cur_wiki = new_wiki;
			lib_site.Notify_wiki_changed();
			lib_text.Notify_wiki_changed();
		}
		byte[] new_lang = wiki.Lang().Key_bry();
		if (!Bry_.Eq(cur_lang, new_lang)) {
			cur_lang = new_lang;
			lib_message.Notify_lang_changed();
			lib_language.Notify_lang_changed();
		}
		lib_uri.Notify_page_changed();
		lib_title.Notify_page_changed();
		lib_wikibase.Notify_page_changed();
		expensive_function_count = 0;
	}	
	public byte[] Cur_wiki() {return cur_wiki;} private byte[] cur_wiki = Bry_.Empty; 
	public byte[] Cur_lang() {return cur_lang;} private byte[] cur_lang = Bry_.Empty;
	public void Increment_expensive_function_count() {
		++expensive_function_count;
		if (expensive_function_count > 255) {}
	}
	public Scrib_lua_mod RegisterInterface(Scrib_lib lib, Io_url url, Keyval... args) {
		this.RegisterLibrary(lib.Procs());
		Scrib_lua_mod rv = this.LoadLibraryFromFile(url.NameAndExt(), Io_mgr.Instance.LoadFilStr(url));
		Scrib_lua_proc setupInterface_func = rv.Fncs_get_by_key("setupInterface");
		if (setupInterface_func != null)
			engine.CallFunction(setupInterface_func.Id(), Scrib_kv_utl_.base1_obj_(args));
		return rv;
	}
	public void RegisterLibrary(Scrib_proc_mgr lib_proc_mgr) {
		int len = lib_proc_mgr.Len();
		Keyval[] functions_ary = new Keyval[len];
		for (int i = 0; i < len; i++) {
			Scrib_proc lib_proc = lib_proc_mgr.Get_at(i);
			String lib_proc_key = lib_proc.Proc_key();
			this.proc_mgr.Set(lib_proc_key, lib_proc);
			functions_ary[i] = Keyval_.new_(lib_proc.Proc_name(), lib_proc_key);
		}
		engine.RegisterLibrary(functions_ary);
	}
	@gplx.Internal protected Scrib_lua_mod LoadLibraryFromFile(String name, String text) {
		int lib_id = engine.LoadString("@" + name, text).Id();	// NOTE: 'Prepending an "@" to the chunk name makes Lua think it is a filename'
		Keyval[] values = engine.CallFunction(lib_id, Keyval_.Ary_empty);
		Scrib_lua_mod rv = new Scrib_lua_mod(this, name);
		if (values.length > 0) {	// NOTE: values.length == 0 for "package.lua" (no fnc_ids returned);
			Keyval[] fncs = Scrib_kv_utl_.Val_to_KeyVal_ary(values, 0);
			int len = fncs.length;
			for (int i = 0; i < len; i++) {
				Keyval itm = fncs[i];
				Scrib_lua_proc fnc = Scrib_lua_proc.cast_or_null_(itm.Val());
				if (fnc != null) rv.Fncs_add(fnc);	// NOTE: some lua funcs will return INF; EX: stringLengthLimit
			}
		}
		return rv;
	}
	public Ordered_hash Frame_created_list() {return frame_created_list;} private Ordered_hash frame_created_list = Ordered_hash_.New();	// created by NewChildFrame
	public Xot_invk Frame_current() {return frame_current;} private Xot_invk frame_current;
	public Xot_invk Frame_parent() {return frame_parent;} private Xot_invk frame_parent;
	@gplx.Internal protected void Frame_current_(Xot_invk v) {frame_current = v;} // TEST:
	@gplx.Internal protected void Frame_parent_(Xot_invk v) {frame_parent = v;}	// TEST:
	public Xop_ctx Ctx() {return ctx;} private Xop_ctx ctx;
	public byte[] Cur_src() {return cur_src;} private byte[] cur_src; // only used for error reporting
	public void Invoke_init(Xowe_wiki wiki, Xop_ctx ctx, byte[] src, Xot_invk parent_frame, Xot_invk current_frame) {	// TEST
		this.wiki = wiki; this.ctx = ctx; this.cur_src = src;
		lib_mw.Invoke_bgn(wiki, ctx, src);
		this.frame_parent = parent_frame; this.frame_current = current_frame;
	}
	public void Invoke(Xowe_wiki wiki, Xop_ctx ctx, byte[] src, Xot_invk parent_frame, Xot_invk current_frame, Bry_bfr bfr, byte[] mod_name, byte[] mod_text, byte[] fnc_name) {
		// save current values for restoring later
		Xot_invk old_frame_parent = this.frame_parent; Xot_invk old_frame_current = this.frame_current;
		byte[] old_src = cur_src;

		// init
		this.wiki = wiki; this.ctx = ctx; this.cur_src = src;
		lib_mw.Invoke_bgn(wiki, ctx, src);
		this.frame_parent = parent_frame; this.frame_current = current_frame;
		parent_frame.Frame_tid_(Scrib_frame_.Tid_parent); current_frame.Frame_tid_(Scrib_frame_.Tid_current);

		try {
			Scrib_lua_mod mod = Mods_get_or_new(mod_name, mod_text);
			Keyval[] func_args = Scrib_kv_utl_.base1_many_(mod.Init_chunk_func(), String_.new_u8(fnc_name));
			Keyval[] func_rslt = engine.CallFunction(lib_mw.Mod().Fncs_get_id("executeModule"), func_args);			// call init_chunk to get proc dynamically; DATE:2014-07-12
			if (func_rslt == null || func_rslt.length < 2) throw Err_.new_wo_type("lua.error:function did not return a value", "fnc_name", String_.new_u8(fnc_name)); // must return at least 2 items for func_rslt[1] below; DATE:2014-09-22
			Scrib_lua_proc proc = (Scrib_lua_proc)func_rslt[1].Val();												// note that init_chunk should have: [0]:true/false result; [1]:proc
			func_args = Scrib_kv_utl_.base1_many_(proc);
			func_rslt = engine.CallFunction(lib_mw.Mod().Fncs_get_id("executeFunction"), func_args);				// call function now
			String rslt = Scrib_kv_utl_.Val_to_str(func_rslt, 0);													// rslt expects an array with 1 scalar value
			bfr.Add_str_u8(rslt);
			//byte[] rslt_bry = Bry_.new_u8(rslt);	// CHART
			//gplx.xowa.parsers.xndes.Xop_xnde_tkn.Hack_ctx = ctx;
			//bfr.Add(rslt_bry);
			if (!Env_.Mode_testing())
				engine.CleanupChunks(Keyval_.Ary(Keyval_.int_(proc.Id(), "")));										// cleanup chunk immediately; needed for heavy pages like en.d:water; DATE:2014-08-07
		}
		finally {
			lib_mw.Invoke_end();
			parent_frame.Frame_tid_(Scrib_frame_.Tid_null); current_frame.Frame_tid_(Scrib_frame_.Tid_null);
			this.frame_parent = old_frame_parent; this.frame_current = old_frame_current;	// NOTE: reset template frame; PAGE:en.w:Constantine_the_Great {{Christianity}}; DATE:2014-06-26
			this.cur_src = old_src;
			frame_created_list.Clear();
		}
	}
	public Scrib_lua_mod Mods_get(byte[] mod_name) {return (Scrib_lua_mod)mods.Get_by(mod_name);}
	private Scrib_lua_mod Mods_get_or_new(byte[] mod_name, byte[] mod_text) {
		Scrib_lua_mod rv = (Scrib_lua_mod)mods.Get_by(mod_name);
		if (rv == null) {
			rv = new Scrib_lua_mod(this, "Module:" + String_.new_u8(mod_name));
			rv.LoadString(String_.new_u8(mod_text));
			mods.Add(mod_name, rv);
		}
		return rv;
	}
	public void Handle_error(String err) {
		String excerpt = "";
		try {
			Xot_invk src_frame = frame_current;
			if (src_frame != null)
				excerpt = String_.new_u8(cur_src, src_frame.Src_bgn(), src_frame.Src_end());
		} catch (Exception e) {Err_.Noop(e);}
		throw Err_.new_wo_type(err, "ttl", page.Ttl().Page_db_as_str(), "excerpt", excerpt);
	}
	public static final String Frame_key_module = "current", Frame_key_template = "parent";
	public static final int Base_1 = 1;
	public static final String Key_mw_interface = "mw_interface";
}
