package erogenousbeef.bigreactors.utils;

import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.InventoryLargeChest;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.MathHelper;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.FluidContainerRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.IFluidHandler;
import buildcraft.api.tools.IToolWrench;
import cofh.api.item.IToolHammer;
import erogenousbeef.bigreactors.common.BigReactors;
import erogenousbeef.bigreactors.utils.intermod.ModHelperBase;
import erogenousbeef.core.common.CoordTriplet;

public class StaticUtils {

	public static final ForgeDirection[] CardinalDirections = new ForgeDirection[] { ForgeDirection.NORTH, ForgeDirection.EAST, ForgeDirection.SOUTH, ForgeDirection.WEST };
	
	public static final ForgeDirection neighborsBySide[][] = new ForgeDirection[][] {
		{ForgeDirection.NORTH, ForgeDirection.SOUTH, ForgeDirection.WEST, ForgeDirection.EAST},
		{ForgeDirection.NORTH, ForgeDirection.SOUTH, ForgeDirection.WEST, ForgeDirection.EAST},
		{ForgeDirection.UP, ForgeDirection.DOWN, ForgeDirection.EAST, ForgeDirection.WEST},
		{ForgeDirection.UP, ForgeDirection.DOWN, ForgeDirection.WEST, ForgeDirection.EAST},
		{ForgeDirection.UP, ForgeDirection.DOWN, ForgeDirection.NORTH, ForgeDirection.SOUTH},
		{ForgeDirection.UP, ForgeDirection.DOWN, ForgeDirection.SOUTH, ForgeDirection.NORTH}
	};
	
	public static class Inventory {
		/**
		 * Consume a single item from a stack of items
		 * @param stack The stack from which to consume
		 * @return The remainder of the stack, or null if the stack was fully consumed.
		 */
		public static ItemStack consumeItem(ItemStack stack)
		{
			return consumeItem(stack, 1);
		}
		
		/**
		 * Consume some amount of items from a stack of items. Assumes you've already validated
		 * the consumption. If you try to consume more than the stack has, it will simply destroy
		 * the stack, as if you'd consumed all of it.
		 * @param stack The stack from which to consume
		 * @return The remainder of the stack, or null if the stack was fully consumed.
		 */
		public static ItemStack consumeItem(ItemStack stack, int amount)
		{
			if(stack == null) { return null; }

			if(stack.stackSize <= amount)
			{
				if(stack.getItem().hasContainerItem(stack))
				{
					return stack.getItem().getContainerItem(stack);
				}
				else
				{
					return null;
				}
			}
			else
			{
				stack.splitStack(amount);
				return stack;
			}	
		}
		
		/**
		 * Is this player holding a goddamn wrench?
		 * @return True if the player is holding a goddamn wrench. BC only, screw you.
		 */
		public static boolean isPlayerHoldingWrench(EntityPlayer player) {
			if(player.inventory.getCurrentItem() == null) { 
				return false;
			}
			Item currentItem = player.inventory.getCurrentItem().getItem();
			return (ModHelperBase.useCofh && currentItem instanceof IToolHammer) ||
					(ModHelperBase.useBuildcraftTools && currentItem instanceof IToolWrench);
		}
		
		/**
		 * Check to see if two stacks are equal. NBT-sensitive.
		 * Lifted from PowerCrystalsCore.
		 * @param s1 First stack to compare
		 * @param s2 Second stack to compare
		 * @return True if stacks are equal, false otherwise.
		 */
        public static boolean areStacksEqual(ItemStack s1, ItemStack s2)
        {
                return areStacksEqual(s1, s2, true);
        }

        /**
		 * Check to see if two stacks are equal. Optionally NBT-sensitive.
		 * Lifted from PowerCrystalsCore.
		 * @param s1 First stack to compare
		 * @param s2 Second stack to compare
         * @param nbtSensitive True if stacks' NBT tags should be checked for equality.
         * @return True if stacks are equal, false otherwise.
         */
        public static boolean areStacksEqual(ItemStack s1, ItemStack s2, boolean nbtSensitive)
        {
                if(s1 == null || s2 == null) return false;
                if(!s1.isItemEqual(s2)) return false;
                
                if(nbtSensitive)
                {
                        if(s1.getTagCompound() == null && s2.getTagCompound() == null) return true;
                        if(s1.getTagCompound() == null || s2.getTagCompound() == null) return false;
                        return s1.getTagCompound().equals(s2.getTagCompound());
                }
                
                return true;
        }

        private static final ForgeDirection[] chestDirections = new ForgeDirection[] { ForgeDirection.NORTH,
        																				ForgeDirection.SOUTH,
        																				ForgeDirection.EAST,
        																				ForgeDirection.WEST};

		public static IInventory checkForDoubleChest(World worldObj, IInventory te, int x, int y, int z) {
			for(ForgeDirection dir : chestDirections) {
				if(worldObj.getBlock(x + dir.offsetX, y + dir.offsetY, z + dir.offsetZ) == Blocks.chest) {
					TileEntity otherTe = worldObj.getTileEntity(x + dir.offsetX, y + dir.offsetY, z + dir.offsetZ);
					if(otherTe instanceof IInventory) {
						return new InventoryLargeChest("Large Chest", te, (IInventory)otherTe);
					}
				}
			}

			// Not a large chest, so just return the single chest.
			return te;
		}
	}
	
	public static class Fluids {
		/* Below stolen from COFHLib because COFHLib itself still relies on cofh.core */
		public static boolean fillTankWithContainer(World world, IFluidHandler handler, EntityPlayer player) {

	        ItemStack container = player.getCurrentEquippedItem();
	        FluidStack fluid = FluidContainerRegistry.getFluidForFilledItem(container);

	        if (fluid != null) {
	                if (handler.fill(ForgeDirection.UNKNOWN, fluid, false) == fluid.amount || player.capabilities.isCreativeMode) {
	                        if (world.isRemote) {
	                                return true;
	                        }
	                        handler.fill(ForgeDirection.UNKNOWN, fluid, true);

	                        if (!player.capabilities.isCreativeMode) {
	                                player.inventory.setInventorySlotContents(player.inventory.currentItem, Inventory.consumeItem(container));
	                        }
	                        return true;
	                }
	        }
	        return false;
		}

		public static boolean fillContainerFromTank(World world, IFluidHandler handler, EntityPlayer player, FluidStack tankFluid) {
			ItemStack container = player.getCurrentEquippedItem();
			
			if (FluidContainerRegistry.isEmptyContainer(container)) {
			        ItemStack returnStack = FluidContainerRegistry.fillFluidContainer(tankFluid, container);
			        FluidStack fluid = FluidContainerRegistry.getFluidForFilledItem(returnStack);
			
			        if (fluid == null || returnStack == null) {
			                return false;
			        }
			        if (!player.capabilities.isCreativeMode) {
			                if (container.stackSize == 1) {
			                        container = container.copy();
			                        player.inventory.setInventorySlotContents(player.inventory.currentItem, returnStack);
			                } else if (!player.inventory.addItemStackToInventory(returnStack)) {
			                        return false;
			                }
			                handler.drain(ForgeDirection.UNKNOWN, fluid.amount, true);
			                container.stackSize--;
			
			                if (container.stackSize <= 0) {
			                        container = null;
			                }
			        } else {
			                handler.drain(ForgeDirection.UNKNOWN, fluid.amount, true);
			        }
			        return true;
			}
			return false;
		}
	}
	
	public static class ExtraMath {
		/**
		 * Linear interpolate between two numbers.
		 * @param from
		 * @param to
		 * @param modifier
		 * @return
		 */
		public static float Lerp(float from, float to, float modifier)
		{
			modifier = Math.min(1f, Math.max(0f, modifier));
		    return from + modifier * (to - from);
		}
		
		/**
		 * Calculate the volume of the cube defined by two coordinates.
		 * @param minimum Minimum coordinate.
		 * @param maximum Maximum coordinate.
		 * @return The cube's volume, in blocks.
		 */
		public static int Volume(CoordTriplet minimum, CoordTriplet maximum) {
			if(minimum == null || maximum == null) { return 0; }

			int xsize = Math.abs(maximum.x - minimum.x) + 1;
			int ysize = Math.abs(maximum.y - minimum.y) + 1;
			int zsize = Math.abs(maximum.z - minimum.z) + 1;
			return xsize * ysize * zsize;
		}
	}
	
	public static class Energy {
		public static float RFPerCentigradePerUnitVolume = 10f;

		public static float getRFFromVolumeAndTemp(int volume, float temperature) {
			return temperature * (float)volume * RFPerCentigradePerUnitVolume;
		}
		
		public static float getTempFromVolumeAndRF(int volume, float rf) {
			return rf / ((float)volume * RFPerCentigradePerUnitVolume);
		}
	}
	
	public static class Strings {
		public static String[] sizePrefixes = {"", "Ki", "Me", "Gi", "Te", "Pe", "Ex", "Ze", "Yo", "Ho"};
		// Ho = Hojillion
		
		public static String formatRF(float number) {
			String prefix = "";
			if(number < 0f) {
				prefix = "-";
				number *= -1;
			}
			
			if(number <= 0.00001f) { return "0.00 RF"; }
			
			int power = (int)Math.floor(Math.log10(number));

			int decimalPoints = 2 - (power % 3);
			int letterIdx = Math.max(0, Math.min(sizePrefixes.length, power / 3));
			double divisor = Math.pow(1000f, letterIdx);
			
			if(divisor > 0) {
				return String.format("%s%." + Integer.toString(decimalPoints) + "f %sRF", prefix, number/divisor, sizePrefixes[letterIdx]);
			}
			else {
				return String.format("%s%." + Integer.toString(decimalPoints) + "f RF", prefix, number);
			}
		}
		
		public static String formatMillibuckets(float number) {
			String prefix = "";
			if(number < 0f) {
				prefix = "-";
				number *= -1;
			}
			
			if(number <= 0.00001f) { return "0.000 mB"; }
			int power = (int)Math.floor(Math.log10(number));
			if(power < 1) {
				return String.format("%.3f mB", number);
			}
			else if(power < 2) {
				return String.format("%.2f mB", number);
			}
			else if(power < 3) {
				return String.format("%.1f mB", number);
			}
			else if(power < 4) {
				return String.format("%.0f mB", number);
			}
			else {
				number /= 1000f; // Re-render into buckets
				if(power < 5) {
					return String.format("%.2f B", number);
				}
				else if(power < 6) {
					return String.format("%.1f B", number);
				}
				else {
					return String.format("%.0f B", number);
				}
			}
		}
	}
	
	// Mob = Mobile = Entity
	public static class Mob {
		/**
		 * @param entity The entity whose facing you wish to query.
		 * @return The ForgeDirection which entity is facing (north/south/east/west)
		 */
		protected ForgeDirection getFacingDirection(Entity entity) {
			int facingAngle = (MathHelper.floor_double((entity.rotationYaw * 4F) / 360F + 0.5D) & 3);
			switch(facingAngle) {
			case 1:
				return ForgeDirection.EAST;
			case 2:
				return ForgeDirection.SOUTH;
			case 3:
				return ForgeDirection.WEST;
			default:
				return ForgeDirection.NORTH;
			}
		}
	}
	
	public static class TE {
		public static TileEntity getTileEntityUnsafe(IBlockAccess iba, int x, int y, int z) {
			TileEntity te = null;

			if(iba instanceof World) {
				// We don't want to trigger tile entity loads in this method
				te = getTileEntityUnsafe((World)iba, x, y, z);
			}
			else {
				// Should never happen, generally
				te = iba.getTileEntity(x, y, z);
			}
			
			return te;
		}
		
		public static TileEntity getTileEntityUnsafe(World world, int x, int y, int z) {
			TileEntity te = null;
			
			Chunk chunk = world.getChunkFromBlockCoords(x, z);
			if(chunk != null) {
				te = chunk.getTileEntityUnsafe(x & 0x0F, y, z & 0x0F);
			}
			
			return te;
		}
	}
	
	public static class WorldGen {
		/**
		 * Check if a Big Reactors world generator should even bother to run
		 * in a given dimension.
		 * @param dimensionId The dimension being queried for WorldGen.
		 * @return True if world generation should proceed, false if it should be skipped altogether.
		 */
		public static boolean shouldGenerateInDimension(int dimensionId) {
			return dimensionId >= 0 || BigReactors.enableWorldGenInNegativeDimensions ||
					BigReactors.dimensionWhitelist.contains(dimensionId);
		}
	}
}
