package com.hbm.blocks.machine;

import com.hbm.blocks.ModBlocks;

import java.util.Random;

import com.hbm.blocks.BlockEnums.LightType;
import com.hbm.blocks.ISpotlight;
import com.hbm.main.ResourceManager;
import com.hbm.world.gen.nbt.INBTBlockTransformable;

import cpw.mods.fml.client.registry.RenderingRegistry;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
import net.minecraft.block.BlockSlab;
import net.minecraft.block.material.MapColor;
import net.minecraft.block.material.Material;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.client.model.obj.WavefrontObject;
import net.minecraftforge.common.util.ForgeDirection;

public class Spotlight extends Block implements ISpotlight, INBTBlockTransformable {

	public static final int META_YELLOW = 0;
	public static final int META_GREEN = 1;
	public static final int META_BLUE = 2;

	public static boolean disableOnGeneration = true;

	// I'd be extending the ReinforcedLamp class if it wasn't for the inverted behaviour of these specific lights
	// I want these blocks to be eminently useful, so removing the need for redstone by default is desired,
	// these act more like redstone torches, in that applying a signal turns them off
	public boolean isOn;

	public int beamLength;
	public LightType type;

	public Spotlight(Material mat, int beamLength, LightType type, boolean isOn) {
		super(mat);

		this.beamLength = beamLength;
		this.type = type;
		this.isOn = isOn;

		this.setHardness(0.5F);

		if(isOn) setLightLevel(1.0F);
	}

	public static int renderID = RenderingRegistry.getNextAvailableRenderId();

	@Override
	public int getRenderType() {
		return renderID;
	}

	public WavefrontObject getModel() {
		switch(type) {
		case FLUORESCENT: return (WavefrontObject) ResourceManager.fluorescent_lamp;
		case HALOGEN: return (WavefrontObject) ResourceManager.flood_lamp;
		default: return (WavefrontObject) ResourceManager.cage_lamp;
		}
	}

	public String getPartName(int connectionCount) {
		switch(type) {
		case HALOGEN: return "FloodLamp";
		default: return "CageLamp";
		}
	}

	@Override
	public boolean isOpaqueCube() {
		return false;
	}

	@Override
	public boolean renderAsNormalBlock() {
		return false;
	}

	@Override
	// Ah yes, I love methods named the literal opposite of what they do
	public boolean getBlocksMovement(IBlockAccess world, int x, int y, int z) {
		return true;
	}

	@Override
	public MapColor getMapColor(int meta) {
        return MapColor.airColor;
    }

	@Override
	public AxisAlignedBB getCollisionBoundingBoxFromPool(World p_149668_1_, int p_149668_2_, int p_149668_3_, int p_149668_4_) {
		return null;
	}

	@Override
	public void setBlockBoundsBasedOnState(IBlockAccess world, int x, int y, int z) {
		ForgeDirection dir = getDirection(world, x, y, z);
		float[] bounds = swizzleBounds(dir);
		float[] offset = new float[] { 0.5F - dir.offsetX * (0.5F - bounds[0]), 0.5F - dir.offsetY * (0.5F - bounds[1]), 0.5F - dir.offsetZ * (0.5F - bounds[2]) };

		setBlockBounds(offset[0] - bounds[0], offset[1] - bounds[1], offset[2] - bounds[2], offset[0] + bounds[0], offset[1] + bounds[1], offset[2] + bounds[2]);
	}

	private float[] swizzleBounds(ForgeDirection dir) {
		float[] bounds = getBounds();
		switch(dir) {
		case EAST:
		case WEST: return new float[] { bounds[2], bounds[1], bounds[0] };
		case UP:
		case DOWN: return new float[] { bounds[1], bounds[2], bounds[0] };
		default: return bounds;
		}
	}

	// Returns an xyz (half-)size for a given object type
	private float[] getBounds() {
		switch(type) {
		case FLUORESCENT: return new float[] { 0.5F, 0.5F, 0.1F };
		case HALOGEN: return new float[] { 0.35F, 0.25F, 0.2F };
		default: return new float[] { 0.25F, 0.2F, 0.15F };
		}
	}

	@Override
	public int onBlockPlaced(World world, int x, int y, int z, int side, float hx, float hy, float hz, int initData) {
		return side << 1;
	}

	@Override
	public void onBlockAdded(World world, int x, int y, int z) {
		if(world.isRemote) return;
		if(updatePower(world, x, y, z)) return;
		updateBeam(world, x, y, z);
	}

	private boolean updatePower(World world, int x, int y, int z) {
		if(isBroken(world.getBlockMetadata(x, y, z))) return false;

		boolean isPowered = world.isBlockIndirectlyGettingPowered(x, y, z);
		if(isOn && isPowered) {
			world.scheduleBlockUpdate(x, y, z, this, 4);
			return true;
		} else if(!isOn && !isPowered) {
			world.setBlock(x, y, z, getOn(), world.getBlockMetadata(x, y, z), 2);
			return true;
		}

		return false;
	}

	@Override
	public void breakBlock(World world, int x, int y, int z, Block block, int metadata) {
		ForgeDirection dir = getDirection(metadata);
		super.breakBlock(world, x, y, z, block, metadata);

		if(world.isRemote) return;

		unpropagateBeam(world, x, y, z, dir);
	}

	@Override
	public void updateTick(World world, int x, int y, int z, Random p_149674_5_) {
		if (world.isRemote) return;

		if (isOn && world.isBlockIndirectlyGettingPowered(x, y, z)) {
			world.setBlock(x, y, z, getOff(), world.getBlockMetadata(x, y, z), 2);
		}
	}

	// Repropagate the beam if we've become unblocked
	@Override
	public void onNeighborBlockChange(World world, int x, int y, int z, Block neighborBlock) {
		if(world.isRemote) return;
		if(neighborBlock instanceof SpotlightBeam) return;
		if(neighborBlock == Blocks.air) return;

		ForgeDirection dir = getDirection(world, x, y, z);

		if(!canPlace(world, x, y, z, dir)) {
			dropBlockAsItem(world, x, y, z, 0, 0);
			world.setBlockToAir(x, y, z);
			return;
		}

		if(updatePower(world, x, y, z)) return;

		updateBeam(world, x, y, z);
	}

	@Override
	public boolean canPlaceBlockOnSide(World world, int x, int y, int z, int side) {
		if(!super.canPlaceBlockOnSide(world, x, y, z, side)) return false;

		ForgeDirection dir = ForgeDirection.getOrientation(side);

		return canPlace(world, x, y, z, dir);
	}

	// BlockSlab doesn't actually properly return isSideSolid,
	// probably because MOJANK thought this would only ever be used for torches,
	// which can't be placed on ceilings...
	private boolean canPlace(World world, int x, int y, int z, ForgeDirection dir) {
		x -= dir.offsetX;
		y -= dir.offsetY;
		z -= dir.offsetZ;

		Block block = world.getBlock(x, y, z);
		if(block instanceof BlockSlab) {
			int meta = world.getBlockMetadata(x, y, z);
			return dir == ((meta & 8) == 8 ? ForgeDirection.UP : ForgeDirection.DOWN) || block.func_149730_j();
		}

		return block.isSideSolid(world, x, y, z, dir);
	}

	private void updateBeam(World world, int x, int y, int z) {
		if(!isOn) return;

		ForgeDirection dir = getDirection(world, x, y, z);
		propagateBeam(world, x, y, z, dir, beamLength, META_YELLOW);
	}

	public ForgeDirection getDirection(IBlockAccess world, int x, int y, int z) {
		int metadata = world.getBlockMetadata(x, y, z);
		return getDirection(metadata);
	}

	public ForgeDirection getDirection(int metadata) {
		return ForgeDirection.getOrientation(metadata >> 1);
	}

	// Replace bulbs on broken lights with a click
	@Override
	public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int side, float hitX, float hitY, float hitZ) {
		int meta = world.getBlockMetadata(x, y, z);
		if(!isBroken(meta)) return false;

		repair(world, x, y, z);
		return true;
	}

	private void repair(World world, int x, int y, int z) {
		int meta = world.getBlockMetadata(x, y, z);
		if(!isBroken(meta)) return;

		world.setBlock(x, y, z, getOn(), meta - 1, 2);

		for(ForgeDirection dir : ForgeDirection.VALID_DIRECTIONS) {
			int ox = x + dir.offsetX;
			int oy = y + dir.offsetY;
			int oz = z + dir.offsetZ;
			Block block = world.getBlock(ox, oy, oz);
			if(block == this) repair(world, ox, oy, oz);
		}
	}

	public boolean isBroken(int metadata) {
		return (metadata & 1) == 1;
	}

	@Override
	public Item getItemDropped(int i, Random r, int j) {
		return Item.getItemFromBlock(getOn());
	}

	@Override
	@SideOnly(Side.CLIENT)
	public Item getItem(World world, int x, int y, int z) {
		return Item.getItemFromBlock(getOn());
	}

	@Override
	protected ItemStack createStackedBlock(int e) {
		return new ItemStack(getOn());
	}

	// Recursively add beam blocks, updating any that already exist with new incoming light directions
	public static void propagateBeam(World world, int x, int y, int z, ForgeDirection dir, int distance, int meta) {
		distance--;
		if(distance <= 0)
			return;

		x += dir.offsetX;
		y += dir.offsetY;
		z += dir.offsetZ;

		Block block = world.getBlock(x, y, z);
		if(!block.isAir(world, x, y, z))
			return;

		if(!(block instanceof SpotlightBeam)) {
			world.setBlock(x, y, z, ModBlocks.spotlight_beam, meta, 3);
		}

		// If we encounter an existing beam, add a new INCOMING direction to the
		// metadata, and cancel propagation if something goes wrong
		if (SpotlightBeam.setDirection(world, x, y, z, dir, true) == 0)
			return;

		propagateBeam(world, x, y, z, dir, distance, meta);
	}

	// Recursively delete beam blocks, if they aren't still illuminated from a different direction
	public static void unpropagateBeam(World world, int x, int y, int z, ForgeDirection dir) {
		x += dir.offsetX;
		y += dir.offsetY;
		z += dir.offsetZ;

		Block block = world.getBlock(x, y, z);
		if(!(block instanceof SpotlightBeam))
			return;

		// Remove the metadata associated with this direction
		// If all directions are set to zero, delete the beam
		if(SpotlightBeam.setDirection(world, x, y, z, dir, false) == 0) {
			world.setBlockToAir(x, y, z);
		}

		unpropagateBeam(world, x, y, z, dir);
	}

	// Travels back through a beam to the source, and if found, repropagates the beam
	public static void backPropagate(World world, int x, int y, int z, ForgeDirection dir, int meta) {
		x -= dir.offsetX;
		y -= dir.offsetY;
		z -= dir.offsetZ;

		Block block = world.getBlock(x, y, z);
		if(block instanceof ISpotlight) {
			ISpotlight spot = (ISpotlight) block;
			propagateBeam(world, x, y, z, dir, spot.getBeamLength(), meta);
		} else if(!(block instanceof SpotlightBeam)) {
			return;
		}

		backPropagate(world, x, y, z, dir, meta);
	}

	protected Block getOff() {
		if(this == ModBlocks.spotlight_incandescent) return ModBlocks.spotlight_incandescent_off;
		if(this == ModBlocks.spotlight_fluoro) return ModBlocks.spotlight_fluoro_off;
		if(this == ModBlocks.spotlight_halogen) return ModBlocks.spotlight_halogen_off;

		return this;
	}

	protected Block getOn() {
		if(this == ModBlocks.spotlight_incandescent_off) return ModBlocks.spotlight_incandescent;
		if(this == ModBlocks.spotlight_fluoro_off) return ModBlocks.spotlight_fluoro;
		if(this == ModBlocks.spotlight_halogen_off) return ModBlocks.spotlight_halogen;

		return this;
	}

	@Override
	public int getBeamLength() {
		return this.beamLength;
	}

	@Override
	public int transformMeta(int meta, int coordBaseMode) {
		// +1 to set as broken, won't turn on until broken and replaced
		int disabled = disableOnGeneration ? 1 : 0;
		return (INBTBlockTransformable.transformMetaDeco(meta >> 1, coordBaseMode) << 1) + disabled;
	}

	@Override
	public Block transformBlock(Block block) {
		if(!disableOnGeneration) return block;
		if(block == ModBlocks.spotlight_incandescent) return ModBlocks.spotlight_incandescent_off;
		if(block == ModBlocks.spotlight_fluoro) return ModBlocks.spotlight_fluoro_off;
		if(block == ModBlocks.spotlight_halogen) return ModBlocks.spotlight_halogen_off;
		return block;
	}

}