
package appeng.fluids.util;


import java.util.Objects;

import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidTankProperties;

import appeng.api.storage.data.IAEFluidStack;
import appeng.core.AELog;
import appeng.util.Platform;


public class AEFluidInventory implements IAEFluidTank
{
	private final IAEFluidStack[] fluids;
	private final IAEFluidInventory handler;
	private final int capacity;
	private IFluidTankProperties[] props = null;

	public AEFluidInventory( final IAEFluidInventory handler, final int slots, final int capcity )
	{
		this.fluids = new IAEFluidStack[slots];
		this.handler = handler;
		this.capacity = capcity;
	}

	public AEFluidInventory( final IAEFluidInventory handler, final int slots )
	{
		this( handler, slots, Integer.MAX_VALUE );
	}

	@Override
	public void setFluidInSlot( final int slot, final IAEFluidStack fluid )
	{
		if( slot >= 0 && slot < this.getSlots() )
		{
			if( Objects.equals( this.fluids[slot], fluid ) )
			{
				if( fluid != null && fluid.getStackSize() != this.fluids[slot].getStackSize() )
				{
					this.fluids[slot].setStackSize( Math.min( fluid.getStackSize(), this.capacity ) );
					this.onContentChanged( slot );
				}
			}
			else
			{
				if( fluid == null )
				{
					this.fluids[slot] = null;
				}
				else
				{
					this.fluids[slot] = fluid.copy();
					this.fluids[slot].setStackSize( Math.min( fluid.getStackSize(), this.capacity ) );
				}

				this.onContentChanged( slot );
			}
		}
	}

	private void onContentChanged( final int slot )
	{
		if( this.handler != null && Platform.isServer() )
		{
			this.handler.onFluidInventoryChanged( this, slot );
		}
	}

	@Override
	public IAEFluidStack getFluidInSlot( final int slot )
	{
		if( slot >= 0 && slot < this.getSlots() )
		{
			return this.fluids[slot];
		}
		return null;
	}

	@Override
	public int getSlots()
	{
		return this.fluids.length;
	}

	@Override
	public IFluidTankProperties[] getTankProperties()
	{
		if( this.props == null )
		{
			this.props = new IFluidTankProperties[this.getSlots()];
			for( int i = 0; i < this.getSlots(); ++i )
			{
				this.props[i] = new FluidTankPropertiesWrapper( i );
			}

		}
		return this.props;
	}

	public int fill( final int slot, final FluidStack resource, final boolean doFill )
	{
		if( resource == null || resource.amount <= 0 )
		{
			return 0;
		}

		final IAEFluidStack fluid = this.fluids[slot];

		if( fluid != null && !fluid.equals( resource ) )
		{
			return 0;
		}

		int amountToStore = this.capacity;

		if( fluid != null )
		{
			amountToStore -= fluid.getStackSize();
		}

		amountToStore = Math.min( amountToStore, resource.amount );

		if( doFill )
		{
			if( fluid == null )
			{
				this.setFluidInSlot( slot, AEFluidStack.fromFluidStack( resource ) );
			}
			else
			{
				fluid.setStackSize( fluid.getStackSize() + amountToStore );
				this.onContentChanged( slot );
			}
		}

		return amountToStore;
	}

	public FluidStack drain( final int slot, final FluidStack resource, final boolean doDrain )
	{
		final IAEFluidStack fluid = this.fluids[slot];
		if( resource == null || fluid == null || !fluid.equals( resource ) )
		{
			return null;
		}
		return this.drain( slot, resource.amount, doDrain );
	}

	public FluidStack drain( final int slot, final int maxDrain, boolean doDrain )
	{
		final IAEFluidStack fluid = this.fluids[slot];
		if( fluid == null || maxDrain <= 0 )
		{
			return null;
		}

		int drained = maxDrain;
		if( fluid.getStackSize() < drained )
		{
			drained = (int) fluid.getStackSize();
		}

		FluidStack stack = new FluidStack( fluid.getFluid(), drained );
		if( doDrain )
		{
			fluid.setStackSize( fluid.getStackSize() - drained );
			if( fluid.getStackSize() <= 0 )
			{
				this.fluids[slot] = null;
			}
			this.onContentChanged( slot );
		}
		return stack;
	}

	@Override
	public int fill( final FluidStack fluid, final boolean doFill )
	{
		if( fluid == null || fluid.amount <= 0 )
		{
			return 0;
		}

		final FluidStack insert = fluid.copy();

		int totalFillAmount = 0;
		for( int slot = 0; slot < this.getSlots(); ++slot )
		{
			int fillAmount = this.fill( slot, insert, doFill );
			totalFillAmount += fillAmount;
			insert.amount -= fillAmount;
			if( insert.amount <= 0 )
			{
				break;
			}
		}
		return totalFillAmount;
	}

	@Override
	public FluidStack drain( final FluidStack fluid, final boolean doDrain )
	{
		if( fluid == null || fluid.amount <= 0 )
		{
			return null;
		}

		final FluidStack resource = fluid.copy();

		FluidStack totalDrained = null;
		for( int slot = 0; slot < this.getSlots(); ++slot )
		{
			FluidStack drain = this.drain( slot, resource, doDrain );
			if( drain != null )
			{
				if( totalDrained == null )
				{
					totalDrained = drain;
				}
				else
				{
					totalDrained.amount += drain.amount;
				}

				resource.amount -= drain.amount;
				if( resource.amount <= 0 )
				{
					break;
				}
			}
		}
		return totalDrained;
	}

	@Override
	public FluidStack drain( final int maxDrain, final boolean doDrain )
	{
		if( maxDrain == 0 )
		{
			return null;
		}

		FluidStack totalDrained = null;
		int toDrain = maxDrain;

		for( int slot = 0; slot < this.getSlots(); ++slot )
		{
			if( totalDrained == null )
			{
				totalDrained = this.drain( slot, toDrain, doDrain );
				if( totalDrained != null )
				{
					toDrain -= totalDrained.amount;
				}
			}
			else
			{
				FluidStack copy = totalDrained.copy();
				copy.amount = toDrain;
				FluidStack drain = this.drain( slot, copy, doDrain );
				if( drain != null )
				{
					totalDrained.amount += drain.amount;
					toDrain -= drain.amount;
				}
			}

			if( toDrain <= 0 )
			{
				break;
			}
		}
		return totalDrained;
	}

	public void writeToNBT( final NBTTagCompound data, final String name )
	{
		final NBTTagCompound c = new NBTTagCompound();
		this.writeToNBT( c );
		data.setTag( name, c );
	}

	private void writeToNBT( final NBTTagCompound target )
	{
		for( int x = 0; x < this.fluids.length; x++ )
		{
			try
			{
				final NBTTagCompound c = new NBTTagCompound();

				if( this.fluids[x] != null )
				{
					this.fluids[x].writeToNBT( c );
				}

				target.setTag( "#" + x, c );
			}
			catch( final Exception ignored )
			{
			}
		}
	}

	public void readFromNBT( final NBTTagCompound data, final String name )
	{
		final NBTTagCompound c = data.getCompoundTag( name );
		if( c != null )
		{
			this.readFromNBT( c );
		}
	}

	private void readFromNBT( final NBTTagCompound target )
	{
		for( int x = 0; x < this.fluids.length; x++ )
		{
			try
			{
				final NBTTagCompound c = target.getCompoundTag( "#" + x );

				if( c != null )
				{
					this.fluids[x] = AEFluidStack.fromNBT( c );
				}
			}
			catch( final Exception e )
			{
				AELog.debug( e );
			}
		}
	}

	private class FluidTankPropertiesWrapper implements IFluidTankProperties
	{
		private final int slot;

		public FluidTankPropertiesWrapper( final int slot )
		{
			this.slot = slot;
		}

		@Override
		public FluidStack getContents()
		{
			return AEFluidInventory.this.fluids[this.slot] == null ? null : AEFluidInventory.this.fluids[this.slot].getFluidStack();
		}

		@Override
		public int getCapacity()
		{
			return Math.min( AEFluidInventory.this.capacity, Integer.MAX_VALUE );
		}

		@Override
		public boolean canFill()
		{
			return true;
		}

		@Override
		public boolean canDrain()
		{
			return true;
		}

		@Override
		public boolean canFillFluidType( FluidStack fluidStack )
		{
			return true;
		}

		@Override
		public boolean canDrainFluidType( FluidStack fluidStack )
		{
			return fluidStack != null;
		}
	}
}
