/*
 * This file is part of Applied Energistics 2.
 * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
 *
 * Applied Energistics 2 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Applied Energistics 2 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Applied Energistics 2.  If not, see <http://www.gnu.org/licenses/lgpl>.
 */

package appeng.tile.misc;


import javax.annotation.Nullable;

import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.FluidTankProperties;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;

import appeng.api.AEApi;
import appeng.api.config.CondenserOutput;
import appeng.api.config.Settings;
import appeng.api.definitions.IMaterials;
import appeng.api.implementations.items.IStorageComponent;
import appeng.api.networking.security.BaseActionSource;
import appeng.api.storage.IMEMonitor;
import appeng.api.storage.IStorageMonitorable;
import appeng.api.storage.IStorageMonitorableAccessor;
import appeng.api.storage.data.IAEFluidStack;
import appeng.api.storage.data.IAEItemStack;
import appeng.api.util.IConfigManager;
import appeng.api.util.IConfigurableObject;
import appeng.capabilities.Capabilities;
import appeng.tile.AEBaseInvTile;
import appeng.tile.TileEvent;
import appeng.tile.events.TileEventType;
import appeng.tile.inventory.AppEngInternalInventory;
import appeng.tile.inventory.InvOperation;
import appeng.util.ConfigManager;
import appeng.util.IConfigManagerHost;
import appeng.util.Platform;


public class TileCondenser extends AEBaseInvTile implements IConfigManagerHost, IConfigurableObject
{

	public static final int BYTE_MULTIPLIER = 8;

	private final int[] sides = {
			0,
			1
	};
	private final AppEngInternalInventory inv = new AppEngInternalInventory( this, 3 );
	private final ConfigManager cm = new ConfigManager( this );
	private final IItemHandler itemHandler = new ItemHandler();
	private final IFluidHandler fluidHandler = new FluidHandler();
	private final MEHandler meHandler = new MEHandler();

	private double storedPower = 0;

	public TileCondenser()
	{
		this.cm.registerSetting( Settings.CONDENSER_OUTPUT, CondenserOutput.TRASH );
	}

	@TileEvent( TileEventType.WORLD_NBT_WRITE )
	public void writeToNBT_TileCondenser( final NBTTagCompound data )
	{
		this.cm.writeToNBT( data );
		data.setDouble( "storedPower", this.getStoredPower() );
	}

	@TileEvent( TileEventType.WORLD_NBT_READ )
	public void readFromNBT_TileCondenser( final NBTTagCompound data )
	{
		this.cm.readFromNBT( data );
		this.setStoredPower( data.getDouble( "storedPower" ) );
	}

	public double getStorage()
	{
		final ItemStack is = this.inv.getStackInSlot( 2 );
		if( is != null )
		{
			if( is.getItem() instanceof IStorageComponent )
			{
				final IStorageComponent sc = (IStorageComponent) is.getItem();
				if( sc.isStorageComponent( is ) )
				{
					return sc.getBytes( is ) * BYTE_MULTIPLIER;
				}
			}
		}
		return 0;
	}

	public void addPower( final double rawPower )
	{
		this.setStoredPower( this.getStoredPower() + rawPower );
		this.setStoredPower( Math.max( 0.0, Math.min( this.getStorage(), this.getStoredPower() ) ) );

		final double requiredPower = this.getRequiredPower();
		final ItemStack output = this.getOutput();
		while( requiredPower <= this.getStoredPower() && output != null && requiredPower > 0 )
		{
			if( this.canAddOutput( output ) )
			{
				this.setStoredPower( this.getStoredPower() - requiredPower );
				this.addOutput( output );
			}
			else
			{
				break;
			}
		}
	}

	private boolean canAddOutput( final ItemStack output )
	{
		final ItemStack outputStack = this.getStackInSlot( 1 );
		return outputStack == null || ( Platform.isSameItem( outputStack, output ) && outputStack.stackSize < outputStack.getMaxStackSize() );
	}

	/**
	 * make sure you validate with canAddOutput prior to this.
	 *
	 * @param output to be added output
	 */
	private void addOutput( final ItemStack output )
	{
		final ItemStack outputStack = this.getStackInSlot( 1 );
		if( outputStack == null )
		{
			this.setInventorySlotContents( 1, output.copy() );
		}
		else
		{
			outputStack.stackSize++;
			this.setInventorySlotContents( 1, outputStack );
		}
	}

	private ItemStack getOutput()
	{
		final IMaterials materials = AEApi.instance().definitions().materials();

		switch( (CondenserOutput) this.cm.getSetting( Settings.CONDENSER_OUTPUT ) )
		{
			case MATTER_BALLS:
				return materials.matterBall().maybeStack( 1 ).orElse( null );

			case SINGULARITY:
				return materials.singularity().maybeStack( 1 ).orElse( null );

			case TRASH:
			default:
				return null;
		}
	}

	public double getRequiredPower()
	{
		return ( (CondenserOutput) this.cm.getSetting( Settings.CONDENSER_OUTPUT ) ).requiredPower;
	}

	@Override
	public IInventory getInternalInventory()
	{
		return this.inv;
	}

	@Override
	public void setInventorySlotContents( final int i, final ItemStack itemstack )
	{
		if( i == 0 )
		{
			if( itemstack != null )
			{
				this.addPower( itemstack.stackSize );
			}
		}
		else
		{
			this.inv.setInventorySlotContents( 1, itemstack );
		}
	}

	@Override
	public boolean isItemValidForSlot( final int i, final ItemStack itemstack )
	{
		return i == 0;
	}

	@Override
	public void onChangeInventory( final IInventory inv, final int slot, final InvOperation mc, final ItemStack removed, final ItemStack added )
	{
		if( slot == 0 )
		{
			final ItemStack is = inv.getStackInSlot( 0 );
			if( is != null )
			{
				this.addPower( is.stackSize );
				inv.setInventorySlotContents( 0, null );
			}
		}
	}

	@Override
	public boolean canInsertItem( final int slotIndex, final ItemStack insertingItem, final EnumFacing side )
	{
		return slotIndex == 0;
	}

	@Override
	public boolean canExtractItem( final int slotIndex, final ItemStack extractedItem, final EnumFacing side )
	{
		return slotIndex != 0;
	}

	@Override
	public int[] getAccessibleSlotsBySide( final EnumFacing side )
	{
		return this.sides;
	}

	@Override
	public void updateSetting( final IConfigManager manager, final Enum settingName, final Enum newValue )
	{
		this.addPower( 0 );
	}

	@Override
	public IConfigManager getConfigManager()
	{
		return this.cm;
	}

	public double getStoredPower()
	{
		return this.storedPower;
	}

	private void setStoredPower( final double storedPower )
	{
		this.storedPower = storedPower;
	}

	@Override
	public boolean hasCapability( Capability<?> capability, EnumFacing facing )
	{
		if( capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY )
		{
			return true;
		}
		else if( capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY )
		{
			return true;
		}
		else if( capability == Capabilities.STORAGE_MONITORABLE_ACCESSOR )
		{
			return true;
		}
		return super.hasCapability( capability, facing );
	}

	@SuppressWarnings( "unchecked" )
	@Override
	public <T> T getCapability( Capability<T> capability, @Nullable EnumFacing facing )
	{
		if( capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY )
		{
			return (T) itemHandler;
		}
		else if( capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY )
		{
			return (T) fluidHandler;
		}
		else if( capability == Capabilities.STORAGE_MONITORABLE_ACCESSOR )
		{
			return (T) meHandler;
		}
		return super.getCapability( capability, facing );
	}

	private class ItemHandler implements IItemHandler
	{

		@Override
		public int getSlots()
		{
			// We only expose the void slot
			return 1;
		}

		@Override
		public ItemStack getStackInSlot( int slot )
		{
			// The void slot never has any content
			return null;
		}

		@Override
		public ItemStack insertItem( int slot, ItemStack stack, boolean simulate )
		{
			if( slot != 0 )
			{
				return stack;
			}
			if( !simulate && stack != null )
			{
				addPower( stack.stackSize );
			}
			return null;
		}

		@Override
		public ItemStack extractItem( int slot, int amount, boolean simulate )
		{
			return null;
		}
	}


	private static final IFluidTankProperties[] EMPTY = { new FluidTankProperties( null, 10, true, false ) };


	/**
	 * A fluid handler that exposes a 10 bucket tank that can only be filled, and - when filled - will add power
	 * to this condenser.
	 */
	private class FluidHandler implements IFluidHandler
	{

		@Override
		public IFluidTankProperties[] getTankProperties()
		{
			return EMPTY;
		}

		@Override
		public int fill( FluidStack resource, boolean doFill )
		{
			if( doFill )
			{
				addPower( ( resource == null ? 0.0 : (double) resource.amount ) / 500.0 );
			}

			return resource == null ? 0 : resource.amount;
		}

		@Nullable
		@Override
		public FluidStack drain( FluidStack resource, boolean doDrain )
		{
			return null;
		}

		@Nullable
		@Override
		public FluidStack drain( int maxDrain, boolean doDrain )
		{
			return null;
		}
	}


	/**
	 * This is used to expose a fake ME subnetwork that is only composed of this condenser tile. The purpose of this is to enable the condenser to
	 * override the {@link appeng.api.storage.IMEInventoryHandler#validForPass(int)} method to make sure a condenser is only ever used if an item
	 * can't go anywhere else.
	 */
	private class MEHandler implements IStorageMonitorableAccessor, IStorageMonitorable
	{
		private final CondenserFluidInventory fluidInventory = new CondenserFluidInventory( TileCondenser.this );

		private final CondenserItemInventory itemInventory = new CondenserItemInventory( TileCondenser.this );

		@Nullable
		@Override
		public IStorageMonitorable getInventory( BaseActionSource src )
		{
			return this;
		}

		@Override
		public IMEMonitor<IAEItemStack> getItemInventory()
		{
			return itemInventory;
		}

		@Override
		public IMEMonitor<IAEFluidStack> getFluidInventory()
		{
			return fluidInventory;
		}
	}
}
