package net.silentchaos512.scalinghealth.lib;

import java.util.List;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.common.util.FakePlayerFactory;
import net.silentchaos512.scalinghealth.ScalingHealth;
import net.silentchaos512.scalinghealth.config.ConfigScalingHealth;
import net.silentchaos512.scalinghealth.utils.SHPlayerDataHandler;
import net.silentchaos512.scalinghealth.utils.SHPlayerDataHandler.PlayerData;

public enum EnumAreaDifficultyMode {

  WEIGHTED_AVERAGE,
  AVERAGE,
  MIN_LEVEL,
  MAX_LEVEL,
  DISTANCE_FROM_SPAWN,
  DISTANCE_FROM_ORIGIN,
  DISTANCE_AND_TIME;

  public static EnumAreaDifficultyMode loadFromConfig(Configuration c,
      EnumAreaDifficultyMode defaultValue) {

    String[] validValues = new String[values().length];
    for (int i = 0; i < values().length; ++i)
      validValues[i] = values()[i].name();

    //@formatter:off
    String str = c.getString("Area Mode", ConfigScalingHealth.CAT_DIFFICULTY,
        defaultValue.name(),
        "Defines how the area difficulty is determined when spawning a mob.\n"
        + "  AVERAGE - The average difficulty level of all nearby players.\n"
        + "  WEIGHTED_AVERAGE - Similar to average, but closer players have a greater impact on difficulty.\n"
        + "  MIN_LEVEL - The lowest difficulty level of all nearby players.\n"
        + "  MAX_LEVEL - The highest difficulty level of all nearby players.\n"
        + "  DISTANCE_FROM_SPAWN - Based on the mob's distance from spawn.\n"
        + "  DISTANCE_FROM_ORIGIN - Based on the mob's distance from the origin.\n"
        + "  DISTANCE_AND_TIME - Mix of DISTANCE_FROM_SPAWN and WEIGHTED_AVERAGE.",
        validValues);
    //@formatter:on

    for (EnumAreaDifficultyMode mode : values())
      if (mode.name().equalsIgnoreCase(str))
        return mode;
    return defaultValue;
  }

  public double getAreaDifficulty(World world, BlockPos pos) {

    return getAreaDifficulty(world, pos, true);
  }

  public double getAreaDifficulty(World world, BlockPos pos, boolean addGroupBonus) {

    // Get players in range. TODO: Only get player list for types that need it?
    int radius = ConfigScalingHealth.DIFFICULTY_SEARCH_RADIUS;
    final long radiusSquared = radius <= 0 ? Long.MAX_VALUE : radius * radius;
    radius = radius <= 0 ? Integer.MAX_VALUE : radius;
    List<EntityPlayer> players = world.getPlayers(EntityPlayer.class,
        p -> p.getDistanceSq(pos) <= radiusSquared);

    if (players.size() == 0)
      return 0;

    double total = 0;
    double ret = 0;
    BlockPos origin = BlockPos.ORIGIN;

    switch (this) {
      case WEIGHTED_AVERAGE:
        int totalWeight = 0;
        for (EntityPlayer player : players) {
          PlayerData data = SHPlayerDataHandler.get(player);
          if (data != null) {
            int distance = (int) pos.getDistance((int) player.posX, pos.getY(), (int) player.posZ);
            int weight = (int) (radius - distance) / 16 + 1;

            total += weight * data.getDifficulty();
            totalWeight += weight;
          }
        }
        ret = totalWeight <= 0 ? 0 : total / totalWeight;
        break;

      case AVERAGE:
        for (EntityPlayer player : players) {
          PlayerData data = SHPlayerDataHandler.get(player);
          if (data != null)
            total += data.getDifficulty();
        }
        ret = total / players.size();
        break;

      case MAX_LEVEL:
        double max = 0;
        for (EntityPlayer player : players) {
          PlayerData data = SHPlayerDataHandler.get(player);
          if (data != null) {
            double d = data.getDifficulty();
            max = Math.max(d, max);
          }
        }
        ret = max;
        break;

      case MIN_LEVEL:
        double min = ConfigScalingHealth.DIFFICULTY_MAX;
        for (EntityPlayer player : players) {
          PlayerData data = SHPlayerDataHandler.get(player);
          if (data != null) {
            double d = data.getDifficulty();
            min = Math.min(d, min);
          }
        }
        ret = min;
        break;

      case DISTANCE_FROM_SPAWN:
        origin = world.getSpawnPoint();
      case DISTANCE_FROM_ORIGIN:
        int dx = pos.getX() - origin.getX();
        int dz = pos.getZ() - origin.getZ();
        double distance = Math.sqrt(dx * dx + dz * dz);
        ret = distance * ConfigScalingHealth.DIFFICULTY_PER_BLOCK;
        break;

      case DISTANCE_AND_TIME:
        double difficultyFromPlayers = WEIGHTED_AVERAGE.getAreaDifficulty(world, pos, false);
        double difficultyFromDistance = DISTANCE_FROM_SPAWN.getAreaDifficulty(world, pos, false);
        ret = difficultyFromPlayers + difficultyFromDistance;
        break;
    }

    // Clamp to difficulty range (intentionally done before group bonus)
    ret = ret < 0 ? 0
        : ret > ConfigScalingHealth.DIFFICULTY_MAX ? ConfigScalingHealth.DIFFICULTY_MAX : ret;

    // Group bonus?
    if (addGroupBonus)
      ret *= 1 + ConfigScalingHealth.DIFFICULTY_GROUP_AREA_BONUS * (players.size() - 1);

    return ret;
  }
}

@@ -154,16 +154,59 @@
 ockPos;%0A
+import net.minecraft.util.math.MathHelper;%0A
 import n
@@ -290,170 +290,8 @@
 on;%0A
-import net.minecraftforge.common.util.FakePlayer;%0Aimport net.minecraftforge.common.util.FakePlayerFactory;%0Aimport net.silentchaos512.scalinghealth.ScalingHealth;%0A
 impo
@@ -2116,24 +2116,205 @@
 upBonus) %7B%0A%0A
+    return getAreaDifficulty(world, pos, addGroupBonus, true);%0A  %7D%0A%0A  public double getAreaDifficulty(World world, BlockPos pos, boolean addGroupBonus,%0A      boolean clampValue) %7B%0A%0A
     // Get p
@@ -4706,30 +4706,24 @@
  double diff
-iculty
 FromPlayers 
@@ -4762,32 +4762,39 @@
 ulty(world, pos,
+ false,
  false);%0A       
@@ -4801,30 +4801,24 @@
  double diff
-iculty
 FromDistance
@@ -4865,24 +4865,31 @@
 (world, pos,
+ false,
  false);%0A   
@@ -4899,30 +4899,24 @@
   ret = diff
-iculty
 FromPlayers 
@@ -4921,22 +4921,16 @@
 s + diff
-iculty
 FromDist
@@ -5038,78 +5038,60 @@
     
-ret = ret %3C 0 ? 0%0A  
+if (clampValue)%0A
      
- :
  ret 
-%3E ConfigScalingHealth.DIFFICULTY_MAX ?
+= MathHelper.clamp(ret, 0,
  Con
@@ -5125,14 +5125,9 @@
 _MAX
- : ret
+)
 ;%0A%0A 

Fix negative difficulty per block not working (closes #75)