package speiger.src.coreengine.utils.io.dataTag.parsing;

import java.io.IOException;
import java.util.function.Consumer;
import java.util.regex.Pattern;

import speiger.src.collections.bytes.lists.ByteArrayList;
import speiger.src.collections.bytes.lists.ByteList;
import speiger.src.collections.doubles.lists.DoubleArrayList;
import speiger.src.collections.doubles.lists.DoubleList;
import speiger.src.collections.floats.lists.FloatArrayList;
import speiger.src.collections.floats.lists.FloatList;
import speiger.src.collections.ints.lists.IntArrayList;
import speiger.src.collections.ints.lists.IntList;
import speiger.src.collections.longs.lists.LongArrayList;
import speiger.src.collections.longs.lists.LongList;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.lists.ObjectList;
import speiger.src.collections.shorts.lists.ShortArrayList;
import speiger.src.collections.shorts.lists.ShortList;
import speiger.src.coreengine.utils.io.dataTag.DataTag;
import speiger.src.coreengine.utils.io.dataTag.array.ByteArrayTag;
import speiger.src.coreengine.utils.io.dataTag.array.DoubleArrayTag;
import speiger.src.coreengine.utils.io.dataTag.array.FloatArrayTag;
import speiger.src.coreengine.utils.io.dataTag.array.IntArrayTag;
import speiger.src.coreengine.utils.io.dataTag.array.LongArrayTag;
import speiger.src.coreengine.utils.io.dataTag.array.MediumArrayTag;
import speiger.src.coreengine.utils.io.dataTag.array.ShortArrayTag;
import speiger.src.coreengine.utils.io.dataTag.array.StringArrayTag;
import speiger.src.coreengine.utils.io.dataTag.simple.ByteTag;
import speiger.src.coreengine.utils.io.dataTag.simple.DoubleTag;
import speiger.src.coreengine.utils.io.dataTag.simple.FloatTag;
import speiger.src.coreengine.utils.io.dataTag.simple.IntTag;
import speiger.src.coreengine.utils.io.dataTag.simple.LongTag;
import speiger.src.coreengine.utils.io.dataTag.simple.MediumTag;
import speiger.src.coreengine.utils.io.dataTag.simple.PrimitiveTag;
import speiger.src.coreengine.utils.io.dataTag.simple.ShortTag;
import speiger.src.coreengine.utils.io.dataTag.simple.StringTag;
import speiger.src.coreengine.utils.io.dataTag.special.ListTag;
import speiger.src.coreengine.utils.io.dataTag.special.MapTag;
import speiger.src.coreengine.utils.io.streams.StringReader;

public class DataTagParser
{
	private static final Pattern BYTE_TYPE = Pattern.compile("[-+]?(?:[0-9]+)b", 2);
	private static final Pattern SHORT_TYPE = Pattern.compile("[-+]?(?:[0-9]+)s", 2);
	private static final Pattern MEDIUM_TYPE = Pattern.compile("[-+]?(?:[0-9]+)m", 2);
	private static final Pattern INT_TYPE = Pattern.compile("[-+]?(?:[0-9]+)i", 2);
	private static final Pattern INT_TYPE_DEFAULT = Pattern.compile("[-+]?(?:[0-9]+)");
	private static final Pattern LONG_TYPE = Pattern.compile("[-+]?(?:[0-9]+)l", 2);
	private static final Pattern FLOAT_TYPE = Pattern.compile("[-+]?(?:[0-9]+[.]?|[0-9]*[.][0-9]+)(?:e[-+]?[0-9]+)?f", 2);
	private static final Pattern DOUBLE_TYPE = Pattern.compile("[-+]?(?:[0-9]+[.]?|[0-9]*[.][0-9]+)(?:e[-+]?[0-9]+)?d", 2);
	private static final Pattern DOUBLE_TYPE_DEFAULT = Pattern.compile("[-+]?(?:[0-9]+[.]|[0-9]*[.][0-9]+)(?:e[-+]?[0-9]+)?", 2);
	StringReader reader;
	
	public DataTagParser(String s)
	{
		reader = new StringReader(s.replaceAll("\\§\\<(.*?)\\>", ""));
	}
	
	public DataTag readTagSave()
	{
		try
		{
			return readTag();
		}
		catch(Exception e)
		{
			return null;
		}
	}
	
	public DataTag readTag() throws IOException
	{
		reader.skipEmpty();
		if(!reader.canRead())
		{
			throw new IOException("Nothing to parse found");
		}
		else
		{
			char c0 = reader.peek();
			if(c0 == '{')
			{
				return readMap();
			}
			else
			{
				return c0 == '[' ? readList() : readTagType();
			}
		}
	}
	
	public MapTag readMap() throws IOException
	{
		requirePresent('{');
		reader.skipEmpty();
		MapTag map = new MapTag();
		while(reader.canRead() && reader.peek() != '}')
		{
			reader.skipEmpty();
			int last = reader.getIndex();
			String s = reader.readString();
			if(s.isEmpty())
			{
				throw new IOException("Expected Key at " + last);
			}
			requirePresent(':');
			map.putTag(s, readTag());
			reader.skipEmpty();
			if(!reader.isPresent(','))
			{
				break;
			}
			reader.skipEmpty();
		}
		requirePresent('}');
		return map;
	}
	
	protected DataTag readList() throws IOException
	{
		return reader.canRead(3) && !StringReader.isQuoteCharacter(reader.peek(1)) && reader.peek(2) == ';' ? readArrayTag() : readListTag();
	}
	
	protected DataTag readTagType() throws IOException
	{
		reader.skipEmpty();
		int last = reader.getIndex();
		if(StringReader.isQuoteCharacter(reader.peek()))
		{
			return new StringTag(reader.readQuotedText());
		}
		else
		{
			String s = reader.readString();
			if(s.isEmpty())
			{
				throw new IOException("Expected Value at " + last);
			}
			else
			{
				return tag(s);
			}
		}
	}
	
	public ListTag readListTag() throws IOException
	{
		requirePresent('[');
		reader.skipEmpty();
		if(!reader.canRead())
		{
			throw new IOException("Expected List Data");
		}
		else
		{
			ListTag list = new ListTag();
			while(reader.peek() != ']')
			{
				list.add(readTag());
				if(!reader.isPresent(','))
				{
					break;
				}
				reader.skipEmpty();
				if(!reader.canRead())
				{
					throw new IOException("Expected Another Element");
				}
			}
			
			requirePresent(']');
			return list;
		}
	}
	
	protected DataTag readArrayTag() throws IOException
	{
		requirePresent('[');
		char type = reader.pull();
		reader.skip();
		reader.skipEmpty();
		if(!reader.canRead())
		{
			throw new IOException("Out of Data!");
		}
		else if(type == 'B')
		{
			ByteList list = new ByteArrayList();
			readArray(T -> list.add(T.asByte()), 1, PrimitiveTag.class);
			return new ByteArrayTag(list.toByteArray());
		}
		else if(type == 'S')
		{
			ShortList list = new ShortArrayList();
			readArray(T -> list.add(T.asShort()), 2, PrimitiveTag.class);
			return new ShortArrayTag(list.toShortArray());
		}
		else if(type == 'M')
		{
			IntList list = new IntArrayList();
			readArray(T -> list.add(T.asInt()), 3, PrimitiveTag.class);
			return new MediumArrayTag(list.toIntArray());
		}
		else if(type == 'I')
		{
			IntList list = new IntArrayList();
			readArray(T -> list.add(T.asInt()), 4, PrimitiveTag.class);
			return new IntArrayTag(list.toIntArray());
		}
		else if(type == 'L')
		{
			LongList list = new LongArrayList();
			readArray(T -> list.add(T.asLong()), 5, PrimitiveTag.class);
			return new LongArrayTag(list.toLongArray());
		}
		else if(type == 'F')
		{
			FloatList list = new FloatArrayList();
			readArray(T -> list.add(T.asFloat()), 6, PrimitiveTag.class);
			return new FloatArrayTag(list.toFloatArray());
		}
		else if(type == 'D')
		{
			DoubleList list = new DoubleArrayList();
			readArray(T -> list.add(T.asDouble()), 7, PrimitiveTag.class);
			return new DoubleArrayTag(list.toDoubleArray());
		}
		else if(type == 'A')
		{
			ObjectList<String> list = new ObjectArrayList<String>();
			readArray(T -> list.add(T.get()), 8, StringTag.class);
			return new StringArrayTag(list.toArray(new String[list.size()]));
		}
		else
		{
			throw new IOException("Invalid Data Type: [Received=" + type + ", Valid=[B, S, M, I, L, F, D, A]");
		}
	}
	
	protected <T extends DataTag> void readArray(Consumer<T> result, int type, Class<T> clz) throws IOException
	{
		while(true)
		{
			if(reader.peek() != ']')
			{
				DataTag tag = readTag();
				if(tag.getId() != type)
				{
					throw new IOException("Mixing of ArrayTypes [Expected=" + DataTag.create((byte)type).getTypeName() + ", Present=" + tag.getTypeName() + "]");
				}
				result.accept(tag.cast());
				reader.skipEmpty();
				if(reader.isPresent(','))
				{
					reader.skipEmpty();
					if(!reader.canRead())
					{
						throw new IOException("Expected Another Element");
					}
					continue;
				}
			}
			requirePresent(']');
			return;
		}
	}
	
	protected DataTag tag(String s)
	{
		try
		{
			if(FLOAT_TYPE.matcher(s).matches())
			{
				return new FloatTag(Float.parseFloat(s.substring(0, s.length() - 1)));
			}
			if(BYTE_TYPE.matcher(s).matches())
			{
				return new ByteTag(Byte.parseByte(s.substring(0, s.length() - 1)));
			}
			if(LONG_TYPE.matcher(s).matches())
			{
				return new LongTag(Long.parseLong(s.substring(0, s.length() - 1)));
			}
			if(SHORT_TYPE.matcher(s).matches())
			{
				return new ShortTag(Short.parseShort(s.substring(0, s.length() - 1)));
			}
			if(MEDIUM_TYPE.matcher(s).matches())
			{
				return new MediumTag(Integer.parseInt(s.substring(0, s.length() - 1)));
			}
			if(INT_TYPE.matcher(s).matches())
			{
				return new IntTag(Integer.parseInt(s.substring(0, s.length() - 1)));
			}
			if(DOUBLE_TYPE.matcher(s).matches())
			{
				return new DoubleTag(Double.parseDouble(s.substring(0, s.length() - 1)));
			}
			if(DOUBLE_TYPE_DEFAULT.matcher(s).matches())
			{
				return new DoubleTag(Double.parseDouble(s));
			}
			if(INT_TYPE_DEFAULT.matcher(s).matches())
			{
				return new IntTag(Integer.parseInt(s));
			}
			if("true".equalsIgnoreCase(s))
			{
				return new ByteTag((byte)1);
			}
			if("false".equalsIgnoreCase(s))
			{
				return new ByteTag((byte)0);
			}
		}
		catch(NumberFormatException e)
		{
		}
		return new StringTag(s);
	}
	
	protected void requirePresent(char value)
	{
		reader.skipEmpty();
		reader.requirePresent(value);
	}
}
