package org.squiddev.plethora.api.reference;

import com.google.common.base.Objects;
import dan200.computercraft.api.lua.LuaException;
import net.minecraft.block.state.IBlockState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.BlockPos;
import net.minecraft.world.World;
import org.squiddev.plethora.api.IWorldLocation;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;

public class BlockReference implements IReference<BlockReference> {
	private final IWorldLocation location;
	private final WeakReference<TileEntity> tile;
	private IBlockState state;

	public BlockReference(@Nonnull IWorldLocation location, @Nonnull IBlockState state, @Nullable TileEntity tile) {
		this.location = location;
		this.tile = tile == null ? null : new WeakReference<TileEntity>(tile);
		this.state = state;
	}

	public BlockReference(@Nonnull IWorldLocation location) {
		this(location, location.getWorld().getBlockState(location.getPos()), location.getWorld().getTileEntity(location.getPos()));
	}

	@Nonnull
	@Override
	public BlockReference get() throws LuaException {
		World world = location.getWorld();
		BlockPos pos = location.getPos();

		IBlockState newState = world.getBlockState(pos);
		TileEntity newTe = world.getTileEntity(pos);

		if (state.getBlock() != newState.getBlock()) throw new LuaException("The block is no longer there");

		if (tile == null && newTe != null) {
			throw new LuaException("The block has changed");
		} else if (tile != null) {
			TileEntity oldTe = tile.get();
			if (oldTe == null) {
				throw new LuaException("The block is no longer there");
			} else if (!oldTe.equals(newTe)) {
				throw new LuaException("The block has changed");
			}
		}

		// Update the block state if everything is OK
		state = newState;

		return this;
	}

	@Nonnull
	public IWorldLocation getLocation() {
		return location;
	}

	@Nonnull
	public IBlockState getState() {
		return state;
	}

	@Nullable
	public TileEntity getTileEntity() {
		return tile == null ? null : tile.get();
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;

		BlockReference that = (BlockReference) o;

		if (!location.equals(that.location)) return false;
		if (tile != that.tile) {
			if (tile == null) return false;

			TileEntity thisTile = tile.get();
			TileEntity thatTile = that.tile == null ? null : that.tile.get();

			if (!Objects.equal(thisTile, thatTile)) return false;
		}

		return state.equals(that.state);
	}

	@Override
	public int hashCode() {
		int result = location.hashCode();
		result = 31 * result + state.getBlock().hashCode();
		return result;
	}
}

@@ -597,16 +597,45 @@
 %3E tile;%0A
+%09private final int tileHash;%0A
 %09private
@@ -870,16 +870,70 @@
 (tile);%0A
+%09%09this.tileHash = tile == null ? 0 : tile.hashCode();%0A
 %09%09this.s
@@ -1380,24 +1380,110 @@
 tity(pos);%0A%0A
+%09%09if (tile == null) %7B%0A%09%09%09// We only monitor block changes if we can't compare the TE%0A%09
 %09%09if (state.
@@ -1580,28 +1580,13 @@
 %0A%0A%09%09
+%09
 if (
-tile == null && 
 newT
@@ -1596,21 +1596,16 @@
 = null) 
-%7B%0A%09%09%09
 throw ne
@@ -1657,26 +1657,8 @@
 else
- if (tile != null)
  %7B%0A%09
@@ -2413,16 +2413,45 @@
 ocation)
+ %7C%7C tileHash != that.tileHash
 ) return
@@ -2458,16 +2458,17 @@
  false;%0A
+%0A
 %09%09if (ti
@@ -2704,32 +2704,12 @@
 urn 
-state.equals(that.state)
+true
 ;%0A%09%7D
@@ -2752,20 +2752,14 @@
 %7B%0A%09%09
-int result =
+return
  loc
@@ -2778,79 +2778,24 @@
 de()
-;%0A%09%09result = 31 * result + state.getBlock().hashCode();%0A%09%09return result
+ + 31 * tileHash
 ;%0A%09%7D

Allow block changing if tile doesn't change