package com.hypixel.hytale.builtin.deployables.config;

import com.hypixel.hytale.builtin.deployables.DeployablesUtils;
import com.hypixel.hytale.builtin.deployables.component.DeployableComponent;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.codec.codecs.array.ArrayCodec;
import com.hypixel.hytale.codec.validation.Validators;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause;
import com.hypixel.hytale.server.core.modules.time.TimeResource;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import javax.annotation.Nonnull;

public class DeployableTrapSpawnerConfig extends DeployableTrapConfig {
   public static final BuilderCodec<DeployableTrapSpawnerConfig> CODEC = BuilderCodec.builder(
         DeployableTrapSpawnerConfig.class, DeployableTrapSpawnerConfig::new, DeployableTrapConfig.CODEC
      )
      .appendInherited(
         new KeyedCodec<>("DeployableConfig", new ArrayCodec<>(Codec.STRING, String[]::new)),
         (o, i) -> o.deployableSpawnerIds = i,
         o -> o.deployableSpawnerIds,
         (o, p) -> o.deployableSpawnerIds = p.deployableSpawnerIds
      )
      .addValidator(Validators.nonNull())
      .add()
      .afterDecode(config -> {
         if (config.deployableSpawnerIds != null) {
            int length = config.deployableSpawnerIds.length;
            config.deployableSpawners = new DeployableSpawner[length];

            for (int i = 0; i < length; i++) {
               String key = config.deployableSpawnerIds[i];
               config.deployableSpawners[i] = DeployableSpawner.getAssetMap().getAsset(key);
            }
         }
      })
      .build();
   private String[] deployableSpawnerIds;
   private DeployableSpawner[] deployableSpawners;

   public DeployableTrapSpawnerConfig() {
   }

   @Override
   public void tick(
      @Nonnull DeployableComponent deployableComponent,
      float dt,
      int index,
      @Nonnull ArchetypeChunk<EntityStore> archetypeChunk,
      @Nonnull Store<EntityStore> store,
      @Nonnull CommandBuffer<EntityStore> commandBuffer
   ) {
      Ref<EntityStore> entityRef = archetypeChunk.getReferenceTo(index);
      switch (deployableComponent.getFlag(DeployableComponent.DeployableFlag.STATE)) {
         case 0:
            this.tickDeploymentState(store, deployableComponent, entityRef);
            break;
         case 1:
            this.tickDeployAnimationState(store, deployableComponent, entityRef);
            break;
         case 2:
            this.tickFuzeState(store, deployableComponent);
            break;
         case 3:
            this.tickLiveState(store, deployableComponent, entityRef, commandBuffer, dt);
            break;
         case 4:
            this.tickTriggeredState(commandBuffer, store, deployableComponent, entityRef);
            break;
         case 5:
            this.tickDespawnState(deployableComponent, entityRef, store);
      }
   }

   private void tickDeploymentState(@Nonnull Store<EntityStore> store, @Nonnull DeployableComponent component, @Nonnull Ref<EntityStore> entityRef) {
      component.setFlag(DeployableComponent.DeployableFlag.STATE, 1);
      playAnimation(store, entityRef, this, "Deploy");
   }

   private void tickDeployAnimationState(Store<EntityStore> store, DeployableComponent component, Ref<EntityStore> entityRef) {
      component.setFlag(DeployableComponent.DeployableFlag.STATE, 2);
      playAnimation(store, entityRef, this, "Deploy");
   }

   private void tickFuzeState(@Nonnull Store<EntityStore> store, @Nonnull DeployableComponent component) {
      Instant now = store.getResource(TimeResource.getResourceType()).getNow();
      Instant readyTime = component.getSpawnInstant().plus((long)this.fuzeDuration, ChronoUnit.SECONDS);
      if (now.isAfter(readyTime)) {
         component.setFlag(DeployableComponent.DeployableFlag.STATE, 3);
      }
   }

   private void tickLiveState(
      @Nonnull Store<EntityStore> store,
      @Nonnull DeployableComponent component,
      @Nonnull Ref<EntityStore> entityRef,
      CommandBuffer<EntityStore> commandBuffer,
      float dt
   ) {
      Vector3d position = store.getComponent(entityRef, TransformComponent.getComponentType()).getPosition();
      float radius = this.getRadius(store, component.getSpawnInstant());
      component.setTimeSinceLastAttack(component.getTimeSinceLastAttack() + dt);
      if (component.getTimeSinceLastAttack() > this.damageInterval && this.isLive(store, component)) {
         component.setTimeSinceLastAttack(0.0F);
         this.handleDetection(store, commandBuffer, entityRef, component, position, radius, DamageCause.PHYSICAL);
      }
   }

   private void tickTriggeredState(
      CommandBuffer<EntityStore> commandBuffer, @Nonnull Store<EntityStore> store, @Nonnull DeployableComponent component, @Nonnull Ref<EntityStore> entityRef
   ) {
      component.setFlag(DeployableComponent.DeployableFlag.STATE, 5);
      Vector3d parentPosition = store.getComponent(entityRef, TransformComponent.getComponentType()).getPosition();
      Ref<EntityStore> parentOwner = store.getComponent(entityRef, DeployableComponent.getComponentType()).getOwner();
      World world = store.getExternalData().getWorld();
      if (this.deployableSpawners != null) {
         for (DeployableSpawner spawner : this.deployableSpawners) {
            if (spawner != null) {
               DeployableConfig config = spawner.getConfig();
               Vector3d[] positionOffsets = spawner.getPositionOffsets();

               for (Vector3d offset : positionOffsets) {
                  Vector3f childPosition = Vector3d.add(parentPosition, offset).toVector3f();
                  world.execute(() -> DeployablesUtils.spawnDeployable(commandBuffer, store, config, parentOwner, childPosition, new Vector3f(), "UP"));
               }
            }
         }
      }
   }

   private void tickDespawnState(@Nonnull DeployableComponent component, @Nonnull Ref<EntityStore> entityRef, @Nonnull Store<EntityStore> store) {
      component.setFlag(DeployableComponent.DeployableFlag.STATE, 6);
      super.onTriggered(store, entityRef);
   }

   @Override
   protected void onTriggered(@Nonnull Store<EntityStore> store, @Nonnull Ref<EntityStore> ref) {
      store.getComponent(ref, DeployableComponent.getComponentType()).setFlag(DeployableComponent.DeployableFlag.STATE, 4);
   }
}
