/** @author Lazaro */
public class NPCXMLDefinition {
  private static final Logger logger = Logging.log();

  private static Map<Integer, NPCXMLDefinition> xmlDefinitions = null;

  public static void load() throws IOException {
    xmlDefinitions = Static.xml.readObject(Static.parseString("%WORK_DIR%/npcdefs.xml"));
    logger.info("Loaded " + xmlDefinitions.size() + " XML NPC definitions");
  }

  public static Map<Integer, NPCXMLDefinition> getXMLDefinitions() {
    return xmlDefinitions;
  }

  public int hp = -1;
  public String examine = "Nothing interesting.";
  public boolean aggressive = false;
  public int attackSpeed = 5;
  public int attackEmote = -1;
  public int defenceEmote = -1;
  public int deathEmote = -1;
  public int meleeMaxHit = 10;
  public int rangedMaxHit = 10;
  public int magicMaxHit = 10;
  public String loopScript = null;
  public String dialogueScript = null;
  public double[] bonuses = null;
  public int[] levels = null;
  public boolean poisonous = false;

  public static enum AttackType {
    MELEE(true, false, false),
    MELEE_MAGIC(true, true, false),
    MELEE_RANGE(true, false, true),
    MELEE_MAGIC_RANGE(true, true, true),
    MAGIC(false, true, false),
    MAGIC_RANGE(false, true, true),
    RANGE(false, false, true);

    public boolean usesMelee;
    public boolean usesMagic;
    public boolean usesRanged;

    private AttackType(boolean melee, boolean magic, boolean ranged) {
      this.usesMelee = melee;
      this.usesMagic = magic;
      this.usesRanged = ranged;
    }
  }

  public static enum MeleeStyle {
    SLASH,
    CRUSH,
    STAB
  }

  protected AttackType attackType = null;
  protected MeleeStyle meleeStyle = null;
  protected int aggressiveRange = 0;
  protected boolean uniqueBehaviour = false;
  protected String behaviourScript = null;
  protected boolean attackable = false;
}
public class NPCCombatHandlerTick extends Tick {

  @SuppressWarnings("unused")
  private static final Logger LOGGER = Logging.log();

  private Combat combat;

  public NPCCombatHandlerTick(Combat combat) {
    super("CombatTick", 1, TickPolicy.PERSISTENT);
    this.combat = combat;
  }

  @Override
  public boolean execute() {
    try {
      CombatAction ca = combat.scheduledAction;
      if (ca != null) {
        if (ca.victim instanceof Player)
          if (((Player) ca.victim).isDestroyed()) {
            return false;
          }
        if (ca.victim != null
            && !ca.victim.isDead()
            && ca.victim.inGame()
            && ca.attacker != null
            && !ca.attacker.isDead()
            && ca.attacker.inGame()) {
          NPC npc = (NPC) ca.attacker;
          npc.setMovementType(MovementType.CONTROLLED);
          boolean correctPosition;
          if (!(correctPosition =
              CombatUtilities.inCorrectPosition(ca.attacker, ca.victim, ca.type))) {
            if (!combat.isFrozen())
              CombatUtilities.moveToCorrectPosition(ca.attacker, ca.victim, ca.type, true);
            correctPosition =
                CombatUtilities.inCorrectFollowPosition(ca.attacker, ca.victim, ca.type);
          }
          combat.attackFromInitiateDistance = !correctPosition;
          if (correctPosition) {
            if (combat.canAttack(ca.victim)) {
              if (combat.isReadyToAttack(ca.attackDelay, ca.spellDelay, ca.type)) {
                if (correctPosition) {
                  ca.attacker.getPathProcessor().reset();
                }
                boolean succesful = ca.executeAction();
                if (succesful) {
                  switch (ca.type) {
                    case MAGIC:
                      combat.setNextSpellTime(ca.spellDelay);
                      break;
                    case MELEE:
                    case RANGED:
                      combat.setNextSpellTime(4);
                      break;
                  }
                  combat.appendAttackTime();
                  ca.victim.getCombat().appendDefenceTime();
                  combat.setNextAttackTime(ca.attackDelay);
                  combat.lastVictim = ca.victim;
                  ca.victim.getCombat().setLastAttacker(ca.attacker);
                  return true;
                } else {
                  combat.stop(false);
                  return false;
                }
              } else {
                return true;
              }
            } else {
              combat.stop(true);
              return false;
            }
          } else {
            if (npc.getDefinition().aggressiveRange
                    > npc.getLocation().distance(ca.victim.getLocation())
                || !npc.getLocation().withinRange(npc.getSpawn().location, npc.getSpawn().range)) {
              combat.stop(true);
              return false;
            }
            return true;
          }
        } else {
          combat.stop(true);
          return false;
        }
      } else {
        combat.stop(true);
        return false;
      }
    } catch (Exception e) {
      e.printStackTrace();
      combat.stop(true);
      return false;
    }
  }
}
/** @author Lazaro */
public class DatabaseLoaderAdapter implements DatabaseLoader {
  private static final Logger logger = Logging.log();

  @Override
  public void banIP(String ip) {}

  @Override
  public boolean isBanned(String userName) {
    return false;
  }

  @Override
  public boolean isIPBanned(String ip) {
    return false;
  }

  @Override
  public LoginResponse loadPlayer(String userName, String password, PlayerSave player) {
    userName = Text.formatNameForProtocol(userName);

    Pool<SQLSession> pool = Static.currentLink().getSQLPool();

    SQLSession sql = null;
    try {
      sql = pool.acquire();

      Statement st = sql.createStatement();

      ResultSet rs =
          st.executeQuery(
              "SELECT * FROM prothieu_members WHERE "
                  + "members_seo_name='"
                  + userName.replace("_", "-")
                  + "' LIMIT 1");
      if (!rs.next()) {
        return LoginResponse.INVALID_DETAILS;
      }

      if (password != null) {
        String passwordHash = rs.getString("members_pass_hash");
        String passwordSalt = rs.getString("members_pass_salt");
        if (!verifyPassword(password, passwordHash, passwordSalt)) {
          return LoginResponse.INVALID_DETAILS;
        }
      }

      int userId = rs.getInt("member_id");
      player.userId = userId;

      int[] userGroups = new int[1];
      userGroups[0] = rs.getInt("member_group_id");
      if (userGroups[0] == 5 || userGroups[0] == 1 || userGroups[0] == 2) {
        return LoginResponse.BANNED;
      }

      if (userGroups[0] == 1 || userGroups[0] == 2) {
        return LoginResponse.ERROR;
      }
      applyGroupData(player, userGroups);

      player.email = rs.getString("email");
      if (player.email != null && player.email.length() == 0) {
        player.email = null;
      }

      player.unreadMessages = rs.getInt("msg_count_new");
      rs.close();

      player.subscriptionEnd = 0;
      rs =
          st.executeQuery(
              "SELECT * FROM subscription_trans WHERE subtrans_member_id='" + userId + "'");
      while (rs.next()) {
        String state = rs.getString("subtrans_state");
        if (state.equalsIgnoreCase("paid") || state.equalsIgnoreCase("canceled")) {
          long end = rs.getLong("subtrans_end_date") * 1000;
          if (end > player.subscriptionEnd) {
            player.subscriptionEnd = end;
          }
        }
      }
      rs.close();

      // if(player.rights == 0 && player.subscriptionEnd < System.currentTimeMillis()) {
      //    return LoginResponse.MEMBERS_REQUIRED;
      // }

      for (int attempt = 0; attempt < 2; attempt++) {
        rs = st.executeQuery("SELECT * FROM playersave WHERE id='" + userId + "' LIMIT 1");
        if (!rs.next()) {
          rs.close();
          st.executeUpdate(
              "INSERT INTO playersave (id, name) VALUES ('" + userId + "', '" + userName + "')");
          continue;
        } else {
          attempt++;
        }

        player.lastLoggedIn = rs.getLong("lastLoggedIn");

        player.lastIPs = toArray(rs.getString("lastIPs"), new String[3]);
        player.isMuted = rs.getInt("muted") == 1;

        player.x = rs.getInt("x");
        player.y = rs.getInt("y");
        player.z = rs.getInt("z");

        player.runToggled = rs.getBoolean("runToggled");
        player.runEnergy = rs.getInt("runEnergy");

        player.inv = primitive(toArray(rs.getString("inv"), new Short[28]));
        player.invN = primitive(toArray(rs.getString("invN"), new Integer[28]));

        player.equip = primitive(toArray(rs.getString("equip"), new Short[14]));
        player.equipN = primitive(toArray(rs.getString("equipN"), new Integer[14]));

        String bankS = rs.getString("bank");
        if (bankS == null) {
          player.bank = new int[0][3];
        } else {
          short[] bank = primitive(toArray(bankS, new Short[0]));
          int[] bankN = primitive(toArray(rs.getString("bankN"), new Integer[0]));
          byte[] bankT = primitive(toArray(rs.getString("bankT"), new Byte[0]));

          player.bank = new int[bank.length][3];
          for (int i = 0; i < bank.length; i++) {
            int[] entry = player.bank[i];
            entry[0] = bank[i];
            entry[1] = bankN[i];
            entry[2] = bankT[i];
          }
        }
        player.level = primitive(toArray(rs.getString("level"), new Byte[Levels.SKILL_COUNT]));
        player.exp = primitive(toArray(rs.getString("exp"), new Double[Levels.SKILL_COUNT]));
        player.xpCounter = rs.getInt("xpCounter");

        player.autoRetaliate = rs.getInt("autoRetaliate") == 1;
        player.attackStyle = rs.getInt("attackStyle");

        player.prayerBook = rs.getInt("prayerBook") == 1;
        String quickPrayers = rs.getString("quickPrayers");
        if (quickPrayers.length() == 0)
          quickPrayers =
              player.prayerBook
                  ? "0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0"
                  : "0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0";
        player.quickPrayers =
            primitive(toArray(quickPrayers, new Byte[player.prayerBook ? 20 : 30]));
        player.spellBook = rs.getInt("spellBook");
        player.specialEnergy = rs.getInt("specialEnergy");
        String looks = rs.getString("looks");
        if (looks.length() == 0) looks = "310;307;443;599;390;646;438";
        player.looks = primitive(toArray(looks, new Integer[7]));
        String colours = rs.getString("colors");
        if (colours.length() == 0) colours = "6;40;216;4;0";
        player.colours = primitive(toArray(colours, new Integer[5]));
        player.isMuted = rs.getInt("muted") == 1;
        player.isSkulled = rs.getInt("skulled") == 1;
        player.skullTimer = rs.getInt("skullTimer");

        player.friends = toArray(rs.getString("friends"), new String[0]);
        player.ignores = toArray(rs.getString("ignores"), new String[0]);

        player.privateChatSetting = rs.getInt("pmSetting");
        player.privateChatColor = rs.getInt("pmColor");

        player.lastClan = rs.getString("lastClan");

        rs.close();

        Clan clan = new Clan(userName);
        if (loadClan(userName, clan, st)) {
          player.ownClan = clan;
        }

        st.close();

        return LoginResponse.LOGIN;
      }
    } catch (SQLException e) {
      logger.error("Error loading player [name=" + userName + "]", e);
    } finally {
      if (sql != null) {
        pool.release(sql);
      }
    }
    return LoginResponse.ERROR;
  }

  @Override
  public void savePlayer(String userName, boolean lobby, PlayerSave player) {
    Pool<SQLSession> pool = Static.currentLink().getSQLPool();

    SQLSession sql = null;
    try {
      sql = pool.acquire();

      StringBuilder query = new StringBuilder();
      query.append("UPDATE playersave SET");
      query.append(" lastIPs='").append(ArrayUtilities.toString(player.lastIPs)).append("'");
      query.append(", muted='").append(player.isMuted ? 1 : 0).append("'");
      query.append(", lastLoggedIn='").append(System.currentTimeMillis()).append("'");
      query.append(", friends='").append(ArrayUtilities.toString(player.friends)).append("'");
      query.append(", ignores='").append(ArrayUtilities.toString(player.ignores)).append("'");
      query.append(", pmSetting='").append(player.privateChatSetting).append("'");
      query.append(", pmColor='").append(player.privateChatColor).append("'");
      if (!lobby) {
        query.append(", x='").append(player.x).append("'");
        query.append(", y='").append(player.y).append("'");
        query.append(", z='").append(player.z).append("'");
        query.append(", runToggled='").append(player.runToggled ? 1 : 0).append("'");
        query.append(", runEnergy='").append(player.runEnergy).append("'");
        query.append(", inv='").append(ArrayUtilities.toString(player.inv)).append("'");
        query.append(", invN='").append(ArrayUtilities.toString(player.invN)).append("'");
        query.append(", equip='").append(ArrayUtilities.toString(player.equip)).append("'");
        query.append(", equipN='").append(ArrayUtilities.toString(player.equipN)).append("'");

        int[] bank = new int[player.bank.length];
        for (int i = 0; i < bank.length; i++) {
          bank[i] = player.bank[i][0];
        }
        query.append(", bank='").append(ArrayUtilities.toString(bank)).append("'");
        for (int i = 0; i < bank.length; i++) {
          bank[i] = player.bank[i][1];
        }
        query.append(", bankN='").append(ArrayUtilities.toString(bank)).append("'");
        for (int i = 0; i < bank.length; i++) {
          bank[i] = player.bank[i][2];
        }
        query.append(", bankT='").append(ArrayUtilities.toString(bank)).append("'");

        query.append(", level='").append(ArrayUtilities.toString(player.level)).append("'");
        query.append(", exp='").append(ArrayUtilities.toString(player.exp)).append("'");
        query.append(", xpCounter='").append(player.xpCounter).append("'");
        query.append(", autoRetaliate='").append(player.autoRetaliate ? 1 : 0).append("'");
        query.append(", attackStyle='").append(player.attackStyle).append("'");
        query.append(", prayerBook='").append(player.prayerBook ? 1 : 0).append("'");
        query
            .append(", quickPrayers='")
            .append(ArrayUtilities.toString(player.quickPrayers))
            .append("'");
        query.append(", spellBook='").append(player.spellBook).append("'");
        query.append(", specialEnergy='").append(player.specialEnergy).append("'");
        query.append(", looks='").append(ArrayUtilities.toString(player.looks)).append("'");
        query.append(", colors='").append(ArrayUtilities.toString(player.colours)).append("'");
        query.append(", muted='").append(player.isMuted ? 1 : 0).append("'");
        query.append(", skulled='").append(player.isSkulled ? 1 : 0).append("'");
        query.append(", skullTimer='").append(player.skullTimer).append("'");
        query.append(", lastClan=");
        if (player.lastClan == null) {
          query.append("NULL");
        } else {
          query.append("'").append(player.lastClan).append("'");
        }
      }
      query.append(" WHERE id='").append(player.userId).append("'");
      Statement st = sql.createStatement();
      st.executeUpdate(query.toString());
      st.close();
    } catch (SQLException e) {
      logger.error("Error saving player [name=" + userName + "]", e);
    } finally {
      if (sql != null) {
        pool.release(sql);
      }
    }
  }

  @Override
  public void registerClan(String owner, String name) {
    Pool<SQLSession> pool = Static.currentLink().getSQLPool();

    SQLSession sql = null;
    try {
      sql = pool.acquire();

      Statement st = sql.createStatement();
      st.executeUpdate("INSERT INTO clans (owner, name) VALUES ('" + owner + "', '" + name + "')");
      st.close();
    } catch (SQLException e) {
      logger.error("Error registering clan [owner=" + owner + "]");
    } finally {
      if (sql != null) {
        pool.release(sql);
      }
    }
  }

  @Override
  public boolean loadClan(String owner, Clan clan) {
    Pool<SQLSession> pool = Static.currentLink().getSQLPool();

    SQLSession sql = null;
    try {
      sql = pool.acquire();

      Statement st = sql.createStatement();

      boolean success = loadClan(owner, clan, st);

      st.close();

      return success;
    } catch (SQLException e) {
      logger.error("Error loading clan [owner=" + owner + "]", e);
    } finally {
      if (sql != null) {
        pool.release(sql);
      }
    }
    return false;
  }

  private boolean loadClan(String owner, Clan clan, Statement st) throws SQLException {
    ResultSet rs = st.executeQuery("SELECT * FROM clans WHERE owner='" + owner + "' LIMIT 1");
    if (!rs.next()) {
      rs.close();
      return false;
    }

    clan.setName(rs.getString("name"));

    clan.setEnabled(rs.getBoolean("enabled"));

    clan.setEnterRequirement(Rank.forValue(rs.getInt("enterReq")));
    clan.setTalkRequirement(Rank.forValue(rs.getInt("talkReq")));
    clan.setKickRequirement(Rank.forValue(rs.getInt("kickReq")));
    clan.setLootShareRequirement(Rank.forValue(rs.getInt("lootShareReq")));

    String[] rankedMembers;

    rankedMembers = toArray(rs.getString("recruits"), new String[0]);
    for (String member : rankedMembers) {
      clan.getRanks().put(member, Rank.RECRUIT);
    }
    rankedMembers = toArray(rs.getString("corporals"), new String[0]);
    for (String member : rankedMembers) {
      clan.getRanks().put(member, Rank.CORPORAL);
    }
    rankedMembers = toArray(rs.getString("sergeants"), new String[0]);
    for (String member : rankedMembers) {
      clan.getRanks().put(member, Rank.SERGEANT);
    }
    rankedMembers = toArray(rs.getString("lieutenants"), new String[0]);
    for (String member : rankedMembers) {
      clan.getRanks().put(member, Rank.LIEUTENANT);
    }
    rankedMembers = toArray(rs.getString("captains"), new String[0]);
    for (String member : rankedMembers) {
      clan.getRanks().put(member, Rank.CAPTAIN);
    }
    rankedMembers = toArray(rs.getString("generals"), new String[0]);
    for (String member : rankedMembers) {
      clan.getRanks().put(member, Rank.GENERAL);
    }

    rs.close();

    return true;
  }

  @SuppressWarnings({"rawtypes", "unchecked"})
  @Override
  public void saveClan(Clan clan) {
    Pool<SQLSession> pool = Static.currentLink().getSQLPool();

    SQLSession sql = null;
    try {
      sql = pool.acquire();

      Statement st = sql.createStatement();
      ResultSet rs =
          st.executeQuery("SELECT * FROM clans WHERE owner='" + clan.getOwner() + "' LIMIT 1");
      if (!rs.next()) {
        st.executeUpdate("INSERT INTO clans (owner) VALUES ('" + clan.getOwner() + "')");
      }
      rs.close();

      StringBuilder query = new StringBuilder();
      query.append("UPDATE clans SET");
      query.append(" name='").append(clan.getName()).append("'");
      query.append(", enabled='").append(clan.isEnabled() ? 1 : 0).append("'");
      query.append(", enterReq='").append(clan.getEnterRequirement().intValue()).append("'");
      query.append(", talkReq='").append(clan.getTalkRequirement().intValue()).append("'");
      query.append(", kickReq='").append(clan.getKickRequirement().intValue()).append("'");
      query
          .append(", lootShareReq='")
          .append(clan.getLootShareRequirement().intValue())
          .append("'");

      List[] ranks = new List[9];
      for (Map.Entry<String, Rank> ranked : clan.getRanks().entrySet()) {
        List rankList = ranks[ranked.getValue().intValue() + 1];
        if (rankList == null) {
          rankList = ranks[ranked.getValue().intValue() + 1] = new ArrayList();
        }

        rankList.add(ranked.getKey());
      }
      List curList;

      query
          .append(", recruits='")
          .append(
              (curList = ranks[Rank.RECRUIT.intValue() + 1]) != null
                  ? ArrayUtilities.toString(curList.toArray(new String[0]))
                  : "")
          .append("'");
      query
          .append(", corporals='")
          .append(
              (curList = ranks[Rank.CORPORAL.intValue() + 1]) != null
                  ? ArrayUtilities.toString(curList.toArray(new String[0]))
                  : "")
          .append("'");
      query
          .append(", sergeants='")
          .append(
              (curList = ranks[Rank.SERGEANT.intValue() + 1]) != null
                  ? ArrayUtilities.toString(curList.toArray(new String[0]))
                  : "")
          .append("'");
      query
          .append(", lieutenants='")
          .append(
              (curList = ranks[Rank.LIEUTENANT.intValue() + 1]) != null
                  ? ArrayUtilities.toString(curList.toArray(new String[0]))
                  : "")
          .append("'");
      query
          .append(", captains='")
          .append(
              (curList = ranks[Rank.CAPTAIN.intValue() + 1]) != null
                  ? ArrayUtilities.toString(curList.toArray(new String[0]))
                  : "")
          .append("'");
      query
          .append(", generals='")
          .append(
              (curList = ranks[Rank.GENERAL.intValue() + 1]) != null
                  ? ArrayUtilities.toString(curList.toArray(new String[0]))
                  : "")
          .append("'");

      query.append(" WHERE owner='").append(clan.getOwner()).append("'");
      st.executeUpdate(query.toString());
      st.close();
    } catch (SQLException e) {
      logger.error("Error saving clan [owner=" + clan + "]", e);
    } finally {
      if (sql != null) {
        pool.release(sql);
      }
    }
  }

  @Override
  public void reload() {}

  private boolean verifyPassword(
      String inputPassword, String storedPasswordHash, String storedSalt) {
    MessageDigest digest;
    try {
      digest = MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException e) {
      logger.error("MD5 is an invalid algorithm!", e);
      return false;
    }

    byte[] inputPasswordHash = digest.digest(inputPassword.getBytes());
    digest.reset();

    byte[] storedSaltHash = digest.digest(storedSalt.getBytes());
    digest.reset();

    byte[] finalHash =
        digest.digest(
            new StringBuilder()
                .append(toHexString(storedSaltHash))
                .append(toHexString(inputPasswordHash))
                .toString()
                .getBytes());

    return toHexString(finalHash).equalsIgnoreCase(storedPasswordHash);
  }

  private void applyGroupData(PlayerSave player, int[] groups) {
    for (int groupId : groups) {
      switch (groupId) {
        case 9: // Donator
          if (player.rights < 3) {
            player.rights = 3;
          }
          break;
        case 8: // In-Game Staff
        case 7: // Global Moderator
        case 20: // Trial moderator
          if (player.rights < 1) {
            player.rights = 1;
          }
          break;
        case 3:
        case 14: // ChatBox Moderator
        case 10: // GFX Designer
        case 6: // Forum Moderator
          if (player.rights > 1) {
            player.rights = 0;
          }
          break;
        case 12: // Developer
        case 11: // Owner
        case 4: // Director
          if (player.rights < 2) {
            player.rights = 2;
          }
          break;
      }
    }
  }

  private String toHexString(byte[] data) {
    StringBuffer buf = new StringBuffer();
    for (int i = 0; i < data.length; i++) {
      int halfByte = (data[i] >>> 4) & 0x0F;
      int twoHalfs = 0;
      do {
        if ((0 <= halfByte) && (halfByte <= 9)) buf.append((char) ('0' + halfByte));
        else buf.append((char) ('a' + (halfByte - 10)));
        halfByte = data[i] & 0x0F;
      } while (twoHalfs++ < 1);
    }
    return buf.toString();
  }
}