package speiger.src.coreengine.utils.io;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import com.google.gson.stream.JsonWriter;

import speiger.src.collections.bytes.collections.ByteStack;
import speiger.src.collections.bytes.lists.ByteArrayList;

public class PrettyGsonWriter extends JsonWriter
{
	String indent;

	ByteStack blocked = ByteArrayList.wrap((byte)0);
	int[] usedArrays = new int[32];
	int arrayPointer = 0;
	
	String[] singleNames = new String[32];
	int namePointer = 0;
	String nextArray = null;
	Set<String> singleLineObjects = new HashSet<>();
	
	
	public PrettyGsonWriter(Writer out)
	{
		this(new Overrider(out));
	}
	
	public PrettyGsonWriter(Writer out, String indent)
	{
		this(new Overrider(out));
		setTabs(indent);
	}
	
	private PrettyGsonWriter(Overrider rider)
	{
		super(rider);
		rider.owner = this;
	}
	
	public PrettyGsonWriter addSinlgeLines(String... names)
	{
		singleLineObjects.addAll(Arrays.asList(names));
		return this;
	}
	
	public void setTabs(String key)
	{
		setIndent(key);
		if(key.length() == 0) indent = null;
		else indent = key;
	}
	
	private void push(boolean value, boolean object)
	{
		if(indent == null) return;
		if(value)
		{
			if(++arrayPointer >= usedArrays.length) usedArrays = Arrays.copyOf(usedArrays, usedArrays.length * 2);
			if(!object && ++namePointer >= singleNames.length) singleNames = Arrays.copyOf(singleNames, singleNames.length * 2);
		}
		else if((blocked.top() & 1) != 0) usedArrays[arrayPointer]++;
		blocked.push((byte)((value ? 1 : 0) | (value && !object ? 2 : 0)));
	}
	
	private void pop()
	{
		if(indent == null) return;
		byte value = blocked.pop();
		if((value & 1) != 0) usedArrays[arrayPointer--] = 0;
		if((value & 2) != 0) singleNames[namePointer--] = null;
	}
	
	@Override
	public JsonWriter name(String name) throws IOException
	{
		if(singleLineObjects.contains(name)) nextArray = name;
		return super.name(name);
	}
	
	@Override
	public JsonWriter beginArray() throws IOException
	{
		super.beginArray();
		push(true, false);
		singleNames[namePointer] = nextArray;
		nextArray = null;
		return this;
	}
	
	@Override
	public JsonWriter beginObject() throws IOException
	{
		boolean shouldRework = shouldBlockObject();
		push(false, true);
		super.beginObject();
		if(shouldRework) push(shouldRework, true);
		return this;
	}
	
	@Override
	public JsonWriter endArray() throws IOException
	{
		boolean ignore = usedArrays[arrayPointer] > 0;
		if(ignore) pop();
		super.endArray();
		if(!ignore) pop();
		return this;
	}
	
	@Override
	public JsonWriter endObject() throws IOException
	{
		super.endObject();
		if(shouldBlockObject()) pop();
		pop();
		return this;
	}
	
	public boolean shouldBlockObject()
	{
		return singleNames[namePointer] != null;
	}
	
	public boolean isBlocked()
	{
		return (blocked.top() & 1) != 0;
	}
	
	public static class Overrider extends BufferedWriter
	{
		PrettyGsonWriter owner;
		public Overrider(Writer out)
		{
			super(out);
		}
		
		@Override
		public void write(int c) throws IOException
		{
			if(owner.indent != null && owner.isBlocked())
			{
				if(c == '\n') return;
				else if(c == ',' && owner.shouldBlockObject())
				{
					super.write(", ");
					return;
				}
			}
			super.write(c);
		}
		
		@Override
		public Writer append(char c) throws IOException
		{
			if(owner.indent != null && c == ',' && owner.isBlocked() && (owner.usedArrays[owner.arrayPointer] <= 0))
			{
				super.write(", ");
				return this;
			}
			return super.append(c);
		}
		
		@Override
		public void write(String str) throws IOException
		{
			if(owner.indent != null && owner.isBlocked() && str.equals(owner.indent)) return;
			super.write(str);
		}
	}
}
