package me.dannytatom.xibalba.helpers;

import com.badlogic.ashley.core.Entity;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.MathUtils;

import java.util.Objects;

import me.dannytatom.xibalba.Main;
import me.dannytatom.xibalba.components.AttributesComponent;
import me.dannytatom.xibalba.components.BrainComponent;
import me.dannytatom.xibalba.components.EquipmentComponent;
import me.dannytatom.xibalba.components.ItemComponent;
import me.dannytatom.xibalba.components.PositionComponent;
import me.dannytatom.xibalba.components.VisualComponent;
import me.dannytatom.xibalba.utils.ComponentMappers;
import me.dannytatom.xibalba.world.Map;
import me.dannytatom.xibalba.world.MapCell;
import me.dannytatom.xibalba.world.ShadowCaster;
import me.dannytatom.xibalba.world.WorldManager;

public class EntityHelpers {
  private final ShadowCaster caster;

  public EntityHelpers() {
    caster = new ShadowCaster();
  }

  /**
   * Should this entity skip it's turn? </p> They should skip a turn if they're crippled and the
   * turn turnCounter isn't 0 or if they're stuck.
   *
   * @param entity The entity to check
   * @return If they should
   */
  public boolean shouldSkipTurn(Entity entity) {
    return (ComponentMappers.encumbered.has(entity)
        && ComponentMappers.encumbered.get(entity).turnCounter != 0)
        || (ComponentMappers.crippled.has(entity)
        && ComponentMappers.crippled.get(entity).turnCounter != 0)
        || (ComponentMappers.stuck.has(entity));
  }

  public boolean hasTrait(Entity entity, String trait) {
    return ComponentMappers.traits.get(entity).traits.contains(trait, false);
  }

  public boolean hasDefect(Entity entity, String defect) {
    return ComponentMappers.defects.get(entity).defects.contains(defect, false);
  }

  /**
   * Checks if an entity is hidden or not.
   *
   * @param entity Entity to check
   * @return Whether or not it's visible
   */
  public boolean isVisible(Entity entity) {
    PositionComponent entityPosition = ComponentMappers.position.get(entity);

    return entityPosition != null
        && !WorldManager.mapHelpers.getCell(entityPosition.pos.x, entityPosition.pos.y).hidden
        && !WorldManager.mapHelpers.getCell(entityPosition.pos.x, entityPosition.pos.y).forgotten;
  }

  /**
   * Find if an entity is near the player.
   *
   * @param entity The entity to check
   * @return Whether or not they are
   */
  public boolean isNearPlayer(Entity entity) {
    return isNear(entity, WorldManager.player);
  }

  /**
   * Whether or not the target is near the entity.
   *
   * @param entity Who's checkin'
   * @param target Who we checkin' for
   * @return Yes/no
   */
  public boolean isNear(Entity entity, Entity target) {
    PositionComponent entityPosition = ComponentMappers.position.get(entity);
    PositionComponent targetPosition = ComponentMappers.position.get(target);

    return (entityPosition.pos.x == targetPosition.pos.x - 1
        || entityPosition.pos.x == targetPosition.pos.x
        || entityPosition.pos.x == targetPosition.pos.x + 1)
        && (entityPosition.pos.y == targetPosition.pos.y - 1
        || entityPosition.pos.y == targetPosition.pos.y
        || entityPosition.pos.y == targetPosition.pos.y + 1);
  }

  /**
   * Update an entity's senses.
   *
   * @param entity The entity
   */
  public void updateSenses(Entity entity) {
    AttributesComponent attributes = ComponentMappers.attributes.get(entity);
    PositionComponent position = ComponentMappers.position.get(entity);

    float[][] fovMap = WorldManager.mapHelpers.createFovMap();

    attributes.hearingMap = caster.calculateFov(
        fovMap, (int) position.pos.x, (int) position.pos.y, attributes.hearing
    );

    if (!Main.debug.fieldOfViewEnabled && ComponentMappers.player.has(entity)) {
      Map map = WorldManager.world.getCurrentMap();
      float[][] visionMap = new float[map.width][map.height];

      for (int x = 0; x < map.width; x++) {
        for (int y = 0; y < map.height; y++) {
          visionMap[x][y] = 1;
        }
      }

      attributes.visionMap = visionMap;
    } else {
      attributes.visionMap = caster.calculateFov(
          fovMap, (int) position.pos.x, (int) position.pos.y, attributes.vision
      );
    }
  }

  /**
   * Check if an entity can see another entity.
   *
   * @param looker Who looking
   * @param target Who they looking for
   * @return Yes/no
   */
  public boolean canSee(Entity looker, Entity target) {
    if (target == null) {
      return false;
    }

    AttributesComponent attributes = ComponentMappers.attributes.get(looker);
    PositionComponent targetPosition = ComponentMappers.position.get(target);

    int cellX = (int) targetPosition.pos.x;
    int cellY = (int) targetPosition.pos.y;

    return attributes.visionMap[cellX][cellY] > 0;
  }

  /**
   * Check if an entity can hear another entity.
   *
   * @param listener Who listening
   * @param target   Who they listening for
   * @return Yes/no
   */
  public boolean canHear(Entity listener, Entity target) {
    AttributesComponent attributes = ComponentMappers.attributes.get(listener);
    PositionComponent targetPosition = ComponentMappers.position.get(target);

    return attributes.hearingMap[(int) targetPosition.pos.x][(int) targetPosition.pos.y] > 0;
  }

  public boolean canSense(Entity entity, Entity target) {
    return canSee(entity, target) || canHear(entity, target);
  }

  /**
   * Are there any enemies in sight.
   *
   * @param entity Who's looking
   * @return Yes/no
   */
  public boolean enemyInSight(Entity entity) {
    AttributesComponent attributes = ComponentMappers.attributes.get(entity);
    float[][] vision = attributes.visionMap;

    for (int x = 0; x < vision.length; x++) {
      for (int y = 0; y < vision[x].length; y++) {
        Entity enemy = WorldManager.mapHelpers.getEnemyAt(x, y);

        if (enemy != null && canSee(entity, enemy)) {
          return true;
        }
      }
    }

    return false;
  }

  public boolean isAquatic(Entity entity) {
    BrainComponent brain = ComponentMappers.brain.get(entity);
    return brain != null && brain.dna.contains(BrainComponent.Dna.AQUATIC, false);
  }

  /**
   * Entity's toughness + armor defense.
   *
   * @param entity Who we're getting defense of
   * @return Their combined defense
   */
  int getCombinedDefense(Entity entity) {
    AttributesComponent attributes = ComponentMappers.attributes.get(entity);

    return MathUtils.random(1, attributes.toughness) + getArmorDefense(entity);
  }

  /**
   * Just armor defense.
   *
   * @param entity Who we're getting defense of
   * @return Their armor defense
   */
  public int getArmorDefense(Entity entity) {
    EquipmentComponent equipment = ComponentMappers.equipment.get(entity);

    int defense = 0;

    if (equipment != null) {
      for (java.util.Map.Entry<String, Entity> entry : equipment.slots.entrySet()) {
        String slot = entry.getKey();
        Entity item = entry.getValue();

        if (item != null) {
          ItemComponent itemDetails = ComponentMappers.item.get(item);

          if (Objects.equals(itemDetails.type, "armor") && itemDetails.location.equals(slot)) {
            defense += itemDetails.attributes.get("defense");
          }
        }
      }
    }

    return defense;
  }

  /**
   * Update an entity's position.
   *
   * @param entity The entity
   * @param cellX  x position
   * @param cellY  y position
   */
  public void updatePosition(Entity entity, float cellX, float cellY) {
    if (!ComponentMappers.position.has(entity)) {
      entity.add(new PositionComponent((int) cellX, (int) cellY));
    } else {
      ComponentMappers.position.get(entity).pos.set(cellX, cellY);
    }
  }

  /**
   * Update sprite position (called after turn is over, after all tweens etc), and update the sprite
   * color based on cell they're in.
   *
   * @param entity The entity
   * @param cellX  x position
   * @param cellY  y position
   */
  public void updateSprite(Entity entity, float cellX, float cellY) {
    VisualComponent visual = ComponentMappers.visual.get(entity);
    visual.sprite.setPosition(cellX * Main.SPRITE_WIDTH, cellY * Main.SPRITE_HEIGHT);

    MapCell cell = WorldManager.mapHelpers.getCell(cellX, cellY);

    if (cell.isWater()) {
      Color tinted = visual.color.cpy().lerp(cell.sprite.getColor(), .5f);

      if (visual.sprite.getColor() != tinted) {
        visual.sprite.setColor(tinted);
      }
    } else {
      if (visual.sprite.getColor() != visual.color) {
        visual.sprite.setColor(visual.color);
      }
    }
  }

  /**
   * Raise health.
   *
   * @param entity Entity whose health we're raising
   * @param amount How much
   */
  public void raiseHealth(Entity entity, int amount) {
    AttributesComponent attributes = ComponentMappers.attributes.get(entity);

    if (attributes.health + amount < attributes.maxHealth) {
      attributes.health += amount;
    } else {
      attributes.health = attributes.maxHealth;
    }

    if (ComponentMappers.player.has(entity)) {
      WorldManager.log.add("stats.healthRaised", "You", amount);

      ComponentMappers.player.get(entity).totalDamageHealed += amount;
    } else {
      WorldManager.log.add("stats.healthRaised", attributes.name, amount);
    }
  }

  /**
   * Take some damage.
   *
   * @param entity Who gettin' hurt
   * @param amount How much
   */
  public void takeDamage(Entity entity, int amount) {
    AttributesComponent attributes = ComponentMappers.attributes.get(entity);

    attributes.health -= amount;

    if (ComponentMappers.player.has(entity)) {
      ComponentMappers.player.get(entity).totalDamageReceived += amount;
    }
  }

  /**
   * Throw up a little bit.
   *
   * @param entity Who's vomiting
   * @param damage How much damage they take when they vomit
   */
  public void vomit(Entity entity, int damage) {
    takeDamage(entity, damage);

    PositionComponent position = ComponentMappers.position.get(entity);
    WorldManager.mapHelpers.makeFloorVomit(position.pos);
  }
}