package thebetweenlands.common.block.terrain;

import java.util.List;
import java.util.Random;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import net.minecraft.block.Block;
import net.minecraft.block.BlockStairs;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.BlockFaceShape;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemBlock;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.Explosion;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.fluids.BlockFluidClassic;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidUtil;
import net.minecraftforge.fluids.IFluidBlock;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import thebetweenlands.api.block.IWaterDisplacementHandler;
import thebetweenlands.client.render.particle.BLParticles;
import thebetweenlands.common.block.ITintedBlock;
import thebetweenlands.common.item.armor.ItemMarshRunnerBoots;
import thebetweenlands.common.registries.BlockRegistry;
import thebetweenlands.common.registries.BlockRegistry.ICustomItemBlock;
import thebetweenlands.common.registries.BlockRegistry.IStateMappedBlock;
import thebetweenlands.common.world.WorldProviderBetweenlands;
import thebetweenlands.util.AdvancedStateMap;

public class BlockSwampWater extends BlockFluidClassic implements IStateMappedBlock, ITintedBlock, ICustomItemBlock {
	private static final int DEEP_COLOR_R = 19;
	private static final int DEEP_COLOR_G = 24;
	private static final int DEEP_COLOR_B = 68;
	private boolean isUnderwaterBlock = false;

	public BlockSwampWater(Fluid fluid, Material material) {
		super(fluid, material);
	}

	public BlockSwampWater setUnderwaterBlock(boolean underwaterBlock) {
		this.isUnderwaterBlock = underwaterBlock;
		return this;
	}

	@SuppressWarnings("deprecation")
	@Override
	public void addCollisionBoxToList(IBlockState state, World worldIn, BlockPos pos, AxisAlignedBB entityBox, List<AxisAlignedBB> collidingBoxes, @Nullable Entity entityIn, boolean isActualState) {
		if(entityIn instanceof EntityPlayer && ItemMarshRunnerBoots.checkPlayerWalkOnWater((EntityPlayer) entityIn)) {
				addCollisionBoxToList(pos, entityBox, collidingBoxes, new AxisAlignedBB(0, 0, 0, 1, ((float)this.getQuantaValue(worldIn, pos) / (float)this.quantaPerBlock) * 0.8F + 0.3F, 1));
				return;
		}
		super.addCollisionBoxToList(state, worldIn, pos, entityBox, collidingBoxes, entityIn, isActualState);
    }
	
	@Override
    @Nonnull
    public Vec3d modifyAcceleration(@Nonnull World world, @Nonnull BlockPos pos, @Nonnull Entity entity, @Nonnull Vec3d vec) {
		if(entity instanceof EntityPlayer && ItemMarshRunnerBoots.checkPlayerWalkOnWater((EntityPlayer) entity)) {
			return Vec3d.ZERO;
		}
		if (densityDir > 0) return vec;
        Vec3d vec_flow = this.getFlowVector(world, pos);
        return vec.add(
                vec_flow.x,
                vec_flow.y,
                vec_flow.z);
    }
	
	@Override
	public boolean canDisplace(IBlockAccess world, BlockPos pos) {
		if (world.isAirBlock(pos)) return true;

		IBlockState state = world.getBlockState(pos);

		if (state.getBlock() instanceof BlockSwampWater) {
			return false;
		}

		if (displacements.containsKey(state.getBlock())) {
			return displacements.get(state.getBlock());
		}

		Material material = state.getMaterial();
		if (material.blocksMovement() || material == Material.PORTAL) {
			return false;
		}

		int density = getDensity(world, pos);
		if (density == Integer.MAX_VALUE) {
			return true;
		}

		if (this.density > density) {
			return true;
		} else {
			return false;
		}
	}

	@Override
	public boolean displaceIfPossible(World world, BlockPos pos) {
		if (world instanceof World && !((World) world).isBlockLoaded(pos)) return false;

		if (world.isAirBlock(pos)) {
			return true;
		}

		IBlockState state = world.getBlockState(pos);
		Block block = state.getBlock();
		if (block instanceof BlockSwampWater) {
			return false;
		}
		
		if(block instanceof IWaterDisplacementHandler && ((IWaterDisplacementHandler) block).onWaterDisplacement(world, pos, this)) {
			return false;
		}

		if (displacements.containsKey(block)) {
			if (displacements.get(block)) {
				block.dropBlockAsItem(world, pos, state, 0);
				return true;
			}
			return false;
		}

		Material material = state.getMaterial();
		if (material.blocksMovement() || material == Material.PORTAL) {
			return false;
		}

		int density = getDensity(world, pos);
		if (density == Integer.MAX_VALUE) {
			block.dropBlockAsItem(world, pos, state, 0);
			return true;
		}

		if (this.density > density) {
			return true;
		} else {
			return false;
		}
	}

	@Override
	public float getFluidHeightForRender(IBlockAccess world, BlockPos pos, @Nonnull IBlockState up) {
		IBlockState here = world.getBlockState(pos);
		if (here.getBlock() instanceof BlockSwampWater) {
			if ((up.getMaterial().isLiquid() || up.getBlock() instanceof IFluidBlock) && (up.getBlock() instanceof BlockSwampWater == false || !((BlockSwampWater)up.getBlock()).isUnderwaterBlock)) {
				return 1;
			}

			if (here.getValue(LEVEL) == getMaxRenderHeightMeta()) {
				return 0.875F;
			}
		}
		return !here.getMaterial().isSolid() && up.getBlock() instanceof BlockSwampWater ? 1 : this.getQuantaPercentage(world, pos) * 0.875F;
	}

	@Override
	public Vec3d getFlowVector(IBlockAccess world, BlockPos pos) {
		Vec3d vec = new Vec3d(0.0D, 0.0D, 0.0D);
		int decay = quantaPerBlock - getQuantaValue(world, pos);

		for (int side = 0; side < 4; ++side)
		{
			int x2 = pos.getX();
			int z2 = pos.getZ();

			switch (side)
			{
			case 0: --x2; break;
			case 1: --z2; break;
			case 2: ++x2; break;
			case 3: ++z2; break;
			}

			BlockPos pos2 = new BlockPos(x2, pos.getY(), z2);
			int otherDecay = quantaPerBlock - getQuantaValue(world, pos2);
			if (otherDecay >= quantaPerBlock)
			{
				if (!world.getBlockState(pos2).getMaterial().blocksMovement())
				{
					otherDecay = quantaPerBlock - getQuantaValue(world, pos2.down());
					if (otherDecay >= 0)
					{
						int power = otherDecay - (decay - quantaPerBlock);
						vec = vec.add((pos2.getX() - pos.getX()) * power, 0, (pos2.getZ() - pos.getZ()) * power);
					}
				}
			}
			else if (otherDecay >= 0)
			{
				int power = otherDecay - decay;
				vec = vec.add((pos2.getX() - pos.getX()) * power, 0, (pos2.getZ() - pos.getZ()) * power);
			}
		}
		
		if (!this.isSourceBlock(world, pos) && world.getBlockState(pos.up()).getBlock() instanceof BlockSwampWater) {
            for (EnumFacing dir : EnumFacing.Plane.HORIZONTAL) {
                if (this.causesDownwardCurrent(world, pos.offset(dir), dir) || this.causesDownwardCurrent(world, pos.offset(dir).up(), dir)) {
                	vec = vec.normalize().add(0.0D, -6.0D, 0.0D);
                    break;
                }
            }
        }
		
		vec = vec.normalize();
		return vec;
	}

	@Override
	protected boolean causesDownwardCurrent(IBlockAccess world, BlockPos pos, EnumFacing side) {
        IBlockState state = world.getBlockState(pos);
        Block block = state.getBlock();
        Material material = state.getMaterial();

        if (material == this.material) {
            return false;
        } else if (side == EnumFacing.UP) {
            return true;
        } else if (material == Material.ICE) {
            return false;
        } else {
            boolean flag = isExceptBlockForAttachWithPiston(block) || block instanceof BlockStairs;
            return !flag && isBlockSolid(world, pos, side);
        }
    }
	
	@Override
	public int getQuantaValue(IBlockAccess world, BlockPos pos) {
		IBlockState state = world.getBlockState(pos);

		if (state.getBlock() instanceof BlockSwampWater && ((BlockSwampWater) state.getBlock()).isUnderwaterBlock) {
			return this.quantaPerBlock;
		}

		if (state.getBlock() == Blocks.AIR) {
			return 0;
		}

		if (state.getBlock() instanceof BlockSwampWater == false) {
			return -1;
		}

		int quantaRemaining = this.quantaPerBlock - state.getValue(LEVEL);
		return quantaRemaining;
	}

	@Override
	public boolean isSourceBlock(IBlockAccess world, BlockPos pos) {
		return super.isSourceBlock(world, pos);
	}

	@Override
	protected boolean canFlowInto(IBlockAccess world, BlockPos pos) {
		if (world instanceof World && !((World) world).isBlockLoaded(pos)) return false;

		if (world.isAirBlock(pos)) return true;

		IBlockState state = world.getBlockState(pos);
		if (state.getBlock() instanceof BlockSwampWater) {
			return true;
		}

		if (displacements.containsKey(state.getBlock())) {
			return displacements.get(state.getBlock());
		}

		Material material = state.getMaterial();
		if (material.blocksMovement() ||
				material == Material.WATER ||
				material == Material.LAVA ||
				material == Material.PORTAL) {
			return false;
		}

		int density = getDensity(world, pos);
		if (density == Integer.MAX_VALUE) {
			return true;
		}

		if (this.density > density) {
			return true;
		} else {
			return false;
		}
	}

	@Override
	protected void flowIntoBlock(World world, BlockPos pos, int meta) {
		if (meta < 0) return;
		if (displaceIfPossible(world, pos)) {
			world.setBlockState(pos, BlockRegistry.SWAMP_WATER.getBlockState().getBaseState().withProperty(LEVEL, meta), 3);
		}
	}

	@Override
	public void updateTick(World world, BlockPos pos, IBlockState state, Random rand) {
		int quantaRemaining = quantaPerBlock - state.getValue(LEVEL);

		//Replenishing source
		if (quantaRemaining < quantaPerBlock && !world.isAirBlock(pos.down())) {
			int adjacentSources = 0;
			if (this.isSourceBlock(world, pos.east())) adjacentSources++;
			if (this.isSourceBlock(world, pos.north())) adjacentSources++;
			if (this.isSourceBlock(world, pos.south())) adjacentSources++;
			if (this.isSourceBlock(world, pos.west())) adjacentSources++;
			if (adjacentSources >= 2) {
				world.setBlockState(pos, state.withProperty(LEVEL, 0), 2);
				quantaRemaining = quantaPerBlock;
			}
		}

		int expQuanta = -101;

		if (!(state.getBlock() instanceof BlockSwampWater && ((BlockSwampWater) state.getBlock()).isUnderwaterBlock)) {
			// check adjacent block levels if non-source
			if (quantaRemaining < quantaPerBlock) {
				if (world.getBlockState(pos.add(0, -densityDir, 0)).getBlock() == this ||
						world.getBlockState(pos.add(-1, -densityDir, 0)).getBlock() == this ||
						world.getBlockState(pos.add(1, -densityDir, 0)).getBlock() == this ||
						world.getBlockState(pos.add(0, -densityDir, -1)).getBlock() == this ||
						world.getBlockState(pos.add(0, -densityDir, 1)).getBlock() == this) {
					expQuanta = quantaPerBlock - 1;
				} else {
					int maxQuanta = -100;
					maxQuanta = getLargerQuanta(world, pos.add(-1, 0, 0), maxQuanta);
					maxQuanta = getLargerQuanta(world, pos.add(1, 0, 0), maxQuanta);
					maxQuanta = getLargerQuanta(world, pos.add(0, 0, -1), maxQuanta);
					maxQuanta = getLargerQuanta(world, pos.add(0, 0, 1), maxQuanta);

					expQuanta = maxQuanta - 1;
				}

				// decay calculation
				if (expQuanta != quantaRemaining) {
					quantaRemaining = expQuanta;

					if (expQuanta <= 0) {
						world.setBlockToAir(pos);
					} else {
						world.setBlockState(pos, state.withProperty(LEVEL, quantaPerBlock - expQuanta), 2);
						world.scheduleUpdate(pos, this, tickRate);
						world.notifyNeighborsOfStateChange(pos, this, true);
					}
				}
			}
			// This is a "source" block, set meta to zero, and send a server only update
			else if (quantaRemaining >= quantaPerBlock) {
				world.setBlockState(pos, this.getDefaultState(), 2);
			}
		}

		// Flow vertically if possible
		if (canDisplace(world, pos.up(densityDir))) {
			flowIntoBlock(world, pos.up(densityDir), 1);
			return;
		}

		// Flow outward if possible
		int flowMeta = quantaPerBlock - quantaRemaining + 1;
		if (flowMeta >= quantaPerBlock) {
			return;
		}

		if (isSourceBlock(world, pos) || !isFlowingVertically(world, pos)) {
			if (world.getBlockState(pos.down(densityDir)).getBlock() instanceof BlockSwampWater) {
				flowMeta = 1;
			}
			boolean flowTo[] = getOptimalFlowDirections(world, pos);

			if (flowTo[0]) flowIntoBlock(world, pos.add(-1, 0, 0), flowMeta);
			if (flowTo[1]) flowIntoBlock(world, pos.add(1, 0, 0), flowMeta);
			if (flowTo[2]) flowIntoBlock(world, pos.add(0, 0, -1), flowMeta);
			if (flowTo[3]) flowIntoBlock(world, pos.add(0, 0, 1), flowMeta);
		}
	}

	@Override
	@SideOnly(Side.CLIENT)
	public AxisAlignedBB getSelectedBoundingBox(IBlockState state, World worldIn, BlockPos pos) {
		if (state.getBlock() instanceof BlockSwampWater && ((BlockSwampWater) state.getBlock()).isUnderwaterBlock)
			return state.getBoundingBox(worldIn, pos).offset(pos);
		return null;
	}

	@Override
	public AxisAlignedBB getCollisionBoundingBox(@Nonnull IBlockState blockState, @Nonnull IBlockAccess worldIn, @Nonnull BlockPos pos) {
		if (blockState.getBlock() instanceof BlockSwampWater && ((BlockSwampWater) blockState.getBlock()).isUnderwaterBlock)
			return blockState.getBoundingBox(worldIn, pos);
		return null;
	}

	@Override
	public boolean canCollideCheck(IBlockState state, boolean fullHit) {
		return state.getBlock() instanceof BlockSwampWater && ((BlockSwampWater) state.getBlock()).isUnderwaterBlock || super.canCollideCheck(state, fullHit);
	}

	@Override
	public boolean isReplaceable(IBlockAccess worldIn, BlockPos pos) {
		IBlockState state = worldIn.getBlockState(pos);
		return !(state.getBlock() instanceof BlockSwampWater) || !((BlockSwampWater) state.getBlock()).isUnderwaterBlock && super.isReplaceable(worldIn, pos);
	}

	@Override
	@SideOnly(Side.CLIENT)
	public void setStateMapper(AdvancedStateMap.Builder builder) {
		builder.ignore(BlockSwampWater.LEVEL);
	}

	@Override
	public boolean canPlaceBlockAt(World worldIn, BlockPos pos) {
		return super.canPlaceBlockAt(worldIn, pos) && (!this.isUnderwaterBlock || worldIn.getBlockState(pos).getMaterial() == Material.WATER);
	}

	@Override
	public int getColorMultiplier(IBlockState state, IBlockAccess worldIn, BlockPos pos, int tintIndex) {
		if (worldIn == null || pos == null || tintIndex != 0) {
			return -1;
		}

		int r = 0;
		int g = 0;
		int b = 0;
		for (int dx = -1; dx <= 1; dx++) {
			for (int dz = -1; dz <= 1; dz++) {
				int colorMultiplier = worldIn.getBiome(pos.add(dx, 0, dz)).getWaterColorMultiplier();
				r += (colorMultiplier & 0xFF0000) >> 16;
			g += (colorMultiplier & 0x00FF00) >> 8;
		b += colorMultiplier & 0x0000FF;
			}
		}
		r /= 9;
		g /= 9;
		b /= 9;
		float depth = 0;
		if (pos.getY() > WorldProviderBetweenlands.CAVE_START) {
			depth = 1;
		} else {
			if (pos.getY() < WorldProviderBetweenlands.CAVE_WATER_HEIGHT) {
				depth = 0;
			} else {
				depth = (pos.getY() - WorldProviderBetweenlands.CAVE_WATER_HEIGHT) / (float) (WorldProviderBetweenlands.CAVE_START - WorldProviderBetweenlands.CAVE_WATER_HEIGHT);
			}
		}
		r = (int) (r * depth + DEEP_COLOR_R * (1 - depth) + 0.5F);
		g = (int) (g * depth + DEEP_COLOR_G * (1 - depth) + 0.5F);
		b = (int) (b * depth + DEEP_COLOR_B * (1 - depth) + 0.5F);
		return r << 16 | g << 8 | b;
	}

	@Override
	@SideOnly(Side.CLIENT)
	public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random rand) {
		if (rand.nextInt(1500) == 0) {
			if (world.getBlockState(pos.up(2)).getMaterial().isLiquid()) {
				BLParticles.FISH.spawn(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D);
			} else if (world.getBlockState(pos.down()).getBlock() == BlockRegistry.MUD) {
				if (rand.nextInt(2) == 0) {
					BLParticles.MOSQUITO.spawn(world, pos.getX() + 0.5D, pos.getY() + 1.5D, pos.getZ() + 0.5D);
				} else {
					BLParticles.FLY.spawn(world, pos.getX() + 0.5D, pos.getY() + 1.5D, pos.getZ() + 0.5D);
				}
			}
		}
	}

	@Override
	public boolean shouldSideBeRendered(@Nonnull IBlockState state, @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side) {
		if(this.isUnderwaterBlock) {
			IBlockState neighbor = world.getBlockState(pos.offset(side));
			if (neighbor.getMaterial() == state.getMaterial()) {
				return false;
			}
			if(this.densityDir == -1 && side == EnumFacing.UP) {
				return true;
			}
			if(this.densityDir == 1 && side == EnumFacing.DOWN) {
				return true;
			}

			//Ignore AABB check, only render sides if there's no block that actually blocks that side visually!
			return !world.getBlockState(pos.offset(side)).doesSideBlockRendering(world, pos.offset(side), side.getOpposite());
		}

		return super.shouldSideBeRendered(state, world, pos, side);
	}

	private boolean isBlockSolid(IBlockAccess world, BlockPos pos, EnumFacing face) {
		return world.getBlockState(pos).getBlockFaceShape(world, pos, face) == BlockFaceShape.SOLID;
	}
	
	@Override
	public BlockFaceShape getBlockFaceShape(IBlockAccess worldIn, IBlockState state, BlockPos pos, EnumFacing face) {
        return BlockFaceShape.UNDEFINED;
    }
	
	@Override
	public float getExplosionResistance(World world, BlockPos pos, Entity exploder, Explosion explosion) {
		return 100;
	}
	
	@Override
	public ItemBlock getItemBlock() {
		return null;
	}
	
	@Override
	public boolean removedByPlayer(IBlockState state, World world, BlockPos pos, EntityPlayer player, boolean willHarvest) {
		if(this.isUnderwaterBlock) {
			this.onBlockHarvested(world, pos, state, player);
			return world.setBlockState(pos, BlockRegistry.SWAMP_WATER.getDefaultState(), world.isRemote ? 11 : 3);
		} else {
			return super.removedByPlayer(state, world, pos, player, willHarvest);
		}
	}
	
	@Override
	public int place(World world, BlockPos pos, FluidStack fluidStack, boolean doPlace) {
		if(fluidStack.amount < Fluid.BUCKET_VOLUME) {
            return 0;
        }
		IBlockState state = world.getBlockState(pos);
		boolean replaceable = state.getBlock().isReplaceable(world, pos);
		boolean isUnderwaterBlock = false;
		Fluid fluid = null;
		if(state.getBlock() instanceof BlockSwampWater) {
			fluid = ((BlockSwampWater) state.getBlock()).getFluid();
			isUnderwaterBlock = ((BlockSwampWater) state.getBlock()).isUnderwaterBlock;
		}
		if(!replaceable && isUnderwaterBlock && fluid != fluidStack.getFluid()) {
			return 0;
		}
        if(doPlace && (replaceable || !isUnderwaterBlock)) {
            FluidUtil.destroyBlockOnFluidPlacement(world, pos);
            world.setBlockState(pos, this.getDefaultState(), 11);
        }
        return Fluid.BUCKET_VOLUME;
	}
	
	@Override
	public FluidStack drain(World world, BlockPos pos, boolean doDrain) {
		if(this.isUnderwaterBlock) {
			if(world.getBlockState(pos).getMaterial() == Material.WATER) {
				// allow draining water but don't remove block
				return stack.copy();
			}
			return null;
		}
		return super.drain(world, pos, doDrain);
	}
}