/*
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.gfml; import gplx.*;
import gplx.core.texts.*; /*CharStream*/
public class GfmlTrie {
	public String[] Symbols() {
		String[] rv = new String[symbols.Count()];
		for (int i = 0; i < rv.length; i++)
			rv[i] = String_.cast(symbols.Get_at(i));
		return rv;
	}	Ordered_hash symbols = Ordered_hash_.New();
	public int LastMatchCount; // PERF: prop is faster than method
	public Object FindMatch(CharStream stream) {
		Object result = null; int moveCount = 0; LastMatchCount = 0;
		IntObjHash_base link = rootLink;
		while (stream.AtMid()) {
			Object found = link.Get_by(stream.Cur());
			if (found == null) break;										//	found is null; can happen for false matches; ex: <!-- reg, and <!-* is cur; goes to <!- before exit
			LastMatchCount++;
			IntObjHash_base foundAsLink = IntObjHash_base_.as_(found);
			if (foundAsLink != null) {										//	if (found is link) (ie: another link exists)
				result = foundAsLink.Bay();									//		set .Bay as possible result; needed for short-long pairs; ex: < and <!-- exist; < found, but need to check next symbol for !
				link = foundAsLink;											//		make foundAsLink the current one
				moveCount++;
				stream.MoveNext();
			}
			else {															//	not a link; must be last
				result = found;
				break;
			}
		}
		if (moveCount > 0) stream.MoveBackBy(moveCount);					//	restore stream to original position; imitate idempotency
		return result;
	}
	public void Add(String symbol, Object data) {
		if (data == null) throw Err_.new_wo_type("null objects cannot be registered", "symbol", symbol);

		char[] ary = String_.XtoCharAry(symbol); int lastIndex = ary.length - 1;
		IntObjHash_base curLink = rootLink;
		for (int i = 0; i < ary.length; i++) {
			char c = ary[i];
			Object found = curLink.Get_by(c);
			IntObjHash_base foundAsLink = IntObjHash_base_.as_(found);
			if (i == lastIndex) {											//	lastChar
				if (found != null) {										//		slot is occupied
					if (foundAsLink != null)								//			found is link; occurs when symbol is shorter than existing: ex: adding '<' when '<!--' exists)
						foundAsLink.Bay_set(data);							//				place found in link's bay
					else													//			found is makr; occurs when symbol is the same as existing; ex: adding '<!' when '<!' exists
						curLink.Set(c, data);								//				place found in slot (replaces slot)
				}
				else														//		slot is unoccupied
					curLink.Add(c, data);									//			place found in slot
			}
			else {															//	not lastChar
				if (foundAsLink == null) {									//		next link does not exist (NOTE: next link is necessary, since char is not lastChar)
					foundAsLink = IntObjHash_base_.new_();						//		create next link
					if (found != null) {									//		slot is occupied; occurs when symbol is longer than existing: ex: adding '<!--' when '<' exists
						foundAsLink.Bay_set(found);							//			transplant occupied found to link's Bay 
						curLink.Set(c, foundAsLink);						//			place new link in slot
					}
					else													//		slot is unoccupied
						curLink.Add(c, foundAsLink);						//			place found in slot
				}
				curLink = foundAsLink;
			}
		}
		symbols.Add_if_dupe_use_nth(symbol, symbol);
	}
	public void Del(String symbol) {
		char[] ary = String_.XtoCharAry(symbol); int lastIndex = ary.length - 1;
		IntObjHash_base[] linkAry = new IntObjHash_base[ary.length];		//	first, get linkAry -- one link for each symbol
		IntObjHash_base link = rootLink;
		for (int i = 0; i < ary.length; i++) {
			char c = ary[i];
			linkAry[i] = link;
			link = IntObjHash_base_.as_(link.Get_by(c));
			if (link == null) break;										//		c does not have nextHash; break
		}

		IntObjHash_base nextHash = null;
		for (int i = lastIndex; i >= 0; i--) {								//	remove each char from hashes; must move backwards
			char c = ary[i];
			IntObjHash_base curLink = linkAry[i];
			Object found = curLink.Get_by(c);
			IntObjHash_base foundAsLink = IntObjHash_base_.as_(found);
			if (nextHash != null && nextHash.Bay() != null)					//		occurs when long is dropped; ex: '<-' and '<'; '<-' dropped; <'s .Bay in '<-' chain must be transplanted to '<' .Bay
				curLink.Set(c, nextHash.Bay());
			else if (foundAsLink != null && foundAsLink.Bay() != null)		//		occurs when short is dropped; ex: '<-' and '<'; '<' dropped; <'s .Bay must be transplanted to '<' .Bay in '<-' chain 
				foundAsLink.Bay_set(found);
			else															//		no long/short overlap; simply remove
				curLink.Del(c);
			nextHash = curLink;
		}
		symbols.Del(symbol);
	}
	public void Clear() {rootLink.Clear();}
	IntObjHash_base rootLink = IntObjHash_base_.new_();
	public static GfmlTrie new_() {return new GfmlTrie();}
}
