package io.github.bedwarsrel.utils;

import io.github.bedwarsrel.BedwarsRel;
import io.github.bedwarsrel.game.Game;
import io.github.bedwarsrel.game.Team;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemorySection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.LeatherArmorMeta;

public final class Utils {

  public static boolean checkBungeePlugin() {
    try {
      Class.forName("net.md_5.bungee.BungeeCord");
      return true;
    } catch (Exception e) {
      BedwarsRel.getInstance().getBugsnag().notify(e);
    }

    return false;
  }

  public static void createParticleInGame(Game game, String particle, Location loc) {
    try {
      Class<?> clazz = Class.forName("io.github.bedwarsrel.com."
          + BedwarsRel.getInstance().getCurrentVersion().toLowerCase() + ".ParticleSpawner");

      Method particleMethod = clazz.getDeclaredMethod("spawnParticle", List.class, String.class,
          float.class, float.class, float.class);
      particleMethod.invoke(null, game.getPlayers(), particle, (float) loc.getX(),
          (float) loc.getY(), (float) loc.getZ());
    } catch (Exception ex) {
      BedwarsRel.getInstance().getBugsnag().notify(ex);
    }
  }

  private static final void die(String foa) {
    throw new IllegalArgumentException(foa);
  }

  public static void equipArmorStand(LivingEntity armor, Team team) {
    if (!(armor instanceof ArmorStand)) {
      return;
    }

    ArmorStand stand = (ArmorStand) armor;

    // helmet
    ItemStack helmet = new ItemStack(Material.LEATHER_HELMET, 1);
    LeatherArmorMeta meta = (LeatherArmorMeta) helmet.getItemMeta();
    meta.setColor(team.getColor().getColor());
    helmet.setItemMeta(meta);

    // chestplate
    ItemStack chestplate = new ItemStack(Material.LEATHER_CHESTPLATE, 1);
    meta = (LeatherArmorMeta) chestplate.getItemMeta();
    meta.setColor(team.getColor().getColor());
    chestplate.setItemMeta(meta);

    // leggings
    ItemStack leggings = new ItemStack(Material.LEATHER_LEGGINGS, 1);
    meta = (LeatherArmorMeta) leggings.getItemMeta();
    meta.setColor(team.getColor().getColor());
    leggings.setItemMeta(meta);

    // boots
    ItemStack boots = new ItemStack(Material.LEATHER_BOOTS, 1);
    meta = (LeatherArmorMeta) boots.getItemMeta();
    meta.setColor(team.getColor().getColor());
    boots.setItemMeta(meta);

    stand.setHelmet(helmet);
    stand.setChestplate(chestplate);
    stand.setLeggings(leggings);
    stand.setBoots(boots);
  }

  public static Block getBedNeighbor(Block head) {
    if (Utils.isBedBlock(head.getRelative(BlockFace.EAST))) {
      return head.getRelative(BlockFace.EAST);
    } else if (Utils.isBedBlock(head.getRelative(BlockFace.WEST))) {
      return head.getRelative(BlockFace.WEST);
    } else if (Utils.isBedBlock(head.getRelative(BlockFace.SOUTH))) {
      return head.getRelative(BlockFace.SOUTH);
    } else {
      return head.getRelative(BlockFace.NORTH);
    }
  }

  public static BlockFace getCardinalDirection(Location location) {
    double rotation = (location.getYaw() - 90) % 360;
    if (rotation < 0) {
      rotation += 360.0;
    }
    if (0 <= rotation && rotation < 22.5) {
      return BlockFace.NORTH;
    } else if (22.5 <= rotation && rotation < 67.5) {
      return BlockFace.NORTH_EAST;
    } else if (67.5 <= rotation && rotation < 112.5) {
      return BlockFace.EAST;
    } else if (112.5 <= rotation && rotation < 157.5) {
      return BlockFace.SOUTH_EAST;
    } else if (157.5 <= rotation && rotation < 202.5) {
      return BlockFace.SOUTH;
    } else if (202.5 <= rotation && rotation < 247.5) {
      return BlockFace.SOUTH_WEST;
    } else if (247.5 <= rotation && rotation < 292.5) {
      return BlockFace.WEST;
    } else if (292.5 <= rotation && rotation < 337.5) {
      return BlockFace.NORTH_WEST;
    } else if (337.5 <= rotation && rotation < 360.0) {
      return BlockFace.NORTH;
    } else {
      return BlockFace.NORTH;
    }
  }

  public static Method getColorableMethod(Material mat) {
    try {
      ItemStack tempStack = new ItemStack(mat, 1);
      Method method =
          tempStack.getItemMeta().getClass().getMethod("setColor", new Class[]{Color.class});
      if (method != null) {
        return method;
      }
    } catch (Exception ex) {
      // NO ERROR
    }

    return null;
  }

  public static Object getCraftPlayer(Player player) {
    try {
      Class<?> craftPlayerClass = BedwarsRel.getInstance()
          .getCraftBukkitClass("entity.CraftPlayer");
      Method getHandle = craftPlayerClass.getMethod("getHandle", new Class[]{});
      getHandle.setAccessible(true);

      return getHandle.invoke(player, new Object[]{});
    } catch (Exception e) {
      BedwarsRel.getInstance().getBugsnag().notify(e);
      return null;
    }
  }

  public static Location getDirectionLocation(Location location, int blockOffset) {
    Location loc = location.clone();
    return loc.add(loc.getDirection().setY(0).normalize().multiply(blockOffset));
  }

  public static String getFormattedTime(int time) {
    int hr = 0;
    int min = 0;
    int sec = 0;
    String minStr = "";
    String secStr = "";
    String hrStr = "";

    hr = (int) Math.floor((time / 60) / 60);
    min = ((int) Math.floor((time / 60)) - (hr * 60));
    sec = time % 60;

    hrStr = (hr < 10) ? "0" + String.valueOf(hr) : String.valueOf(hr);
    minStr = (min < 10) ? "0" + String.valueOf(min) : String.valueOf(min);
    secStr = (sec < 10) ? "0" + String.valueOf(sec) : String.valueOf(sec);

    return hrStr + ":" + minStr + ":" + secStr;
  }

  public static Class<?> getGenericTypeOfParameter(Class<?> clazz, String method,
      int parameterIndex) {
    try {
      Method m = clazz.getMethod(method, new Class<?>[]{Set.class, int.class});
      ParameterizedType type = (ParameterizedType) m.getGenericParameterTypes()[parameterIndex];
      return (Class<?>) type.getActualTypeArguments()[0];
    } catch (Exception e) {
      BedwarsRel.getInstance().getBugsnag().notify(e);
      try {
        Method m = clazz.getMethod(method, new Class<?>[]{HashSet.class, int.class});
        ParameterizedType type = (ParameterizedType) m.getGenericParameterTypes()[parameterIndex];
        return (Class<?>) type.getActualTypeArguments()[0];
      } catch (Exception ex) {
        BedwarsRel.getInstance().getBugsnag().notify(ex);
        ex.printStackTrace();
      }
    }

    return null;
  }

  @SuppressWarnings("deprecation")
  public static Material getMaterialByConfig(String key, Material defaultMaterial) {
    try {
      String cfg = BedwarsRel.getInstance().getStringConfig(key, defaultMaterial.name());
      if (Utils.isNumber(cfg)) {
        return Material.getMaterial(Integer.valueOf(cfg));
      } else {
        return Material.getMaterial(cfg.toUpperCase());
      }
    } catch (Exception ex) {
      BedwarsRel.getInstance().getBugsnag().notify(ex);
      // just return default
    }

    return defaultMaterial;
  }

  public static Class<?> getPrimitiveWrapper(Class<?> primitive) {
    if (!primitive.isPrimitive()) {
      return primitive;
    }

    if (primitive.getSimpleName().equals("int")) {
      return Integer.class;
    } else if (primitive.getSimpleName().equals("long")) {
      return Long.class;
    } else if (primitive.getSimpleName().equals("short")) {
      return Short.class;
    } else if (primitive.getSimpleName().equals("byte")) {
      return Byte.class;
    } else if (primitive.getSimpleName().equals("float")) {
      return Float.class;
    } else if (primitive.getSimpleName().equals("boolean")) {
      return Boolean.class;
    } else if (primitive.getSimpleName().equals("char")) {
      return Character.class;
    } else if (primitive.getSimpleName().equals("double")) {
      return Double.class;
    } else {
      return primitive;
    }
  }

  @SuppressWarnings("resource")
  public static String[] getResourceListing(Class<?> clazz, String path)
      throws URISyntaxException, IOException {
    URL dirURL = clazz.getClassLoader().getResource(path);
    if (dirURL != null && dirURL.getProtocol().equals("file")) {
      /* A file path: easy enough */
      return new File(dirURL.toURI()).list();
    }

    if (dirURL == null) {
      /*
       * In case of a jar file, we can't actually find a directory. Have to assume the same jar as
       * clazz.
       */
      String me = clazz.getName().replace(".", "/") + ".class";
      dirURL = clazz.getClassLoader().getResource(me);
    }

    if (dirURL.getProtocol().equals("jar")) {
      /* A JAR path */
      String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")); // strip
      // out
      // only
      // the
      // JAR
      // file
      JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
      Enumeration<JarEntry> entries = jar.entries(); // gives ALL entries
      // in jar
      Set<String> result = new HashSet<String>(); // avoid duplicates in
      // case it is a
      // subdirectory
      while (entries.hasMoreElements()) {
        String name = entries.nextElement().getName();
        if (name.startsWith(path)) { // filter according to the path
          String entry = name.substring(path.length());
          int checkSubdir = entry.indexOf("/");
          if (checkSubdir >= 0) {
            // if it is a subdirectory, we just return the directory
            // name
            entry = entry.substring(0, checkSubdir);
          }
          result.add(entry);
        }
      }
      return result.toArray(new String[result.size()]);
    }

    throw new UnsupportedOperationException("Cannot list files for URL " + dirURL);
  }

  public static String implode(String glue, ArrayList<String> strings) {
    if (strings.isEmpty()) {
      return "";
    }

    StringBuilder builder = new StringBuilder();
    builder.append(strings.remove(0));

    for (String str : strings) {
      builder.append(glue);
      builder.append(str);
    }

    return builder.toString();
  }

  public static boolean isBedBlock(Block isBed) {
    if (isBed == null) {
      return false;
    }

    return (isBed.getType() == Material.BED || isBed.getType() == Material.BED_BLOCK);
  }

  public static boolean isColorable(ItemStack itemstack) {
    return (itemstack.getType().equals(Material.STAINED_CLAY)
        || itemstack.getType().equals(Material.WOOL)
        || itemstack.getType().equals(Material.CARPET)
        || itemstack.getType().equals(Material.STAINED_GLASS)
        || itemstack.getType().equals(Material.STAINED_GLASS_PANE));
  }

  public static boolean isNumber(String numberString) {
    try {
      Integer.parseInt(numberString);
      return true;
    } catch (Exception ex) {
      // NO ERROR
      return false;
    }
  }

  @SuppressWarnings("unchecked")
  public static Location locationDeserialize(Object obj) {
    if (obj instanceof Location) {
      return (Location) obj;
    }

    Map<String, Object> section = new HashMap<String, Object>();
    if (obj instanceof MemorySection) {
      MemorySection sec = (MemorySection) obj;
      for (String key : sec.getKeys(false)) {
        section.put(key, sec.get(key));
      }
    } else if (obj instanceof ConfigurationSection) {
      ConfigurationSection sec = (ConfigurationSection) obj;
      for (String key : sec.getKeys(false)) {
        section.put(key, sec.get(key));
      }
    } else {
      section = (Map<String, Object>) obj;
    }

    try {
      if (section == null) {
        return null;
      }

      double x = Double.valueOf(section.get("x").toString());
      double y = Double.valueOf(section.get("y").toString());
      double z = Double.valueOf(section.get("z").toString());
      float yaw = Float.valueOf(section.get("yaw").toString());
      float pitch = Float.valueOf(section.get("pitch").toString());
      World world = BedwarsRel.getInstance().getServer().getWorld(section.get("world").toString());

      if (world == null) {
        return null;
      }

      return new Location(world, x, y, z, yaw, pitch);
    } catch (Exception ex) {
      BedwarsRel.getInstance().getBugsnag().notify(ex);
      ex.printStackTrace();
    }

    return null;
  }

  @SuppressWarnings("unchecked")
  public static Location locationDeserialize(String key, FileConfiguration config) {
    if (!config.contains(key)) {
      return null;
    }

    Object locSec = config.get(key);
    if (locSec instanceof Location) {
      return (Location) locSec;
    }

    try {
      Map<String, Object> section = (Map<String, Object>) config.get(key);
      if (section == null) {
        return null;
      }

      double x = Double.valueOf(section.get("x").toString());
      double y = Double.valueOf(section.get("y").toString());
      double z = Double.valueOf(section.get("z").toString());
      float yaw = Float.valueOf(section.get("yaw").toString());
      float pitch = Float.valueOf(section.get("pitch").toString());
      World world = BedwarsRel.getInstance().getServer().getWorld(section.get("world").toString());

      if (world == null) {
        return null;
      }

      return new Location(world, x, y, z, yaw, pitch);
    } catch (Exception ex) {
      BedwarsRel.getInstance().getBugsnag().notify(ex);
      ex.printStackTrace();
    }

    return null;
  }

  public static Map<String, Object> locationSerialize(Location location) {
    Map<String, Object> section = new HashMap<String, Object>();
    section.put("x", location.getX());
    section.put("y", location.getY());
    section.put("z", location.getZ());
    section.put("pitch", (double) location.getPitch());
    section.put("yaw", (double) location.getYaw());
    section.put("world", location.getWorld().getName());

    return section;
  }

  @SuppressWarnings("deprecation")
  public static Material parseMaterial(String material) {
    try {
      if (Utils.isNumber(material)) {
        return Material.getMaterial(Integer.parseInt(material));
      } else {
        return Material.getMaterial(material.toUpperCase());
      }
    } catch (Exception ex) {
      BedwarsRel.getInstance().getBugsnag().notify(ex);
      // failed to parse
    }

    return null;
  }

  public static int randInt(int min, int max) {
    Random rand = new Random();
    int randomNum = rand.nextInt((max - min) + 1) + min;

    return randomNum;
  }

  public final static String unescape_perl_string(String oldstr) {

    /*
     * In contrast to fixing Java's broken regex charclasses, this one need be no bigger, as
     * unescaping shrinks the string here, where in the other one, it grows it.
     */

    StringBuffer newstr = new StringBuffer(oldstr.length());

    boolean saw_backslash = false;

    for (int i = 0; i < oldstr.length(); i++) {
      int cp = oldstr.codePointAt(i);
      if (oldstr.codePointAt(i) > Character.MAX_VALUE) {
        i++;
        /**** WE HATES UTF-16! WE HATES IT FOREVERSES!!! ****/
      }

      if (!saw_backslash) {
        if (cp == '\\') {
          saw_backslash = true;
        } else {
          newstr.append(Character.toChars(cp));
        }
        continue; /* switch */
      }

      if (cp == '\\') {
        saw_backslash = false;
        newstr.append('\\');
        newstr.append('\\');
        continue; /* switch */
      }

      switch (cp) {

        case 'r':
          newstr.append('\r');
          break; /* switch */

        case 'n':
          newstr.append('\n');
          break; /* switch */

        case 'f':
          newstr.append('\f');
          break; /* switch */

        /* PASS a \b THROUGH!! */
        case 'b':
          newstr.append("\\b");
          break; /* switch */

        case 't':
          newstr.append('\t');
          break; /* switch */

        case 'a':
          newstr.append('\007');
          break; /* switch */

        case 'e':
          newstr.append('\033');
          break; /* switch */

        /*
         * A "control" character is what you get when you xor its codepoint with '@'==64. This only
         * makes sense for ASCII, and may not yield a "control" character after all.
         *
         * Strange but true: "\c{" is ";", "\c}" is "=", etc.
         */
        case 'c': {
          if (++i == oldstr.length()) {
            die("trailing \\c");
          }
          cp = oldstr.codePointAt(i);
          /*
           * don't need to grok surrogates, as next line blows them up
           */
          if (cp > 0x7f) {
            die("expected ASCII after \\c");
          }
          newstr.append(Character.toChars(cp ^ 64));
          break; /* switch */
        }

        case '8':
        case '9':
          die("illegal octal digit");
          /* NOTREACHED */

          /*
           * may be 0 to 2 octal digits following this one so back up one for fallthrough to next
           * case; unread this digit and fall through to next case.
           */
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
          --i;
          /* FALLTHROUGH */

          /*
           * Can have 0, 1, or 2 octal digits following a 0 this permits larger values than octal
           * 377, up to octal 777.
           */
        case '0': {
          if (i + 1 == oldstr.length()) {
            /* found \0 at end of string */
            newstr.append(Character.toChars(0));
            break; /* switch */
          }
          i++;
          int digits = 0;
          int j;
          for (j = 0; j <= 2; j++) {
            if (i + j == oldstr.length()) {
              break; /* for */
            }
            /* safe because will unread surrogate */
            int ch = oldstr.charAt(i + j);
            if (ch < '0' || ch > '7') {
              break; /* for */
            }
            digits++;
          }
          if (digits == 0) {
            --i;
            newstr.append('\0');
            break; /* switch */
          }
          int value = 0;
          try {
            value = Integer.parseInt(oldstr.substring(i, i + digits), 8);
          } catch (NumberFormatException nfe) {
            die("invalid octal value for \\0 escape");
          }
          newstr.append(Character.toChars(value));
          i += digits - 1;
          break; /* switch */
        } /* end case '0' */

        case 'x': {
          if (i + 2 > oldstr.length()) {
            die("string too short for \\x escape");
          }
          i++;
          boolean saw_brace = false;
          if (oldstr.charAt(i) == '{') {
            /* ^^^^^^ ok to ignore surrogates here */
            i++;
            saw_brace = true;
          }
          int j;
          for (j = 0; j < 8; j++) {

            if (!saw_brace && j == 2) {
              break; /* for */
            }

            /*
             * ASCII test also catches surrogates
             */
            int ch = oldstr.charAt(i + j);
            if (ch > 127) {
              die("illegal non-ASCII hex digit in \\x escape");
            }

            if (saw_brace && ch == '}') {
              break; /* for */
            }

            if (!((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f')
                || (ch >= 'A' && ch <= 'F'))) {
              die(String.format("illegal hex digit #%d '%c' in \\x", ch, ch));
            }

          }
          if (j == 0) {
            die("empty braces in \\x{} escape");
          }
          int value = 0;
          try {
            value = Integer.parseInt(oldstr.substring(i, i + j), 16);
          } catch (NumberFormatException nfe) {
            die("invalid hex value for \\x escape");
          }
          newstr.append(Character.toChars(value));
          if (saw_brace) {
            j++;
          }
          i += j - 1;
          break; /* switch */
        }

        case 'u': {
          if (i + 4 > oldstr.length()) {
            die("string too short for \\u escape");
          }
          i++;
          int j;
          for (j = 0; j < 4; j++) {
            /* this also handles the surrogate issue */
            if (oldstr.charAt(i + j) > 127) {
              die("illegal non-ASCII hex digit in \\u escape");
            }
          }
          int value = 0;
          try {
            value = Integer.parseInt(oldstr.substring(i, i + j), 16);
          } catch (NumberFormatException nfe) {
            die("invalid hex value for \\u escape");
          }
          newstr.append(Character.toChars(value));
          i += j - 1;
          break; /* switch */
        }

        case 'U': {
          if (i + 8 > oldstr.length()) {
            die("string too short for \\U escape");
          }
          i++;
          int j;
          for (j = 0; j < 8; j++) {
            /* this also handles the surrogate issue */
            if (oldstr.charAt(i + j) > 127) {
              die("illegal non-ASCII hex digit in \\U escape");
            }
          }
          int value = 0;
          try {
            value = Integer.parseInt(oldstr.substring(i, i + j), 16);
          } catch (NumberFormatException nfe) {
            die("invalid hex value for \\U escape");
          }
          newstr.append(Character.toChars(value));
          i += j - 1;
          break; /* switch */
        }

        default:
          newstr.append('\\');
          newstr.append(Character.toChars(cp));
          /*
           * say(String.format( "DEFAULT unrecognized escape %c passed through", cp));
           */
          break; /* switch */

      }
      saw_backslash = false;
    }

    /* weird to leave one at the end */
    if (saw_backslash) {
      newstr.append('\\');
    }

    return newstr.toString();
  }

  /*
   * Return a string "U+XX.XXX.XXXX" etc, where each XX set is the xdigits of the logical Unicode
   * code point. No bloody brain-damaged UTF-16 surrogate crap, just true logical characters.
   */
  public final static String uniplus(String s) {
    if (s.length() == 0) {
      return "";
    }
    /* This is just the minimum; sb will grow as needed. */
    StringBuffer sb = new StringBuffer(2 + 3 * s.length());
    sb.append("U+");
    for (int i = 0; i < s.length(); i++) {
      sb.append(String.format("%X", s.codePointAt(i)));
      if (s.codePointAt(i) > Character.MAX_VALUE) {
        i++;
        /**** WE HATES UTF-16! WE HATES IT FOREVERSES!!! ****/
      }
      if (i + 1 < s.length()) {
        sb.append(".");
      }
    }
    return sb.toString();
  }

}