/*
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.parsers.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*;
import gplx.core.primitives.*;
import gplx.xowa.parsers.amps.*; import gplx.xowa.parsers.xndes.*;
public class Mwh_doc_parser {
	private final    Mwh_doc_mgr dom_mgr = new Mwh_doc_mgr(16);
	private final    Mwh_atr_parser atr_parser = new Mwh_atr_parser();
	private final    List_adp nde_stack = List_adp_.New();
	private final    Xop_amp_mgr amp_mgr = Xop_amp_mgr.Instance; private final    Xop_tkn_mkr tkn_mkr = new Xop_tkn_mkr();
	private byte[] src; private int src_end;
	private Mwh_doc_wkr wkr;
	private Hash_adp_bry nde_regy;
	private int txt_bgn, nde_uid;
	private Xop_xnde_tag cur_nde; private int cur_nde_tid;
	public void Parse(Mwh_doc_wkr wkr, byte[] src, int src_bgn, int src_end) {
		this.wkr = wkr; this.src = src; this.src_end = src_end;
		this.nde_regy = wkr.Nde_regy();
		nde_stack.Clear();
		int pos = txt_bgn = src_bgn;
		nde_uid = cur_nde_tid = -1;
		cur_nde = null;

		while (pos < src_end) {
			byte b = src[pos];
			switch (b) {
				case Byte_ascii.Angle_bgn:	// "<": possible nde start
					pos = Parse_nde(pos);
					break;
				case Byte_ascii.Amp:		// "&": check for entity; EX: &nbsp; in sr-ec -> sr-el
					Xop_amp_mgr_rslt rv = amp_mgr.Parse_tkn(tkn_mkr, src, src_end, pos, pos + 1);
					Xop_tkn_itm rv_tkn = rv.Tkn();
					if (rv_tkn == null)
						++pos;
					else {
						wkr.On_txt_end(this, src, cur_nde_tid, txt_bgn, pos);
						wkr.On_entity_end(this, src, cur_nde_tid, rv_tkn.Src_bgn(), rv_tkn.Src_end());
						pos = rv_tkn.Src_end();
						txt_bgn = pos;
					}
					break;
				default:					// else, just increment
					++pos;
					break;
			}
		}
		if (src_end != txt_bgn) wkr.On_txt_end(this, src, cur_nde_tid, txt_bgn, pos);
	}
	private int Parse_nde(int pos) {
		int nde_end_tid = Nde_end_tid__invalid;
		boolean nde_is_head = true;
		int nde_bgn = pos;
		++pos;
		int name_bgn = pos;
		int name_end = pos;
		while (pos < src_end) {
			byte b = src[pos];
			switch (b) {
				// valid chars for name
				case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E:
				case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J:
				case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O:
				case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T:
				case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z:
				case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e:
				case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j:
				case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o:
				case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t:
				case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z:
				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: case Byte_ascii.Dash: case Byte_ascii.Underline: case Byte_ascii.Colon:	// XML allowed punctuation
				case Byte_ascii.Dollar:// MW: handles <br$2>;
					++pos;
					break;
				// comment check
				case Byte_ascii.Bang:
					boolean comment_found = false;
					if (name_bgn == pos && Bry_.Eq(src, pos + 1, pos + 3, Comment_bgn)) {
						int comment_end_pos = Bry_find_.Find_fwd(src, Comment_end, pos + 3);
						if (comment_end_pos != Bry_find_.Not_found) {
							nde_end_tid = Nde_end_tid__comment;
							pos = comment_end_pos + 3;
							comment_found = true;
						}
					}
					if (!comment_found)
						return pos;
					else
						break;
				// invalid char; not a node; treat as text; EX: "<!@#", "< /b>"
				default:
					return pos;
				// slash -> either "</b>"  or "<b/>"
				case Byte_ascii.Slash:
					if (name_bgn == pos) {	// "</"; EX: "</b>"
						nde_is_head = false;
						++name_bgn;
						++pos;
						continue;
					}
					else {					// check for "/>"; NOTE: <pre/a>, <pre//> are allowed
						name_end = pos;
						++pos;
						if (pos == src_end) return pos;		// end of doc; treat as text; EX: "<b/EOS"
						if (src[pos] == Byte_ascii.Gt) {
							nde_end_tid = Nde_end_tid__inline;
							++pos;
						}
						else
							nde_end_tid = Nde_end_tid__slash;
					}
					break;
				// stops "name"
				case Byte_ascii.Gt:
					nde_end_tid = Nde_end_tid__gt;
					name_end = pos;
					++pos;
					break;
				case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space:
					nde_end_tid = Nde_end_tid__ws;
					name_end = pos;
					break;
				case Byte_ascii.Backslash:	 // MW: allows "<br\>" -> "<br/>"
					nde_end_tid = Nde_end_tid__backslash;
					name_end = pos;
					break;
			}
			if (nde_end_tid != Nde_end_tid__invalid) break;
		}
		// get name
		Xop_xnde_tag nde_itm = null;
		if (nde_end_tid != Nde_end_tid__comment) {
			nde_itm = (Xop_xnde_tag)nde_regy.Get_by_mid(src, name_bgn, name_end);
			if (nde_itm == null) return pos;	// not a known nde; exit
		}
		if (txt_bgn != nde_bgn) {	// notify txt
			wkr.On_txt_end(this, src, cur_nde_tid, txt_bgn, nde_bgn);
			txt_bgn = pos;
		}
		if (nde_is_head) {
			wkr.On_nde_head_bgn(this, src, cur_nde_tid, name_bgn, name_end);
			switch (nde_end_tid) {
				case Nde_end_tid__comment:
					wkr.On_comment_end(this, src, cur_nde_tid, nde_bgn, pos);
					break;
				case Nde_end_tid__ws:
				case Nde_end_tid__slash:
				case Nde_end_tid__backslash:
					// look for ">" or "/>"
					int tmp_pos = pos, atrs_end = src_end, head_end = src_end;
					boolean loop = true;
					while (loop) {
						byte b = src[tmp_pos];
						switch (b) {
							// angle_end -> stop iterating
							case Byte_ascii.Angle_end:
								atrs_end = tmp_pos;
								head_end = tmp_pos + 1;
								nde_end_tid = Mwh_doc_parser.Nde_end_tid__gt;
								loop = false;
								break;
							// slash -> check for "/>" or " / "
							case Byte_ascii.Slash:
								int nxt_pos = tmp_pos + 1;
								if		(nxt_pos == src_end) {
									nde_end_tid = Mwh_doc_parser.Nde_end_tid__invalid;
									loop = false;
								}
								else if (src[nxt_pos] == Byte_ascii.Angle_end) {
									atrs_end = tmp_pos;
									head_end = tmp_pos + 2;
									nde_end_tid = Mwh_doc_parser.Nde_end_tid__inline;
									loop = false;
								}				
								break;
						}
						if (loop) {
							++tmp_pos;
							if (tmp_pos == src_end) break;
						}
						else
							break;
					}
					atr_parser.Parse(wkr, nde_uid, cur_nde_tid, src, pos, atrs_end);
					pos = head_end;
					txt_bgn = head_end;
					break;
			}
			switch (nde_end_tid) {
				case Nde_end_tid__inline:
					wkr.On_nde_head_end(this, src, cur_nde_tid, nde_bgn, pos, Bool_.Y);
					txt_bgn = pos;
					break;
				case Nde_end_tid__gt:
					wkr.On_nde_head_end(this, src, cur_nde_tid, nde_bgn, pos, Bool_.N);
					txt_bgn = pos;
					if (	nde_itm != null
						&&	!nde_itm.Single_only_html()				// ignore <b>
						&&	(cur_nde == null || !cur_nde.Xtn())		// <pre> ignores inner
						) {
						if (cur_nde != null)
							nde_stack.Add(cur_nde);
						this.cur_nde = nde_itm;
						this.cur_nde_tid = nde_itm.Id();
					}
					break;
				case Nde_end_tid__ws:
				case Nde_end_tid__slash:
				case Nde_end_tid__backslash: break; // handled above
			}
			nde_uid = dom_mgr.Add(Mwh_doc_itm.Itm_tid__nde_head, nde_bgn, pos);
		}
		else {
			switch (nde_end_tid) {
				case Nde_end_tid__gt:
					wkr.On_nde_tail_end(this, src, cur_nde_tid, nde_bgn, pos);
					txt_bgn = pos;
					if (nde_itm.Id() == cur_nde_tid) {
						cur_nde = (Xop_xnde_tag)List_adp_.Pop_or(nde_stack, null);
						cur_nde_tid = cur_nde == null ? -1 : cur_nde.Id();
					}
					break;
			}
		}
		return pos;
	}
	public static final int Nde_end_tid__invalid = 0, Nde_end_tid__gt = 1, Nde_end_tid__ws = 2, Nde_end_tid__inline = 3, Nde_end_tid__slash = 4, Nde_end_tid__backslash = 5, Nde_end_tid__comment = 6;
	private static final    byte[] Comment_bgn = Bry_.new_a7("--"), Comment_end = Bry_.new_a7("-->");
}
