content: package mods.eln.transparentnode.teleporter;
  
  import mods.eln.i18n.I18N;
  import mods.eln.misc.Coordinate;
  import mods.eln.misc.Direction;
  import mods.eln.misc.LRDU;
  import mods.eln.misc.Utils;
  import mods.eln.node.NodePeriodicPublishProcess;
  import mods.eln.node.transparent.TransparentNode;
  import mods.eln.node.transparent.TransparentNodeDescriptor;
  import mods.eln.node.transparent.TransparentNodeElement;
  import mods.eln.sim.ElectricalLoad;
  import mods.eln.sim.IProcess;
  import mods.eln.sim.ThermalLoad;
  import mods.eln.sim.mna.component.Resistor;
  import mods.eln.sim.nbt.NbtElectricalLoad;
  import mods.eln.sim.process.destruct.VoltageStateWatchDog;
  import mods.eln.sim.process.destruct.WorldExplosion;
  import mods.eln.sixnode.lampsocket.LightBlockEntity;
  import mods.eln.sound.SoundCommand;
  import net.minecraft.entity.Entity;
  import net.minecraft.entity.EntityLivingBase;
  import net.minecraft.entity.item.EntityItem;
  import net.minecraft.entity.player.EntityPlayer;
  import net.minecraft.init.Blocks;
  import net.minecraft.nbt.NBTTagCompound;
  import net.minecraft.util.AxisAlignedBB;
  import org.jetbrains.annotations.NotNull;
  import org.jetbrains.annotations.Nullable;
  
  import java.io.DataInputStream;
  import java.io.DataOutputStream;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  public class TeleporterElement extends TransparentNodeElement implements ITeleporter {
  
      TeleporterDescriptor descriptor;
      NbtElectricalLoad powerLoad = new NbtElectricalLoad("powerLoad");
      Resistor powerResistor = new Resistor(powerLoad, null);
      TeleporterSlowProcess slowProcess = new TeleporterSlowProcess();
  
      NodePeriodicPublishProcess publisher;
      static public final ArrayList<ITeleporter> teleporterList = new ArrayList<ITeleporter>();
  
      public TeleporterElement(TransparentNode transparentNode,
                               TransparentNodeDescriptor descriptor) {
          super(transparentNode, descriptor);
          this.descriptor = (TeleporterDescriptor) descriptor;
          publisher = new NodePeriodicPublishProcess(node, 2, 1);
          powerLoad.isPrivateSubSystem();
          electricalLoadList.add(powerLoad);
          electricalComponentList.add(powerResistor);
          slowProcessList.add(slowProcess);
          slowProcessList.add(publisher);
  
          teleporterList.add(this);
  
          WorldExplosion exp = new WorldExplosion(this).machineExplosion();
          slowProcessList.add(voltageWatchdog.setNominalVoltage(this.descriptor.cable.electricalNominalVoltage).setDestroys(exp));
  
      }
  
      VoltageStateWatchDog voltageWatchdog = new VoltageStateWatchDog(powerLoad);
  
      Coordinate lightCoordinate;
  
      @Override
      public ElectricalLoad getElectricalLoad(Direction side, LRDU lrdu) {
  
          return powerLoad;
      }
  
      @Nullable
      @Override
      public ThermalLoad getThermalLoad(@NotNull Direction side, @NotNull LRDU lrdu) {
  
          return null;
      }
  
      @Override
      public int getConnectionMask(Direction side, LRDU lrdu) {
  
          if (side == Direction.YP || side == Direction.YN) return 0;
          if (lrdu != LRDU.Down) return 0;
          return node.maskElectricalPower;
      }
  
      @NotNull
      @Override
      public String multiMeterString(@NotNull Direction side) {
  
          return null;
      }
  
      @NotNull
      @Override
      public String thermoMeterString(@NotNull Direction side) {
  
          return null;
      }
  
      @Override
      public boolean hasGui() {
          return true;
      }
  
      @Override
      public void initialize() {
  
          descriptor.cable.applyTo(powerLoad);
  
          powerResistor.highImpedance();
  
          for (Coordinate c : descriptor.getPowerCoordonate(node.coordinate.world())) {
              TeleporterPowerNode n = new TeleporterPowerNode();
              n.setElement(this);
              c.applyTransformation(front, node.coordinate);
              n.onBlockPlacedBy(c, Direction.XN, null, null);
  
              powerNodeList.add(n);
          }
  
          lightCoordinate = new Coordinate(this.descriptor.lightCoordinate);
          lightCoordinate.applyTransformation(front, node.coordinate);
  
          descriptor.ghostDoorClose.newRotate(front).eraseGeo(node.coordinate);
          descriptor.ghostDoorOpen.newRotate(front).plot(node.coordinate, node.coordinate, descriptor.getGhostGroupUuid());
  
          connect();
      }
  
      @Override
      public void onBreakElement() {
  
          super.onBreakElement();
  
          for (TeleporterPowerNode n : powerNodeList) {
              n.onBreakBlock();
          }
          powerNodeList.clear();
          unregister();
      }
  
      ArrayList<TeleporterPowerNode> powerNodeList = new ArrayList<TeleporterPowerNode>();
  
      @Override
      public void unload() {
          super.unload();
          unregister();
      }
  
      void unregister() {
          teleporterList.remove(this);
      }
  
      @Override
      public boolean onBlockActivated(EntityPlayer player, Direction side,
                                      float vx, float vy, float vz) {
  
          return false;
      }
  
      String name = "Unnamed", targetName = "Unnamed";
      double energyHit = 0;
      double energyTarget = 0;
      double powerCharge = 2000;
  
      @Override
      public void writeToNBT(NBTTagCompound nbt) {
  
          super.writeToNBT(nbt);
  
          nbt.setString("name", name);
          nbt.setString("targetName", targetName);
          nbt.setDouble("powerCharge", powerCharge);
          nbt.setBoolean("reset", state != StateIdle);
  
      }
  
      @Override
      public void readFromNBT(NBTTagCompound nbt) {
  
          super.readFromNBT(nbt);
  
          name = nbt.getString("name");
          targetName = nbt.getString("targetName");
          powerCharge = nbt.getDouble("powerCharge");
  
          if (nbt.getBoolean("reset"))
              state = StateReset;
      }
  
      public static final byte StateIdle = 0;
      public static final byte StateStart = 1;
      public static final byte StateClose = 2;
      public static final byte StateCharge = 3;
      public static final byte StateTeleport = 4;
      public static final byte StateOpen = 5;
      public static final byte StateReserved = 6;
      public static final byte StateReset = 7;
  
      byte state = StateIdle;
      float timeCounter;
  
      boolean doorState = true;
  
      void setState(byte state) {
          if (state != this.state) {
              timeCounter = 0;
              switch (this.state) {
                  case StateCharge:
                      powerResistor.highImpedance();
                      publisher.reconfigure(2, 1);
                      energyHit = 0;
                      processRatio = 0;
                      break;
  
                  case StateReserved:
                      publisher.reconfigure(2, 1);
                      doorState = true;
                      processRatio = 0;
                      break;
                  case StateClose:
                      descriptor.ghostDoorOpen.newRotate(front).eraseGeo(node.coordinate);
                      break;
                  case StateOpen:
                      descriptor.ghostDoorClose.newRotate(front).eraseGeo(node.coordinate);
                      break;
                  default:
                      break;
              }
  
              this.state = state;
  
              switch (this.state) {
                  case StateClose:
                      doorState = false;
                      descriptor.ghostDoorClose.newRotate(front).plot(node.coordinate, node.coordinate, descriptor.getGhostGroupUuid());
                      break;
                  case StateOpen:
                      doorState = true;
                      descriptor.ghostDoorOpen.newRotate(front).plot(node.coordinate, node.coordinate, descriptor.getGhostGroupUuid());
                      break;
                  case StateCharge:
                      powerResistor.setResistance(Math.pow(descriptor.cable.electricalNominalVoltage, 2) / powerCharge);
                      publisher.reconfigure(0.4, 0);
                      break;
                  case StateReserved:
                      publisher.reconfigure(0.4, 0);
                      break;
                  default:
                      break;
              }
  
              Utils.println("Teleporter state=" + state);
              needPublish();
  
          }
      }
  
      @Override
      public boolean reservate() {
          if (state != StateIdle) return false;
          if (powerLoad.getVoltage() < descriptor.cable.electricalNominalVoltage * 0.8) return false;
  
          setState(StateReserved);
          imMaster = false;
          return true;
      }
  
      @Override
      public void reservateRefresh(boolean doorState, float processRatio) {
          reservateRefreshed = true;
          if (this.doorState == false && doorState == true) {
              setState(StateOpen);
              // setState(StateReserved);
          }
          if (this.doorState == true && doorState == false) {
              setState(StateClose);
              // setState(StateReserved);
          }
          this.processRatio = processRatio;
      }
  
      boolean reservateRefreshed = false;
  
      float processRatio = 0;
      public static final byte eventNoTargetFind = 1;
      public static final byte eventMultipleoTargetFind = 2;
      public static final byte eventTargetFind = 3;
      public static final byte eventSameTarget = 4;
      public static final byte eventNotSameDimensionTarget = 5;
      public static final byte eventTargetBusy = 6;
      public static final byte eventInstablePowerSupply = 7;
      boolean imMaster = false;
  
      class TeleporterSlowProcess implements IProcess {
  
          int dx, dy, dz;
  
          int blinkCounter = 0;
          int soundCounter = 0;
          String targetNameCopy = "";
  
          @Override
          public void process(double time) {
              {
                  ITeleporter target = getTarget(state == StateIdle ? targetName : targetNameCopy);
                  if (target == null) {
                      energyTarget = 0;
                  } else {
                      Coordinate c = getTeleportCoordonate();
                      double distance = getTeleportCoordonate().trueDistanceTo(target.getTeleportCoordonate());
                      AxisAlignedBB bb = descriptor.getBB(node.coordinate, front);
                      int playerCount = c.world().getEntitiesWithinAABB(EntityPlayer.class, bb).size();
                      int itemCount = c.world().getEntitiesWithinAABB(EntityItem.class, bb).size();
                      int petCount = c.world().getEntitiesWithinAABB(EntityLivingBase.class, bb).size() - playerCount;
                      // Object o = c.world().getEntitiesWithinAABB(EntityItem.class,bb);
                      energyTarget = 10000 +
                          40000 * playerCount +
                          5000 * petCount +
                          5000 * itemCount;
  
                      energyTarget *= 1.0 + Math.pow(distance / 250.0, 0.5);
                  }
              }
  
              if (++blinkCounter >= 9) {
                  blinkCounter = 0;
                  if ((powerLoad.getVoltage() / descriptor.cable.electricalNominalVoltage - 0.5) * 3 > Math.random())
                      LightBlockEntity.addLight(lightCoordinate, 12, 11);
              }
              switch (state) {
                  case StateReserved:
                      if (reservateRefreshed == false) {
                          if (doorState == false)
                              setState(StateOpen);
                          else
                              setState(StateIdle);
                      }
                      break;
  
                  case StateIdle:
                      imMaster = false;
                      if (startFlag) {
                          targetNameCopy = targetName;
                          energyHit = 0;
                          if (targetNameCopy.equals(name)) {
                              sendIdToAllClient(eventSameTarget);
                              break;
                          }
                          int count = getTargetCount(targetNameCopy);
                          if (count == 0) {
                              sendIdToAllClient(eventNoTargetFind);
                              break;
                          }
                          if (count > 1) {
                              sendIdToAllClient(eventMultipleoTargetFind);
                              break;
                          }
                          if (powerLoad.getVoltage() < descriptor.cable.electricalNominalVoltage * 0.8) {
                              break;
                          }
                          ITeleporter target = getTarget(targetNameCopy);
  
                          if (target.reservate() == false) {
                              sendIdToAllClient(eventTargetBusy);
                              break;
                          }
  
                          sendIdToAllClient(eventTargetFind);
  
  					/*
                       * AxisAlignedBB bb = descriptor.getBB(node.coordonate,front); List list = node.coordonate.world().getEntitiesWithinAABB(EntityItem.class, bb); for(Object o : list){ Entity e = (Entity)o; if(e instanceof EntityPlayerMP) ((EntityPlayerMP)e).setPositionAndUpdate(e.posX + dx, e.posY + dy, e.posZ + dz); else e.setPosition(e.posX + dx, e.posY + dy, e.posZ + dz); }
  					 */
                          imMaster = true;
                          setState(StateStart);
  
                      }
                      break;
  
                  case StateStart: {
                      int count = node.coordinate.world().getEntitiesWithinAABB(Entity.class, descriptor.getBB(node.coordinate, front)).size();
                      if (count == 0) {
                          timeCounter = 0;
                      } else {
                          timeCounter += time;
                          if (timeCounter > 2) {
                              setState(StateClose);
                          }
                      }
                  }
                  break;
  
                  case StateClose:
                      timeCounter += time;
                      if (timeCounter > 3) {
                          if (reservateRefreshed)
                              setState(StateReserved);
                          else {
                              setState(StateCharge);
                              soundCounter = 0;
                          }
                      }
                      break;
  
                  case StateCharge: {
                      if (soundCounter++ % 18 == 0)
                          play(new SoundCommand(descriptor.chargeSound).mulVolume(descriptor.chargeVolume, 1f));
  
                      if (targetNameCopy.equals(name)) {
                          sendIdToAllClient(eventSameTarget);
                          setState(StateOpen);
                          break;
                      }
                      int count = getTargetCount(targetNameCopy);
                      if (count == 0) {
                          sendIdToAllClient(eventNoTargetFind);
                          setState(StateOpen);
                          break;
                      }
                      if (count > 1) {
                          sendIdToAllClient(eventMultipleoTargetFind);
                          setState(StateOpen);
                          break;
                      }
                      if (powerLoad.getVoltage() < descriptor.cable.electricalNominalVoltage * 0.8) {
                          sendIdToAllClient(eventInstablePowerSupply);
                          AxisAlignedBB bb = descriptor.getBB(node.coordinate, front);
                          List list = node.coordinate.world().getEntitiesWithinAABB(Entity.class, bb);
                          for (Object o : list) {
                              Entity e = (Entity) o;
                              double failDistance = 1000;
                              while (true) {
                                  int x, y, z;
                                  x = (int) (e.posX + (Math.random() * 2 - 1) * failDistance);
                                  z = (int) (e.posZ + (Math.random() * 2 - 1) * failDistance);
                                  y = 20;
                                  while (e.worldObj.getBlock(x, y, z) != Blocks.air && e.worldObj.getBlock(x, y + 1, z) != Blocks.air) {
                                      y++;
                                  }
                                  Utils.serverTeleport(e, x + 0.5, y, z + 0.5);
                                  break;
                              }
                          }
                          setState(StateOpen);
                      } else {
                          ITeleporter target = getTarget(targetNameCopy);
                          Coordinate c = getTeleportCoordonate();
                          // double distance = getTeleportCoordonate().trueDistanceTo(c);
                          // AxisAlignedBB bb = descriptor.getBB(node.coordonate, front);
                          // int playerCount = c.world().getEntitiesWithinAABB(EntityPlayer.class, bb).size();
                          // int itemCount = c.world().getEntitiesWithinAABB(EntityItem.class, bb).size();
                          // int petCount = c.world().getEntitiesWithinAABB(EntityLivingBase.class, bb).size() - playerCount;
                          // // Object o = c.world().getEntitiesWithinAABB(EntityItem.class,bb);
                          // energyTarget = 10000 +
                          // 40000 * playerCount +
                          // 5000 * petCount +
                          // 5000 * itemCount;
                          //
                          // energyTarget *= 1.0 + Math.pow(distance / 250.0, 0.5);
  
                          energyHit += powerResistor.getPower() * time;
                          processRatio = (float) (energyHit / energyTarget);
  
                          if (energyHit >= energyTarget) {
                              dx = target.getTeleportCoordonate().x - c.x;
                              dy = target.getTeleportCoordonate().y - c.y;
                              dz = target.getTeleportCoordonate().z - c.z;
                              setState(StateTeleport);
                          }
                      }
                  }
                  break;
  
                  case StateTeleport: {
                      timeCounter += time;
                      if (timeCounter > 0) {
  
                          AxisAlignedBB bb = descriptor.getBB(node.coordinate, front);
                          List list = node.coordinate.world().getEntitiesWithinAABB(Entity.class, bb);
                          for (Object o : list) {
                              Entity e = (Entity) o;
                              Utils.serverTeleport(e, e.posX + dx, e.posY + dy, e.posZ + dz);
  
                          }
                          setState(StateOpen);
                      }
                  }
                  break;
  
                  case StateOpen:
                      timeCounter += time;
                      if (timeCounter > 3) {
                          if (reservateRefreshed)
                              setState(StateReserved);
                          else
                              setState(StateIdle);
                      }
                      break;
                  case StateReset:
                      setState(StateIdle);
                      break;
  
              }
  
              if (state != StateIdle && imMaster) {
                  ITeleporter target = getTarget(targetNameCopy);
                  if (target != null) target.reservateRefresh(doorState, processRatio);
              }
  
              reservateRefreshed = false;
  
              startFlag = false;
          }
  
      }
  
      int getTargetCount(String str) {
          int count = 0;
          for (ITeleporter t : teleporterList) {
              if (t.getName().equals(str) && node.coordinate.dimension == t.getTeleportCoordonate().dimension) {
                  count++;
              }
          }
          return count;
      }
  
      ITeleporter getTarget(String str) {
          ITeleporter target = null;
          for (ITeleporter t : teleporterList) {
              if (t.getName().equals(str) && node.coordinate.dimension == t.getTeleportCoordonate().dimension) {
                  if (target != null) return null;
                  target = t;
              }
          }
          return target;
      }
  
      boolean startFlag = false;
  
      public static final byte setNameId = 1;
      public static final byte startId = 2;
      public static final byte setTargetNameId = 3;
      public static final byte setChargePowerId = 4;
  
      @Override
      public byte networkUnserialize(DataInputStream stream) {
  
          switch (super.networkUnserialize(stream)) {
              case setNameId:
                  if (state != StateIdle) break;
                  try {
                      name = stream.readUTF();
                  } catch (IOException e) {
  
                      e.printStackTrace();
                  }
                  needPublish();
                  break;
              case setTargetNameId:
                  if (state != StateIdle) break;
                  try {
                      targetName = stream.readUTF();
                  } catch (IOException e) {
  
                      e.printStackTrace();
                  }
                  needPublish();
                  break;
              case startId:
                  startFlag = true;
                  break;
              case setChargePowerId:
                  if (state != StateIdle) break;
                  try {
                      powerCharge = stream.readFloat();
                  } catch (IOException e) {
  
                      e.printStackTrace();
                  }
                  needPublish();
                  break;
          }
          return unserializeNulldId;
      }
  
      @Override
      public void networkSerialize(DataOutputStream stream) {
  
          super.networkSerialize(stream);
          try {
              stream.writeUTF(name);
              stream.writeUTF(targetName);
              stream.writeFloat((float) powerCharge);
              stream.writeByte(state);
              stream.writeByte(
                  (doorState ? 0x1 : 0x0)
              );
              stream.writeFloat(processRatio);
              stream.writeFloat((float) powerLoad.getVoltage());
              stream.writeFloat((float) energyHit);
              stream.writeFloat((float) energyTarget);
              // stream.writeFloat((float) energyHit);
              // stream.writeFloat((float) energyTarget);
  
          } catch (IOException e) {
  
              e.printStackTrace();
          }
  
      }
  
      @Override
      public Coordinate getTeleportCoordonate() {
  
          return descriptor.getTeleportCoordonate(front, node.coordinate);
      }
  
      @Override
      public String getName() {
  
          return name;
      }
  
      @NotNull
      @Override
      public Map<String, String> getWaila() {
          Map<String, String> info = new HashMap<String, String>();
          info.put(I18N.tr("Destination"), targetName);
          info.put(I18N.tr("Distance"),
              Utils.plotValue(getTeleportCoordonate().trueDistanceTo(getTarget(targetName).getTeleportCoordonate()), "m"));
          info.put(I18N.tr("Required energy"), Utils.plotEnergy("", energyTarget));
          info.put(I18N.tr("Charge power"), Utils.plotPower("", powerCharge));
          return info;
      }
  }
  