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

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.StringJoiner;
import java.util.function.Consumer;

import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.utils.io.dataTag.DataTag;
import speiger.src.coreengine.utils.io.dataTag.compression.DataTagLimiter;
import speiger.src.coreengine.utils.io.dataTag.compression.TagCompressor;
import speiger.src.coreengine.utils.io.dataTag.compression.TagDecompressor;
import speiger.src.coreengine.utils.io.streams.ExtendedDataInput;
import speiger.src.coreengine.utils.io.streams.ExtendedDataOutput;

public class ListTag implements IListTag, Iterable<DataTag>
{
	List<DataTag> list = new ObjectArrayList<DataTag>();
	
	@Override
	public int getId()
	{
		return 18;
	}
	
	@Override
	public String getTypeName()
	{
		return "List";
	}
	
	@Override
	public void addKeys(Consumer<String> keys)
	{
		for(int i = 0,m=list.size();i<m;i++)
		{
			list.get(i).addKeys(keys);
		}
	}
	
	@Override
	public long countBytes(long input)
	{
		input+=4;
		for(int i = 0,m=list.size();i<m;i++)
		{
			input = list.get(i).countBytes(input);
		}
		return input;
	}
	
	@Override
	public void write(ExtendedDataOutput output, TagCompressor compressor) throws IOException
	{
		output.writeVarInt(list.size());
		if(!list.isEmpty())
		{
			output.writeByte(list.get(0).getId());
			for(int i = 0,m=list.size();i<m;i++)
			{
				list.get(i).write(output, compressor);
			}
		}
	}
	
	@Override
	public void read(ExtendedDataInput input, TagDecompressor decompressor, int depthLimit, DataTagLimiter limiter) throws IOException
	{
		if(depthLimit >= 512)
		{
			throw new IllegalStateException("Depth Limit Exceeded");
		}
		limiter.countBytes(6);
		int size = input.readVarInt();
		if(size <= 0)
		{
			return;
		}
		byte type = input.readByte();
		for(int i = 0;i<size;i++)
		{
			list.add(decompressor.readSubTag(type, "Unknown", depthLimit+1));
		}
	}
	
	@Override
	public DataTag copy()
	{
		ListTag tag = new ListTag();
		for(int i = 0,m=list.size();i<m;i++)
		{
			tag.list.add(list.get(i).copy());
		}
		return tag;
	}
	
	@Override
	public int hashCode()
	{
		return list.hashCode();
	}

	@Override
	public boolean equals(Object obj)
	{
		return obj instanceof ListTag && ((ListTag)obj).list.equals(list);
	}

	@Override
	public String toString()
	{
		StringJoiner joiner = new StringJoiner(",", "[", "]");
		for(int i = 0,m=list.size();i<m;i++)
		{
			joiner.add(list.get(i).toString());
		}
		return joiner.toString();
	}
	
	@Override
	public String prettyPrint(String indent, int depth)
	{
		if(list.isEmpty())
		{
			return "[]";
		}
		if(list.size() < 8 && list.get(0).getId() >= 1 && list.get(0).getId() < 8)
		{
			StringJoiner joiner = new StringJoiner(",", "[", "]");
			for(int i = 0,m=list.size();i<m;i++)
			{
				joiner.add(list.get(i).prettyPrint(indent, depth+1));
			}
			return joiner.toString();
		}
		else
		{
			StringBuilder builder = new StringBuilder().append("[");
			if(!indent.isEmpty())
			{
				builder.append('\n');
			}
			String s = DataTag.repeate(indent, depth+1);
			for(int i = 0,m=list.size();i<m;i++)
			{
				builder.append(s).append(list.get(i).prettyPrint(indent, depth+1));
				if(i + 1 < m)
				{
					builder.append(",").append(indent.isEmpty() ? " " : '\n');
				}
			}
			if(indent.isEmpty())
			{
				builder.append('\n').append(DataTag.repeate(indent, depth));
			}
			return builder.append("]").toString();
		}
	}
	
	@Override
	public void add(DataTag tag)
	{
		if(tag == null || tag.getId() == 0)
		{
			throw new IllegalStateException("Tried to insert an Empty Tag into the system");
		}
		if(list.isEmpty())
		{
			list.add(tag);
			return;
		}
		else if(list.get(0).getId() == tag.getId())
		{
			list.add(tag);
		}
	}
	
	public DataTag set(int index, DataTag tag)
	{
		if(list.isEmpty() || index < 0 || index >= list.size())
		{
			throw new ArrayIndexOutOfBoundsException(index);
		}
		else if(list.get(0).getId() != tag.getId() || tag.getId() == 0)
		{
			throw new IllegalStateException("Tag Type didn't match. [Inserted="+tag.getTypeName()+", Expected="+list.get(0).getTypeName()+"]");
		}
		return list.set(index, tag);
	}
	
	public DataTag remove(int index)
	{
		return list.remove(index);
	}
	
	@Override
	public DataTag get(int index)
	{
		return list.get(index);
	}
	
	public boolean isEmpty()
	{
		return list.isEmpty();
	}
	
	public int size()
	{
		return list.size();
	}
	
	public ListTag subList(int from, int to)
	{
		ListTag list = new ListTag();
		for(int i = 0,m=to-from;i<m;i++)
		{
			list.list.add(list.get(from+i).copy());
		}
		return list;
	}
	
	@Override
	public Iterator<DataTag> iterator()
	{
		return new Iterator<DataTag>(){
			int index = 0;
			@Override
			public boolean hasNext()
			{
				return index < list.size();
			}
			@Override
			public DataTag next()
			{
				return list.get(index++);
			}
		};
	}
	
	public <S> Iterable<S> typeIterator(Class<S> clz)
	{
		return () -> new Iterator<S>(){
			int index = 0;
			@Override
			public boolean hasNext()
			{
				return index < list.size();
			}
			@Override
			public S next()
			{
				return list.get(index++).cast();
			}
		};
	}
}
