
package com.hbm.tileentity;

import java.util.HashSet;
import java.util.Set;

import com.hbm.blocks.BlockDummyable;
import com.hbm.blocks.generic.BlockDoorGeneric;
import com.hbm.lib.Library;
import com.hbm.main.MainRegistry;
import com.hbm.render.anim.HbmAnimations.Animation;
import com.hbm.sound.AudioWrapper;
import com.hbm.tileentity.machine.TileEntityLockableBase;
import com.hbm.util.fauxpointtwelve.BlockPos;
import com.hbm.util.fauxpointtwelve.Rotation;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraftforge.common.util.ForgeDirection;

public class TileEntityDoorGeneric extends TileEntityLockableBase {

	public static final byte STATE_CLOSED =		0;
	public static final byte STATE_OPEN =		1;
	public static final byte STATE_CLOSING =	2;
	public static final byte STATE_OPENING =	3;

	//0: closed, 1: open, 2: closing, 3: opening
	public byte state = STATE_CLOSED;
	protected DoorDecl doorType;
	public int openTicks = 0;
	public long animStartTime = 0;
	public int redstonePower;
	public boolean shouldUseBB = false;
	private byte skinIndex = 0;

	public Set<BlockPos> activatedBlocks = new HashSet<>(4);

	private AudioWrapper audio;
	private AudioWrapper audio2;
	
	public Animation currentAnimation;

	@Override
	public void updateEntity() {
		
		if(getDoorType().onDoorUpdate() != null) {
			getDoorType().onDoorUpdate().accept(this);
		}
		
		if(state == STATE_OPENING) {
			openTicks++;
			if(openTicks >= getDoorType().timeToOpen()) openTicks = getDoorType().timeToOpen();
			
		} else if(state == STATE_CLOSING) {
			openTicks--;
			if(openTicks <= 0) openTicks = 0;
		}

		if(!worldObj.isRemote) {

			BlockPos pos = new BlockPos(this);

			int[][] ranges = getDoorType().getDoorOpenRanges();
			ForgeDirection dir = ForgeDirection.getOrientation(getBlockMetadata() - BlockDummyable.offset);

			if(state == STATE_OPENING) {

				for(int i = 0; i < ranges.length; i++) {

					int[] range = ranges[i];
					BlockPos startPos = new BlockPos(range[0], range[1], range[2]);
					float time = getDoorType().getDoorRangeOpenTime(openTicks, i);

					for(int j = 0; j < Math.abs(range[3]); j++) {

						if((float)j / (Math.abs(range[3] - 1)) > time)
							break;

						for(int k = 0; k < range[4]; k++) {
							BlockPos add = new BlockPos(0, 0, 0);
							switch(range[5]) {
							case 0: add = new BlockPos(0, k, (int)Math.signum(range[3]) * j); break;
							case 1: add = new BlockPos(k, (int)Math.signum(range[3]) * j, 0); break;
							case 2: add = new BlockPos((int)Math.signum(range[3]) * j, k, 0); break;
							}

							Rotation r = Rotation.getBlockRotation(dir);
							if(dir == Library.POS_X || dir == Library.NEG_X)
								r = r.add(Rotation.CLOCKWISE_180);

							BlockPos finalPos = startPos.add(add).rotate(r).add(pos);

							if(finalPos.equals(pos)) {
								this.shouldUseBB = false;
							} else {
								((BlockDummyable)getBlockType()).makeExtra(worldObj, finalPos.getX(), finalPos.getY(), finalPos.getZ());
							}
						}
					}
				}

			} else if(state == STATE_CLOSING) {

				for(int i = 0; i < ranges.length; i++) {

					int[] range = ranges[i];

					BlockPos startPos = new BlockPos(range[0], range[1], range[2]);
					float time = getDoorType().getDoorRangeOpenTime(openTicks, i);

					for(int j = Math.abs(range[3])-1; j >= 0; j--) {

						if((float)j / (Math.abs(range[3] - 1)) < time)
							break;

						for(int k = 0; k < range[4]; k++) {
							BlockPos add = new BlockPos(0, 0, 0);
							switch(range[5]) {
							case 0: add = new BlockPos(0, k, (int)Math.signum(range[3]) * j); break;
							case 1: add = new BlockPos(k, (int)Math.signum(range[3]) * j, 0); break;
							case 2: add = new BlockPos((int)Math.signum(range[3]) * j, k, 0); break;
							}

							Rotation r = Rotation.getBlockRotation(dir);
							if(dir == Library.POS_X || dir == Library.NEG_X)
								r = r.add(Rotation.CLOCKWISE_180);

							BlockPos finalPos = startPos.add(add).rotate(r).add(pos);

							if(finalPos.equals(pos)) {
								this.shouldUseBB = false;
							} else {
								((BlockDummyable)getBlockType()).removeExtra(worldObj, finalPos.getX(), finalPos.getY(), finalPos.getZ());
							}
						}
					}
				}
			}
			if(state == STATE_OPENING && openTicks == getDoorType().timeToOpen()) {
				state = STATE_OPEN;
			}
			if(state == STATE_CLOSING && openTicks == 0) {
				state = STATE_CLOSED;
			}

			this.networkPackNT(100);

			if(redstonePower == -1 && state == STATE_OPEN) {
				tryToggle(-1);
			} else if(redstonePower > 0 && state == STATE_CLOSED) {
				tryToggle(-1);
			}
			if(redstonePower == -1) {
				redstonePower = 0;
			}
		}
	}

	@Override
	public void serialize(ByteBuf buf) {
		buf.writeByte(state);
		buf.writeByte(skinIndex);
		buf.writeBoolean(shouldUseBB);
	}

	@Override
	public void deserialize(ByteBuf buf) {
		handleNewState(buf.readByte());
		skinIndex = buf.readByte();
		shouldUseBB = buf.readBoolean();
	}

	@Override
	public void onChunkUnload() {
		if(audio != null) {
			audio.stopSound();
			audio = null;
		}
		if(audio2 != null) {
			audio2.stopSound();
			audio2 = null;
		}
	}

	public DoorDecl getDoorType() {

		if(this.doorType == null && this.getBlockType() instanceof BlockDoorGeneric)
			this.doorType = ((BlockDoorGeneric)this.getBlockType()).type;

		return this.doorType;
	}

	public boolean tryToggle(EntityPlayer player) {

		if(this.isLocked() && player == null) return false;

		if(state == STATE_CLOSED && redstonePower > 0) {
			//Redstone "power locks" doors, just like minecraft iron doors
			return false;
		}
		
		if(this.state == STATE_CLOSED) {
			if(!worldObj.isRemote && canAccess(player)) this.state = STATE_OPENING;
			return true;
			
		} else if(this.state == STATE_OPEN) {
			if(!worldObj.isRemote && canAccess(player)) this.state = STATE_CLOSING;
			return true;
		}
		return false;
	}

	public boolean tryToggle(int passcode) {
		if(this.isLocked() && passcode != this.lock) return false;
		
		if(this.state == STATE_CLOSED) {
			if(!worldObj.isRemote) this.state = STATE_OPENING;
			return true;
			
		} else if(this.state == STATE_OPEN) {
			if(!worldObj.isRemote) this.state = STATE_CLOSING;
			return true;
		}
		return false;
	}

	@SideOnly(Side.CLIENT)
	public void handleNewState(byte state) {

		if(this.state != state) {
			DoorDecl doorType = getDoorType();

			if(this.state == STATE_CLOSED && state == STATE_OPENING) { // Door transitioning to open
				if(audio != null) {
					audio.stopSound();
					audio.setKeepAlive(0);
				}

				if(doorType.getOpenSoundLoop() != null) {
					audio = MainRegistry.proxy.getLoopedSound(doorType.getOpenSoundLoop(), xCoord, yCoord, zCoord, doorType.getSoundVolume(), 10F, 1F);
					audio.startSound();
				}

				if(doorType.getOpenSoundStart() != null) {
					worldObj.playSound(xCoord, yCoord, zCoord, doorType.getOpenSoundStart(), doorType.getSoundVolume(), 1F, false);
				}

				if(doorType.getSoundLoop2() != null) {
					if(audio2 != null) audio2.stopSound();

					audio2 = MainRegistry.proxy.getLoopedSound(doorType.getSoundLoop2(), xCoord, yCoord, zCoord, doorType.getSoundVolume(), 10F, 1F);
					audio2.startSound();
				}
			}

			if(this.state == STATE_OPEN && state == STATE_CLOSING) { // Door transitioning to closed
				if(audio != null) {
					audio.stopSound();
				}

				if(doorType.getCloseSoundLoop() != null) {
					audio = MainRegistry.proxy.getLoopedSound(doorType.getCloseSoundLoop(), xCoord, yCoord, zCoord, doorType.getSoundVolume(), 10F, 1F);
					audio.startSound();
				}

				if(doorType.getCloseSoundStart() != null) {
					worldObj.playSound(xCoord, yCoord, zCoord, doorType.getCloseSoundStart(), doorType.getSoundVolume(), 1F, false);
				}

				if(doorType.getSoundLoop2() != null) {
					if(audio2 != null) audio2.stopSound();

					audio2 = MainRegistry.proxy.getLoopedSound(doorType.getSoundLoop2(), xCoord, yCoord, zCoord, doorType.getSoundVolume(), 10F, 1F);
					audio2.startSound();
				}
			}

			if(state == STATE_OPEN || state == STATE_CLOSED) { // Door finished any transition
				if(audio != null) {
					audio.stopSound();
					audio = null;
				}
				if(audio2 != null) {
					audio2.stopSound();
					audio2 = null;
				}
			}

			if(this.state == STATE_OPENING && state == STATE_OPEN) { // Door finished transitioning to open
				if(doorType.getOpenSoundEnd() != null) {
					worldObj.playSound(xCoord, yCoord, zCoord, doorType.getOpenSoundEnd(), doorType.getSoundVolume(), 1F, false);
				}
			}

			if(this.state == STATE_CLOSING && state == STATE_CLOSED) { // Door finished transitioning to closed
				if(doorType.getCloseSoundEnd() != null) {
					worldObj.playSound(xCoord, yCoord, zCoord, doorType.getCloseSoundEnd(), doorType.getSoundVolume(), 1F, false);
				}
			}


			this.state = state;
			
			if(state == STATE_OPENING || state == STATE_CLOSING) {
				animStartTime = System.currentTimeMillis();
				currentAnimation = this.doorType.getSEDNAAnim(state, this.skinIndex);
			}
		}
	}

	public int getSkinIndex() {
		return skinIndex;
	}

	public boolean cycleSkinIndex() {
		if(!getDoorType().hasSkins()) return false;
		this.skinIndex++;
		this.skinIndex %= getDoorType().getSkinCount();
		this.markDirty();
		return true;
	}

	/**Useful for logic block interactions, as a way to close/open doors**/
	public void open(){
		if(state == STATE_CLOSED) state = STATE_OPENING;
	}

	public void close() {
		if(state == STATE_OPEN) state = STATE_CLOSING;
	}

	@Override
	public AxisAlignedBB getRenderBoundingBox() {
		return INFINITE_EXTENT_AABB;
	}

	@Override
	public double getMaxRenderDistanceSquared() {
		return 65536D;
	}

	@Override
	public void readFromNBT(NBTTagCompound tag) {
		this.state = tag.getByte("state");
		this.openTicks = tag.getInteger("openTicks");
		this.animStartTime = tag.getInteger("animStartTime");
		this.redstonePower = tag.getInteger("redstoned");
		this.shouldUseBB = tag.getBoolean("shouldUseBB");
		this.skinIndex = tag.getByte("skin");
		NBTTagCompound activatedBlocks = tag.getCompoundTag("activatedBlocks");
		this.activatedBlocks.clear();
		for(int i = 0; i < activatedBlocks.func_150296_c().size() / 3; i++) {
			this.activatedBlocks.add(new BlockPos(activatedBlocks.getInteger("x" + i), activatedBlocks.getInteger("y" + i), activatedBlocks.getInteger("z" + i)));
		}
		super.readFromNBT(tag);
	}

	@Override
	public void writeToNBT(NBTTagCompound tag) {
		super.writeToNBT(tag);

		tag.setByte("state", state);
		tag.setInteger("openTicks", openTicks);
		tag.setLong("animStartTime", animStartTime);
		tag.setInteger("redstoned", redstonePower);
		tag.setBoolean("shouldUseBB", shouldUseBB);
		if(getDoorType().hasSkins())
			tag.setByte("skin", skinIndex);
		NBTTagCompound activatedBlocks = new NBTTagCompound();
		int i = 0;
		for(BlockPos p : this.activatedBlocks) {
			activatedBlocks.setInteger("x" + i, p.getX());
			activatedBlocks.setInteger("y" + i, p.getY());
			activatedBlocks.setInteger("z" + i, p.getZ());
			i++;
		}
		tag.setTag("activatedBlocks", activatedBlocks);
	}

	@Override
	public void validate() {
		super.validate();
	}

	@Override
	public void invalidate() {
		super.invalidate();
		if(audio != null) {
			audio.stopSound();
			audio = null;
		}
		if(audio2 != null) {
			audio2.stopSound();
			audio2 = null;
		}
	}

	public void updateRedstonePower(int x, int y, int z) {
		//Drillgon200: Best I could come up with without having to use dummy tile entities
		BlockPos pos = new BlockPos(x, y, z);
		boolean powered = worldObj.isBlockIndirectlyGettingPowered(x, y, z);
		boolean contained = activatedBlocks.contains(pos);
		if(!contained && powered) {
			activatedBlocks.add(pos);
			if(redstonePower == -1) {
				redstonePower = 0;
			}
			redstonePower++;
		} else if(contained && !powered) {
			activatedBlocks.remove(pos);
			redstonePower--;
			if(redstonePower == 0) {
				redstonePower = -1;
			}
		}
	}

}
