package net.minecraftforge.common.util;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

import net.minecraft.block.Block;
import net.minecraft.block.BlockAnvil;
import net.minecraft.block.BlockBed;
import net.minecraft.block.BlockButton;
import net.minecraft.block.BlockChest;
import net.minecraft.block.BlockCocoa;
import net.minecraft.block.BlockDispenser;
import net.minecraft.block.BlockDoor;
import net.minecraft.block.BlockEndPortalFrame;
import net.minecraft.block.BlockEnderChest;
import net.minecraft.block.BlockFenceGate;
import net.minecraft.block.BlockFurnace;
import net.minecraft.block.BlockHopper;
import net.minecraft.block.BlockHugeMushroom;
import net.minecraft.block.BlockLadder;
import net.minecraft.block.BlockLever;
import net.minecraft.block.BlockLog;
import net.minecraft.block.BlockPistonBase;
import net.minecraft.block.BlockPistonExtension;
import net.minecraft.block.BlockPumpkin;
import net.minecraft.block.BlockRail;
import net.minecraft.block.BlockRailDetector;
import net.minecraft.block.BlockRailPowered;
import net.minecraft.block.BlockRedstoneComparator;
import net.minecraft.block.BlockRedstoneRepeater;
import net.minecraft.block.BlockSkull;
import net.minecraft.block.BlockStairs;
import net.minecraft.block.BlockTorch;
import net.minecraft.block.BlockTrapDoor;
import net.minecraft.block.BlockTripWireHook;
import net.minecraft.block.BlockVine;
import net.minecraft.init.Blocks;
import net.minecraft.world.World;

import java.util.HashMap;
import java.util.Map;

import static net.minecraftforge.common.util.ForgeDirection.*;

/**
 * This class is a helper function for vanilla blocks, and should not be called by Modders.
 * Refer to block.rotateBlock and block.getValidRotations instead.
 *
 */
public class RotationHelper {
	/**
	 * Some blocks have the same rotation.
	 * The first of these blocks (sorted by itemID) should be listed as a type.
	 * Some of the types aren't actual blocks (helper types).
	 */
	private static enum BlockType {
		LOG,
		DISPENSER,
		BED,
		RAIL,
		RAIL_POWERED,
		RAIL_ASCENDING,
		RAIL_CORNER,
		TORCH,
		STAIR,
		CHEST,
		SIGNPOST,
		DOOR,
		LEVER,
		BUTTON,
		REDSTONE_REPEATER,
		TRAPDOOR,
		MUSHROOM_CAP,
		MUSHROOM_CAP_CORNER,
		MUSHROOM_CAP_SIDE,
		VINE,
		SKULL,
		ANVIL
	}

	private static final ForgeDirection[] UP_DOWN_AXES = new ForgeDirection[] { UP, DOWN };
	private static final Map<BlockType, BiMap<Integer, ForgeDirection>> MAPPINGS = new HashMap<BlockType, BiMap<Integer, ForgeDirection>>();

	public static ForgeDirection[] getValidVanillaBlockRotations(Block block)
	{
		return (block instanceof BlockBed || 
				block instanceof BlockPumpkin ||
				block instanceof BlockFenceGate || 
				block instanceof BlockEndPortalFrame || 
				block instanceof BlockTripWireHook || 
				block instanceof BlockCocoa || 
				block instanceof BlockRailPowered || 
				block instanceof BlockRailDetector || 
				block instanceof BlockStairs || 
				block instanceof BlockChest || 
				block instanceof BlockEnderChest || 
				block instanceof BlockFurnace || 
				block instanceof BlockLadder || 
				block == Blocks.wall_sign || 
				block == Blocks.standing_sign || 
				block instanceof BlockDoor || 
				block instanceof BlockRail ||
				block instanceof BlockButton || 
				block instanceof BlockRedstoneRepeater || 
				block instanceof BlockRedstoneComparator || 
				block instanceof BlockTrapDoor || 
				block instanceof BlockHugeMushroom || 
				block instanceof BlockVine || 
				block instanceof BlockSkull || 
				block instanceof BlockAnvil) ? UP_DOWN_AXES : VALID_DIRECTIONS;
	}

	public static boolean rotateVanillaBlock(Block block, World worldObj, int x, int y, int z, ForgeDirection axis)
	{
		if (worldObj.isRemote)
		{
			return false;
		}

		if (axis == UP || axis == DOWN)
		{
			if (block instanceof BlockBed || block instanceof BlockPumpkin || block instanceof BlockFenceGate || block instanceof BlockEndPortalFrame || block instanceof BlockTripWireHook || block instanceof BlockCocoa)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0x3, BlockType.BED);
			}
			if (block instanceof BlockRail)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0xF, BlockType.RAIL);
			}
			if (block instanceof BlockRailPowered || block instanceof BlockRailDetector)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0x7, BlockType.RAIL_POWERED);
			}
			if (block instanceof BlockStairs)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0x3, BlockType.STAIR);
			}
			if (block instanceof BlockChest || block instanceof BlockEnderChest || block instanceof BlockFurnace || block instanceof BlockLadder || block == Blocks.wall_sign)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0x7, BlockType.CHEST);
			}
			if (block == Blocks.standing_sign)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0xF, BlockType.SIGNPOST);
			}
			if (block instanceof BlockDoor)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0x3, BlockType.DOOR);
			}
			if (block instanceof BlockButton)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0x7, BlockType.BUTTON);
			}
			if (block instanceof BlockRedstoneRepeater || block instanceof BlockRedstoneComparator)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0x3, BlockType.REDSTONE_REPEATER);
			}
			if (block instanceof BlockTrapDoor)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0x3, BlockType.TRAPDOOR);
			}
			if (block instanceof BlockHugeMushroom)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0xF, BlockType.MUSHROOM_CAP);
			}
			if (block instanceof BlockVine)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0xF, BlockType.VINE);
			}
			if (block instanceof BlockSkull)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0x7, BlockType.SKULL);
			}
			if (block instanceof BlockAnvil)
			{
				return rotateBlock(worldObj, x, y, z, axis, 0x1, BlockType.ANVIL);
			}
		}

		if (block instanceof BlockLog)
		{
			return rotateBlock(worldObj, x, y, z, axis, 0xC, BlockType.LOG);
		}
		if (block instanceof BlockDispenser || block instanceof BlockPistonBase || block instanceof BlockPistonExtension || block instanceof BlockHopper)
		{
			return rotateBlock(worldObj, x, y, z, axis, 0x7, BlockType.DISPENSER);
		}
		if (block instanceof BlockTorch)
		{
			return rotateBlock(worldObj, x, y, z, axis, 0xF, BlockType.TORCH);
		}
		if (block instanceof BlockLever)
		{
			return rotateBlock(worldObj, x, y, z, axis, 0x7, BlockType.LEVER);
		}

		return false;
	}

	private static boolean rotateBlock(World worldObj, int x, int y, int z, ForgeDirection axis, int mask, BlockType blockType)
	{
		int rotMeta = worldObj.getBlockMetadata(x, y, z);
		if (blockType == BlockType.DOOR && (rotMeta & 0x8) == 0x8)
		{
			return false;
		}
		int masked = rotMeta & ~mask;
		int meta = rotateMetadata(axis, blockType, rotMeta & mask);
		if (meta == -1)
		{
			return false;
		}
		worldObj.setBlockMetadataWithNotify(x, y, z, meta & mask | masked, 3);
		return true;
	}

	private static int rotateMetadata(ForgeDirection axis, BlockType blockType, int meta)
	{
		if (blockType == BlockType.RAIL || blockType == BlockType.RAIL_POWERED)
		{
			if (meta == 0x0 || meta == 0x1)
			{
				return ~meta & 0x1;
			}
			if (meta >= 0x2 && meta <= 0x5)
			{
				blockType = BlockType.RAIL_ASCENDING;
			}
			if (meta >= 0x6 && meta <= 0x9 && blockType == BlockType.RAIL)
			{
				blockType = BlockType.RAIL_CORNER;
			}
		}
		if (blockType == BlockType.SIGNPOST)
		{
			return (axis == UP) ? (meta + 0x4) % 0x10 : (meta + 0xC) % 0x10;
		}
		if (blockType == BlockType.LEVER && (axis == UP || axis == DOWN))
		{
			switch (meta)
			{
			case 0x5:
				return 0x6;
			case 0x6:
				return 0x5;
			case 0x7:
				return 0x0;
			case 0x0:
				return 0x7;
			}
		}
		if (blockType == BlockType.MUSHROOM_CAP)
		{
			if (meta % 0x2 == 0)
			{
				blockType = BlockType.MUSHROOM_CAP_SIDE;
			}
			else
			{
				blockType = BlockType.MUSHROOM_CAP_CORNER;
			}
		}
		if (blockType == BlockType.VINE)
		{
			return ((meta << 1) | ((meta & 0x8) >> 3));
		}

		ForgeDirection orientation = metadataToDirection(blockType, meta);
		ForgeDirection rotated = orientation.getRotation(axis);
		return directionToMetadata(blockType, rotated);
	}

	private static ForgeDirection metadataToDirection(BlockType blockType, int meta)
	{
		if (blockType == BlockType.LEVER)
		{
			if (meta == 0x6)
			{
				meta = 0x5;
			}
			else if (meta == 0x0)
			{
				meta = 0x7;
			}
		}

		if (MAPPINGS.containsKey(blockType))
		{
			BiMap<Integer, ForgeDirection> biMap = MAPPINGS.get(blockType);
			if (biMap.containsKey(meta))
			{
				return biMap.get(meta);
			}
		}

		if (blockType == BlockType.TORCH)
		{
			return ForgeDirection.getOrientation(6 - meta);
		}
		if (blockType == BlockType.STAIR)
		{
			return ForgeDirection.getOrientation(5 - meta);
		}
		if (blockType == BlockType.CHEST || blockType == BlockType.DISPENSER || blockType == BlockType.SKULL)
		{
			return ForgeDirection.getOrientation(meta);
		}
		if (blockType == BlockType.BUTTON)
		{
			return ForgeDirection.getOrientation(6 - meta);
		}
		if (blockType == BlockType.TRAPDOOR)
		{
			return ForgeDirection.getOrientation(meta + 2).getOpposite();
		}

		return ForgeDirection.UNKNOWN;
	}

	private static int directionToMetadata(BlockType blockType, ForgeDirection direction)
	{
		if ((blockType == BlockType.LOG || blockType == BlockType.ANVIL) && (direction.offsetX + direction.offsetY + direction.offsetZ) < 0)
		{
			direction = direction.getOpposite();
		}

		if (MAPPINGS.containsKey(blockType))
		{
			BiMap<ForgeDirection, Integer> biMap = MAPPINGS.get(blockType).inverse();
			if (biMap.containsKey(direction))
			{
				return biMap.get(direction);
			}
		}

		if (blockType == BlockType.TORCH)
		{
			if (direction.ordinal() >= 1)
			{
				return 6 - direction.ordinal();
			}
		}
		if (blockType == BlockType.STAIR)
		{
			return 5 - direction.ordinal();
		}
		if (blockType == BlockType.CHEST || blockType == BlockType.DISPENSER || blockType == BlockType.SKULL)
		{
			return direction.ordinal();
		}
		if (blockType == BlockType.BUTTON)
		{
			if (direction.ordinal() >= 2)
			{
				return 6 - direction.ordinal();
			}
		}
		if (blockType == BlockType.TRAPDOOR)
		{
			return direction.getOpposite().ordinal() - 2;
		}

		return -1;
	}

	static
	{
		BiMap<Integer, ForgeDirection> biMap;

		biMap = HashBiMap.create(3);
		biMap.put(0x0, UP);
		biMap.put(0x4, EAST);
		biMap.put(0x8, SOUTH);
		MAPPINGS.put(BlockType.LOG, biMap);

		biMap = HashBiMap.create(4);
		biMap.put(0x0, SOUTH);
		biMap.put(0x1, WEST);
		biMap.put(0x2, NORTH);
		biMap.put(0x3, EAST);
		MAPPINGS.put(BlockType.BED, biMap);

		biMap = HashBiMap.create(4);
		biMap.put(0x2, EAST);
		biMap.put(0x3, WEST);
		biMap.put(0x4, NORTH);
		biMap.put(0x5, SOUTH);
		MAPPINGS.put(BlockType.RAIL_ASCENDING, biMap);

		biMap = HashBiMap.create(4);
		biMap.put(0x6, WEST);
		biMap.put(0x7, NORTH);
		biMap.put(0x8, EAST);
		biMap.put(0x9, SOUTH);
		MAPPINGS.put(BlockType.RAIL_CORNER, biMap);

		biMap = HashBiMap.create(6);
		biMap.put(0x1, EAST);
		biMap.put(0x2, WEST);
		biMap.put(0x3, SOUTH);
		biMap.put(0x4, NORTH);
		biMap.put(0x5, UP);
		biMap.put(0x7, DOWN);
		MAPPINGS.put(BlockType.LEVER, biMap);

		biMap = HashBiMap.create(4);
		biMap.put(0x0, WEST);
		biMap.put(0x1, NORTH);
		biMap.put(0x2, EAST);
		biMap.put(0x3, SOUTH);
		MAPPINGS.put(BlockType.DOOR, biMap);

		biMap = HashBiMap.create(4);
		biMap.put(0x0, NORTH);
		biMap.put(0x1, EAST);
		biMap.put(0x2, SOUTH);
		biMap.put(0x3, WEST);
		MAPPINGS.put(BlockType.REDSTONE_REPEATER, biMap);

		biMap = HashBiMap.create(4);
		biMap.put(0x1, EAST);
		biMap.put(0x3, SOUTH);
		biMap.put(0x7, NORTH);
		biMap.put(0x9, WEST);
		MAPPINGS.put(BlockType.MUSHROOM_CAP_CORNER, biMap);

		biMap = HashBiMap.create(4);
		biMap.put(0x2, NORTH);
		biMap.put(0x4, WEST);
		biMap.put(0x6, EAST);
		biMap.put(0x8, SOUTH);
		MAPPINGS.put(BlockType.MUSHROOM_CAP_SIDE, biMap);

		biMap = HashBiMap.create(2);
		biMap.put(0x0, SOUTH);
		biMap.put(0x1, EAST);
		MAPPINGS.put(BlockType.ANVIL, biMap);
	}
}
