package speiger.src.coreengine.utils.collections.managers.dynamic;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

import speiger.src.collections.ints.collections.IntIterator;
import speiger.src.collections.ints.maps.abstracts.AbstractInt2IntMap.BasicEntry;
import speiger.src.collections.ints.maps.interfaces.Int2IntMap;
import speiger.src.collections.ints.maps.interfaces.Int2ObjectMap;
import speiger.src.collections.ints.misc.pairs.IntObjectPair;
import speiger.src.collections.ints.sets.IntAVLTreeSet;
import speiger.src.collections.ints.sets.IntSet;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.sets.ObjectAVLTreeSet;
import speiger.src.collections.objects.utils.ObjectLists;
import speiger.src.coreengine.utils.collections.pools.ThreadPool;

public class DynamicDataManager<T>
{
	static final ThreadPool<DataSlot> SLOTS = new ThreadPool<>(1000, DataSlot::new, DataSlot::clear);
	final IDynamicDataHandler<T> manager;
	Int2ObjectMap<DataSlot> slots = Int2ObjectMap.builder().linkedMap();
	Set<DataSlot> emptySlots = new ObjectAVLTreeSet<>();
	IntSet changedSlots = new IntAVLTreeSet();
	DataSlot lastSlot = null;
	
	public DynamicDataManager(IDynamicDataHandler<T> manager)
	{
		this.manager = manager;
	}
	
	public boolean add(int dataIndex, T value)
	{
		if(slots.containsKey(dataIndex)) return false;		
		return add(dataIndex, manager.toBytes(value));
	}
	
	protected boolean add(int dataIndex, byte[] data)
	{
		if(!emptySlots.isEmpty())
		{
			for(Iterator<DataSlot> iter = emptySlots.iterator();iter.hasNext();)
			{
				DataSlot slot = iter.next();
				if(slot.getFreeBytes() >= data.length)
				{
					slot.setData(dataIndex, data);
					slots.put(dataIndex, slot);
					changedSlots.add(dataIndex);
					return true;
				}
			}
		}
		DataSlot next = SLOTS.get();
		next.set(lastSlot, dataIndex, data);
		slots.put(dataIndex, next);
		changedSlots.add(dataIndex);
		lastSlot = next;
		return true;
	}
	
	public boolean contains(int dataIndex)
	{
		return slots.containsKey(dataIndex);
	}
	
	public boolean update(int dataIndex, T value)
	{
		DataSlot slot = slots.get(dataIndex);
		if(slot != null)
		{
			byte[] data = manager.toBytes(value);
			if(slot.getFreeBytes() >= data.length)
			{
				slot.setData(dataIndex, data);
				changedSlots.add(dataIndex);
				return true;
			}
			else
			{
				slot.setData(-1, null);
				slots.remove(dataIndex);
				add(dataIndex, data);
				emptySlots.add(slot);
				return true;
			}
		}
		return false;
	}
	
	public Iterator<Int2IntMap.Entry> iterator(int byteOffset)
	{
		return new Iterator<Int2IntMap.Entry>() {
			BasicEntry entry = new BasicEntry();
			DataSlot current = findNextSlot(lastSlot);
			@Override
			public boolean hasNext()
			{
				return current != null;
			}
			
			DataSlot findNextSlot(DataSlot input)
			{
				for(input = input.previous; input != null && input.value == null;input = input.previous);
				return input;
			}
			
			@Override
			public Int2IntMap.Entry next()
			{
				if(!hasNext()) throw new IllegalStateException();
				entry.set(current.dataIndex, current.byteOffset / byteOffset);
				current = findNextSlot(current);
				return entry;
			}
		};
	}
	
	public boolean remove(int dataIndex)
	{
		DataSlot slot = slots.remove(dataIndex);
		if(slot != null)
		{
			slot.setData(-1, null);
			emptySlots.add(slot);
			return true;
		}
		return false;
	}
	
	protected void clearEmptySlots()
	{
		while(lastSlot != null && lastSlot.value == null)
		{
			DataSlot prev = lastSlot;
			emptySlots.remove(prev);
			lastSlot = prev.previous;
			SLOTS.accept(prev);
		}
	}
	
	public void clear()
	{
		slots.clear();
		emptySlots.clear();
		changedSlots.clear();
		while(lastSlot != null)
		{
			DataSlot prev = lastSlot;
			lastSlot = prev.previous;
			SLOTS.accept(prev);
		}
	}
	
	public void update()
	{
		int last = getEndSize();
		clearEmptySlots();
		if(changedSlots.isEmpty())
		{
			if(last != getEndSize())
			{
				manager.uploadBytes(ObjectLists.empty(), getEndSize());
			}
			return;
		}
		List<IntObjectPair<byte[]>> list = new ObjectArrayList<>();
		while(!changedSlots.isEmpty())
		{
			DataSlot start = null;
			DataSlot current = null;
			int bytesAllocated = 0;
			for(IntIterator iter = changedSlots.iterator();iter.hasNext();)
			{
				DataSlot slot = slots.get(iter.nextInt());
				iter.remove();
				if(slot == null) break;
				if(current == null || current.next == slot)
				{
					current = slot;
					if(start == null) start = slot;
					bytesAllocated += slot.getUsedBytes();
					continue;
				}
				break;
			}
			if(bytesAllocated <= 0 || start == null || current == null) continue;
			byte[] data = new byte[bytesAllocated];
			int byteOffset = start.byteOffset;
			bytesAllocated = 0;
			while(start.byteOffset <= current.byteOffset);
			{
				System.arraycopy(start.value, 0, data, bytesAllocated, start.value.length);
				bytesAllocated += start.getUsedBytes();				
				start = start.next;
			}
			list.add(IntObjectPair.of(byteOffset, data));
		}
		if(!list.isEmpty())
		{
			manager.uploadBytes(list, getEndSize());
		}
	}
	
	private int getEndSize()
	{
		return lastSlot == null ? 0 : lastSlot.getNextByteOffset();
	}
	
	public int size()
	{
		return slots.size();
	}
}
