/*
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.pfuncs.exprs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.pfuncs.*;
import gplx.core.btries.*;
public class Pfunc_expr_shunter {
	Btrie_fast_mgr trie = expression_();
	Val_stack val_stack = new Val_stack();
	Func_tkn_stack prc_stack = new Func_tkn_stack();
	public static final DecimalAdp Null_rslt = null;
	public Bry_bfr Err() {return err_bfr;} Bry_bfr err_bfr = Bry_bfr.new_();
	public DecimalAdp Err_set(Xop_ctx ctx, int msgId) {return Err_set(ctx, msgId, Bry_.Empty);}
	public DecimalAdp Err_set(Xop_ctx ctx, int msg_id, byte[] arg) {
		byte[] msg_val = ctx.Wiki().Msg_mgr().Val_by_id(msg_id);
		err_bfr.Clear().Add(Err_bgn_ary);
		tmp_fmtr.Fmt_(msg_val).Bld_bfr_one(err_bfr, arg);
		err_bfr.Add(Err_end_ary);
		return Null_rslt;
	}	static final byte[] Err_bgn_ary = Bry_.new_ascii_("<strong class=\"error\">"), Err_end_ary = Bry_.new_ascii_("</strong>"); Bry_fmtr tmp_fmtr = Bry_fmtr.tmp_();
	public void Rslt_set(byte[] bry) {
		err_bfr.Add(bry);
	}
	public DecimalAdp Evaluate(Xop_ctx ctx, byte[] src) {	// REF.MW: Expr.php
		int src_len = src.length; if (src_len == 0) return Null_rslt;
		int cur_pos = 0; byte cur_byt = src[0];
		boolean mode_expr = true; Func_tkn prv_prc = null;
		val_stack.Clear(); prc_stack.Clear();
		while (true) {
			// can't think of a way for this to happen; note that operators will automatically push values/operators off stack that are lower; can't get up to 100 
			// if (val_stack.Len() > 100 || prc_stack.Len() > 100) return Err_set(ctx, Xol_msg_itm_.Id_pfunc_expr_err__stack_exhausted);
			Object o = trie.Match_bgn_w_byte(cur_byt, src, cur_pos, src_len);
			int bgn_pos = cur_pos;
			if (o == null) {	// letter or unknown symbol
				while (cur_pos < src_len) {
					byte b = src[cur_pos++];
					if (Byte_ascii.Is_ltr(b))
						continue;
					else
						break;
				}
				return Err_set(ctx, Xol_msg_itm_.Id_pfunc_expr_unrecognised_word, Bry_.Mid(src, bgn_pos, cur_pos));
			}
			else {
				Expr_tkn t = (Expr_tkn)o;
				cur_pos = trie.Match_pos();
				switch (t.Tid()) {
					case Expr_tkn_.Tid_space: break;
					case Expr_tkn_.Tid_number:
						if (!mode_expr) return Err_set(ctx, Xol_msg_itm_.Id_pfunc_expr_unexpected_number);
						int numBgn = cur_pos - 1;
						boolean loop = true;
						while (loop) {
							if (cur_pos == src_len) break;
							switch (src[cur_pos]) {
								case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4:
								case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9:
								case Byte_ascii.Dot:
									++cur_pos;
									break;
								default: loop = false; break;
							}
						}
						DecimalAdp num = Null_rslt;
						try {num = Bry_.XtoDecimalByPos(src, numBgn, cur_pos);}
						catch (Exception exc) {
							// NOTE: PATCH.PHP: 65.5.5 can evaluate to 65.5; EX "{{Geological eras|-600|height=2|border=none}}" eventually does "|10-to={{#ifexpr:{{{1|-4567}}}<-65.5|-65.5|{{{1}}}}}.5" which is 65.5.5
							Err_.Noop(exc); 
							int dot_count = 0;
							for (int i = numBgn; i < cur_pos; i++) {
								if (src[i] == Byte_ascii.Dot) {
									switch (dot_count) {
										case 0: dot_count = 1; break;
										case 1: 
											try {
												num = Bry_.XtoDecimalByPos(src, numBgn, i);
											}
											catch (Exception exc_inner) {Err_.Noop(exc_inner);}
											break;
									}
								}
							}
							if (num == null) return Null_rslt;
						}
						val_stack.Push(num);
						mode_expr = false;
						break;
					case Expr_tkn_.Tid_paren_lhs:
						if (!mode_expr) return Err_set(ctx, Xol_msg_itm_.Id_pfunc_expr_unexpected_operator, Bry_.new_ascii_("("));
						prc_stack.Push((Func_tkn)t);
						break;
					case Expr_tkn_.Tid_operator:
						Func_tkn cur_prc = (Func_tkn)t;
						if (Byte_ascii.Is_ltr(cur_byt)) {
							int nxt_pos = Bry_finder.Find_fwd_while_letter(src, cur_pos, src_len);
							if (nxt_pos > cur_pos)
								return Err_set(ctx, Xol_msg_itm_.Id_pfunc_expr_unrecognised_word, Bry_.Mid(src, bgn_pos, nxt_pos));
						}
						if (cur_prc.Func_is_const()) {		// func is "pi" or "e"; DATE:2014-03-01
							if (mode_expr) {				// number expected; just call Calc (which will place value on stack)
								cur_prc.Calc(ctx, this, val_stack);
								break;
							}
							else							// operator expected; fail b/c pi / e is not an operator;
								return Err_set(ctx, Xol_msg_itm_.Id_pfunc_expr_unexpected_number);
						}
						if (mode_expr) {	// NOTE: all the GetAlts have higher precedence; "break;" need to skip evaluation below else will fail for --1
							Func_tkn alt_prc = cur_prc.GetAlt();
							prc_stack.Push(alt_prc);
							break;
						}
						prv_prc = prc_stack.GetLast();
						while (prv_prc != null && (cur_prc.Precedence() <= prv_prc.Precedence())) {
							if (!prv_prc.Calc(ctx, this, val_stack)) return Null_rslt;
							prc_stack.Pop();
							prv_prc = prc_stack.GetLast();
						}
						prc_stack.Push(cur_prc);
						mode_expr = true;
						break;
					case Expr_tkn_.Tid_paren_rhs: {
						prv_prc = prc_stack.GetLast();
						while (prv_prc != null && prv_prc.Tid() != Expr_tkn_.Tid_paren_lhs) {
							if (!prv_prc.Calc(ctx, this, val_stack)) return Null_rslt;
							prc_stack.Pop();
							prv_prc = prc_stack.GetLast();
						}
						if (prv_prc == Paren_bgn_tkn._)
							prc_stack.Pop();
						else
							return Err_set(ctx, Xol_msg_itm_.Id_pfunc_expr_unexpected_closing_bracket);
						mode_expr = false;
						break;
					}
				}
			}
			if (cur_pos == src_len) break;
			cur_byt = src[cur_pos];
		}
		while (prc_stack.Len() > 0) {
			Func_tkn cur_prc = prc_stack.Pop();
			if (cur_prc.Tid() == Expr_tkn_.Tid_paren_lhs) return Err_set(ctx, Xol_msg_itm_.Id_pfunc_expr_unclosed_bracket);
			if (!cur_prc.Calc(ctx, this, val_stack)) return Null_rslt;
		}
		return val_stack.Len() == 0 ? Null_rslt : val_stack.Pop();	// HACK: for [[List of Premiers of South Australia by time in office]] and {{#expr:\n{{age in days
	}
	private static Btrie_fast_mgr expression_() {
		Btrie_fast_mgr rv = Btrie_fast_mgr.ci_ascii_();	// NOTE:ci.ascii:MW_const.en; math and expressions
		Trie_add(rv, new Ws_tkn(Byte_ascii.Space));
		Trie_add(rv, new Ws_tkn(Byte_ascii.Tab));
		Trie_add(rv, new Ws_tkn(Byte_ascii.NewLine));
		Trie_add(rv, Paren_bgn_tkn._);
		Trie_add(rv, Paren_end_tkn._);
		Trie_add(rv, new Func_tkn_plus("+"));
		Trie_add(rv, new Func_tkn_minus("-"));
		Trie_add(rv, new Func_tkn_minus(Char_.XtoStr((char)8722)));
		Trie_add(rv, new Func_tkn_times("*"));
		Trie_add(rv, new Func_tkn_divide("/"));
		Trie_add(rv, new Func_tkn_divide("div"));
		Trie_add(rv, new Func_tkn_pow("^"));
		Trie_add(rv, new Func_tkn_mod("mod"));
		Trie_add(rv, new Func_tkn_eq("="));
		Trie_add(rv, new Func_tkn_neq("<>"));
		Trie_add(rv, new Func_tkn_neq("!="));
		Trie_add(rv, new Func_tkn_gt(">"));
		Trie_add(rv, new Func_tkn_lt("<"));
		Trie_add(rv, new Func_tkn_gte(">="));
		Trie_add(rv, new Func_tkn_lte("<="));
		Trie_add(rv, new Func_tkn_and("and"));
		Trie_add(rv, new Func_tkn_or("or"));
		Trie_add(rv, new Func_tkn_not("not"));
		Trie_add(rv, new Func_tkn_e_op("e"));
		Trie_add(rv, new Func_tkn_pi("pi"));
		Trie_add(rv, new Func_tkn_ceil("ceil"));
		Trie_add(rv, new Func_tkn_trunc("trunc"));
		Trie_add(rv, new Func_tkn_floor("floor"));
		Trie_add(rv, new Func_tkn_abs("abs"));
		Trie_add(rv, new Func_tkn_exp("exp"));
		Trie_add(rv, new Func_tkn_ln("ln"));
		Trie_add(rv, new Func_tkn_sin("sin"));
		Trie_add(rv, new Func_tkn_cos("cos"));
		Trie_add(rv, new Func_tkn_tan("tan"));
		Trie_add(rv, new Func_tkn_asin("asin"));
		Trie_add(rv, new Func_tkn_acos("acos"));
		Trie_add(rv, new Func_tkn_atan("atan"));
		Trie_add(rv, new Func_tkn_round("round"));
		Trie_add(rv, new Func_tkn_fmod("fmod"));
		Trie_add(rv, new Func_tkn_sqrt("sqrt"));
		Trie_add(rv, new Num_tkn(0));
		Trie_add(rv, new Num_tkn(1));
		Trie_add(rv, new Num_tkn(2));
		Trie_add(rv, new Num_tkn(3));
		Trie_add(rv, new Num_tkn(4));
		Trie_add(rv, new Num_tkn(5));
		Trie_add(rv, new Num_tkn(6));
		Trie_add(rv, new Num_tkn(7));
		Trie_add(rv, new Num_tkn(8));
		Trie_add(rv, new Num_tkn(9));
		Trie_add(rv, new Dot_tkn());
		Trie_add(rv, new Func_tkn_gt("&gt;"));
		Trie_add(rv, new Func_tkn_lt("&lt;"));
		Trie_add(rv, new Func_tkn_minus("&minus;"));
		return rv;
	}
	private static void Trie_add(Btrie_fast_mgr trie, Expr_tkn tkn) {trie.Add(tkn.Val_ary(), tkn);}
	public static final Pfunc_expr_shunter _ = new Pfunc_expr_shunter(); Pfunc_expr_shunter() {}
}
