Source: parser.js

/*jslint evil: true */

/** @exports parser
 * KoC Parser - To parse KoC HTML pages into JS Objects */
var parser = {};

var helpers = require('./helpers');

/**
 * Parse and return the turing from given HTML content
 * @param {String} html HTML content to be parsed
 * @return {String} Turing, if found, empty if not
 */
parser.getTuring = function (html) {
  var re = /name="turing"\s*value="([^"]+)">/gmi;
  var m = re.exec(html);
  if (m !== null)
    return m[1];
  return "";
};

parser.parseErrorMessageRed = function (html) {
  var re = /<font\s*color="red">([^<]+)/gmi;
  var m = re.exec(html);
  if (m !== null)
    return m[1];
  return "";
};

/**
 * Get the error message from a KoC error.php page
 * @param {String} html the HTML page to parse the error from
 * @return {String} the error message, if found, "Unknown Error" otherwise
 */
parser.parseErrorMessage = function (html) {
  var re = /<h3>Error<\/h3>([^<]+)</gmi;
  var m = re.exec(html);
  if (m !== null) {
    return m[1].trim();
  }
  return "Unknown Error";
};

/**
 * Get the reason for being banned from a KoC bansuspend.php page
 * @param {String} html the HTML page to parse the error from
 * @return {String} the reason, if found, "Unknown Reason" otherwise
 */
parser.parseBannedMessage = function (html) {
  var re = /Banned<\/[^>]*>\s*([^<]+)/gmi;
  var m = re.exec(html);
  if (m !== null) {
    return m[1].trim();
  }
  return "Unknown Reason";
};

/**
 * Parses a battlefield page in HTML format and returns an array of players
 * @param {String} html the content of the HTML page to parse
 * @return {Object} an object with an array of the players information found on that page
 */
parser.parseBattlefield = function (html) {
  var reLoggedIn = /<td align="center" valign="middle" style="padding: 0">\s*(.*)\s*(.*)\s*<\/td>\s*<td.*><a href="alliances\.php\?id=([^"]*)">[^<]*<\/a><\/td>\s*<td><a class="player" href="\/stats\.php\?id=([0-9]+)"\s*>([^<]+)<\/a><\/td>[^>]*>([0-9,]+)<\/td>[^>]*>\s*([A-Za-z0-9-_]+)\s*<\/td>[^>]*>([^G]+)Gold<\/td>[^>]*>([^<]+)<\/td>/gmi;
  var reLoggedOut = /<a href="alliances\.php\?id=([^"]*)">[^<]*<\/a><\/td>\s*<td><a class="player" href="\/stats\.php\?id=([0-9]+)"\s*>([^<]+)<\/a><\/td>[^>]*>([0-9,]+)<\/td>[^>]*>\s*([A-Za-z0-9-_]+)\s*<\/td>[^>]*>([^G]+)Gold<\/td>[^>]*>([^<]+)<\/td>/gmi;
  var players = [];
  var loggedIn = false;
  var m;
  [
    {
      re: reLoggedIn,
      loggedIn: true
    },
    {
      re: reLoggedOut,
      loggedIn: false
    }
  ].forEach(function (currentCase) {
      if (!players.length) { // do not check reLoggedOut if reLoggedIn worked
        while ((m = currentCase.re.exec(html)) !== null) {
          var startFrom = 1;
          var player = {};
          if (currentCase.loggedIn) {
            loggedIn = true;
            startFrom = 3;
            player.buddyStatus = helpers.parseBuddyStatus(m[1]); // Buddy Information
            player.intelFile = helpers.parseIntelFile(m[2]);   // Spy Information
          }
          player.alliance = m[startFrom];
          player.userid = m[startFrom + 1];
          player.username = m[startFrom + 2];
          player.armySizeText = m[startFrom + 3];
          player.armySize = helpers.quantityFromString(m[startFrom + 3]);
          player.race = m[startFrom + 4];
          player.goldText = m[startFrom + 5].trim();
          player.gold = helpers.quantityFromString(m[startFrom + 5], -1);
          player.rankText = m[startFrom + 6];
          player.rank = helpers.quantityFromString(m[startFrom + 6], -1);
          players.push(player);
        }
      }
    });
  // Pages
  var reTotal = />(.*) players total \| page (.*) of ([^<]+)/gmi;
  var playersTotal = 0,
    currentPage = 0,
    maxPage = 0;
  var mTotal = reTotal.exec(html);
  if (mTotal !== null) {
    playersTotal = helpers.quantityFromString(mTotal[1]);
    currentPage = helpers.quantityFromString(mTotal[2]);
    maxPage = helpers.quantityFromString(mTotal[3]);
  }
  return {
    players: players,
    playersTotal: playersTotal,
    currentPage: currentPage,
    maxPage: maxPage,
    loggedIn: loggedIn,
    success: true,
  };
};

/**
 * Get user info from the base HTML page
 * @param {String} baseHtml HTML content of the Base page
 * @return {Object} an object containing user information
 */
parser.parseBase = function (baseHtml) {
  //var re=/<tr>\s*<td>\s*<b>Name<\/b>\s*<\/td>\s*<td>\s*<a href="stats\.php\?id=([0-9]+)">([^<]+)<\/a>\s*<\/td>\s*<\/tr>/mg;
  //var re = new RegExp(createTrRegExp("Name", '<a href="stats\\.php\\?id=([0-9]+)">([^<]+)<\\/a>'), 'gm');
  try {
    var reUserInfo = new RegExp(helpers.createTableRegExp([{
      Key: "Name",
      Value: '<a href="stats\\.php\\?id=([0-9]+)">([^<]+)<\\/a>' // 1, 2
    }, {
      Key: "Race",
      Value: '([^<]+)' // 3
    }, {
      Key: "Rank",
      Value: '([^<]+)' // 4
    }, {
      Key: "Highest Rank",
      Value: '([^<]+)' // 5
    }]), 'gm');
    var matches = reUserInfo.exec(baseHtml);
    var reCommander = /Commander<\/b>\s*<\/td>\s*<td>\s*<a.*\?id=([0-9]+)">([^<]+)<\/a>/gmi;
    var mCommander = reCommander.exec(baseHtml);
    var commander = (mCommander === null) ? {
      username: '',
      userid: -1
    } : {
      username: mCommander[2],
      userid: mCommander[1]
    };
    var reMilitary = new RegExp(helpers.createTableRegExp([{
      Key: "Fortification",
      Value: '([^<]+)' // 1
    }, {
      Key: "Siege Technology",
      Value: '([^<]+)' // 2
    }, {
      Key: "Economy",
      Value: '([^<]+)' // 3
    }, {
      Key: "Technology",
      Value: '([^<]+)' // 4
    }, {
      Key: "Conscription",
      Value: '([^<]+)' // 5
    }, {
      Key: "Available Funds",
      Value: '([^<]+)' // 6
    }, {
      Key: "Projected Income",
      Value: '([^<]+)' // 7
    }, {
      Key: "Game Turns",
      Value: '([^<]+)' // 8
    }, {
      Key: "Covert Level",
      Value: '([^<]+)' // 9
    }]), 'gm');
    var matchesMilitary = reMilitary.exec(baseHtml);
    var reMilitaryEffectiveness = new RegExp(helpers.createTableTripleTdRegExp([{
      Key: "<b>Strike Action<\\/b>",
      Value1: '([^<]+)', // 3
      Value2: '([^<]+)' // 5
    }, {
      Key: "<b>Defensive Action<\\/b>",
      Value1: '([^<]+)', // 8
      Value2: '([^<]+)' // 10
    }, {
      Key: "<b>Spy Rating<\\/b>",
      Value1: '([^<]+)', // 13
      Value2: '([^<]+)' // 15
    }, {
      Key: "<b>Sentry Rating<\\/b>",
      Value1: '([^<]+)', // 18
      Value2: '([^<]+)' // 20
    }]), 'gm');
    var matchesMilitaryEffectiveness = reMilitaryEffectiveness.exec(baseHtml);
    var rePreviousLogins = new RegExp(helpers.createTableTripleTdRegExp([{
      Key: "([0-9.]+)", // 2
      Value1: '([^<]+)', // 4
      Value2: '([^<]+)' // 6
    }]), 'gm');
    var previousLogins = [];
    var matchesPreviousLogins;
    while ((matchesPreviousLogins = rePreviousLogins.exec(baseHtml)) !== null) {
      previousLogins.push({
        ip: matchesPreviousLogins[2],
        date: matchesPreviousLogins[4],
        success: matchesPreviousLogins[6]
      });
    }
    var rePersonnel = new RegExp(helpers.createTableTripleTdRegExp([{
      Key: "<b>Trained Attack Soldiers<\\/b>",
      Value1: '([^<]+)', // 3
      Value2: '([^<]+)<a([^>]+)>([^<]+)<\\/a>([^<]+)'
    }, {
      Key: "<b>Trained Attack Mercenaries<\\/b>",
      Value1: '([^<]+)', // 11
      Value2: '([^<]+)<a([^>]+)>([^<]+)<\\/a>([^<]+)'
    }, {
      Key: "<b>Trained Defense Soldiers<\\/b>",
      Value1: '([^<]+)', // 19
      Value2: '([^<]+)<a([^>]+)>([^<]+)<\\/a>([^<]+)'
    }, {
      Key: "<b>Trained Defense Mercenaries<\\/b>",
      Value1: '([^<]+)', // 27
      Value2: '([^<]+)<a([^>]+)>([^<]+)<\\/a>([^<]+)'
    }, {
      Key: "<b>Untrained Soldiers<\\/b>",
      Value1: '([^<]+)', // 35
      Value2: '([^<]+)<a([^>]+)>([^<]+)<\\/a>([^<]+)'
    }, {
      Key: "<b>Untrained Mercenaries<\\/b>",
      Value1: '([^<]+)', // 43
      Value2: '([^<]+)<a([^>]+)>([^<]+)<\\/a>([^<]+)'
    }, {
      Key: "<b>Spies<\\/b>",
      Value1: '([^<]+)', // 51
      Value2: '([^<]+)<a([^>]+)>([^<]+)<\\/a>([^<]+)'
    }, {
      Key: "<b>Sentries<\\/b>",
      Value1: '([^<]+)', // 59
      Value2: '([^<]+)<a([^>]+)>([^<]+)<\\/a>([^<]+)'
    }, {
      Key: "<b>Army Morale<\\/b>",
      Value1: '([^<]+)', // 67
      Value2: '([^<]+)<a([^>]+)>([^<]+)<\\/a>([^<]+)'
    }, {
      Key: "<b>Total Fighting Force<\\/b>",
      Value1: '([^<]+)', // 75
      Value2: '([^<]+)<a([^>]+)>([^<]+)<\\/a>([^<]+)'
    }]), 'gm');
    var matchesPersonnel = rePersonnel.exec(baseHtml);
    //if(matches!==null) this.username = matches[2];
    // Race Change
    var reRaceChangesLeft = /([0-9]+) changes left/gmi;
    var mRaceChangesLeft = reRaceChangesLeft.exec(baseHtml);
    var raceChangesLeft = (mRaceChangesLeft !== null) ? Number(mRaceChangesLeft[1]) : -1;
    // Recent Attacks
    var reRecentAttacks = /<tr>\s*<td>(.*)<\/td>\s*<td>\s*<a.*href="stats\.php\?id=([0-9]+)".*>(.*)<\/a>.*<\/td>.*<td>([^<]+)/gmi;
    var mRecentAttacks;
    var recentAttacks = [];
    while ((mRecentAttacks = reRecentAttacks.exec(baseHtml)) !== null) {
      recentAttacks.push({
        date: mRecentAttacks[1],
        enemy: {
          username: mRecentAttacks[3],
          userid: mRecentAttacks[2]
        },
        result: mRecentAttacks[4]
      });
    }
    return {
      success: true,
      userInfo: {
        userid: matches[1],
        username: matches[2],
        race: matches[3].trim(),
        rank: matches[4],
        highestRank: matches[5],
        commander: commander
      },
      militaryOverview: {
        fortification: matchesMilitary[1],
        siegeTechnology: matchesMilitary[2],
        economy: matchesMilitary[3],
        technology: matchesMilitary[4],
        conscription: matchesMilitary[5],
        availableFunds: matchesMilitary[6],
        projectedIncome: matchesMilitary[7],
        gameTurns: matchesMilitary[8].trim().split('/')[0].trim(),
        covertLevel: matchesMilitary[9]
      },
      militaryEffectiveness: {
        strikeAction: matchesMilitaryEffectiveness[3],
        strikeActionRank: matchesMilitaryEffectiveness[5].replace(/Ranked #/gm, ''),
        defensiveAction: matchesMilitaryEffectiveness[8],
        defensiveActionRank: matchesMilitaryEffectiveness[10].replace(/Ranked #/gm, ''),
        spyRating: matchesMilitaryEffectiveness[13],
        spyRatingRank: matchesMilitaryEffectiveness[15].replace(/Ranked #/gm, ''),
        sentryRating: matchesMilitaryEffectiveness[18],
        sentryRatingRank: matchesMilitaryEffectiveness[20].replace(/Ranked #/gm, '')
      },
      personnel: {
        trainedAttackSoldiers: matchesPersonnel[3],
        trainedAttackMercenaries: matchesPersonnel[11],
        trainedDefenseSoldiers: matchesPersonnel[19],
        trainedDefenseMercenaries: matchesPersonnel[27],
        untrainedSoldiers: matchesPersonnel[35],
        untrainedMercenaries: matchesPersonnel[43],
        spies: matchesPersonnel[51],
        sentries: matchesPersonnel[59],
        armyMorale: matchesPersonnel[67],
        totalFightingForce: matchesPersonnel[75]
      },
      previousLogins: previousLogins,
      raceChangesLeft: raceChangesLeft,
      recentAttacksOnYou: recentAttacks
    };
  }
  catch (e) {
    return {
      success: false,
      error: "An error occurred parsing the base: " + e.toString()
    };
  }
};

/**
 * Parse the box on the left-side of each page with the stats
 * @param {String} html the HTML content of the page to parse
 * @return {Object} an object containing gold, experience, turns, rank,
 *                  lastAttacked, mails and newMails if found
 */
parser.parseLeftSideBox = function (html) {
  var result = {};
  if (html === undefined || html === null || !html.length)
    return result;

  // Username and Fortification
  var re = /<title>Kings\s*of\s*Chaos\s*::\s*([^'<,]+)[^ ]*\s*([^<]+)<\/title>/gmi;
  var m = re.exec(html);
  result.username = (m !== null) ? m[1] : "???";
  result.fortification = (m !== null) ? m[2] : "???";

  // Gold
  re.compile(/Gold:[^>]+>\s*([^\n<]+)/gmi);
  m = re.exec(html);
  result.goldText = (m !== null) ? m[1] : "???";
  result.gold = (m !== null) ? helpers.quantityFromString(m[1], 0) : 0;
  result.success = m !== null;

  // Experience
  re.compile(/Experience:\s*<[^>]*>\s*<[^>]*>\s*<[^>]*>\s*([^\n<]+)/gmi);
  m = re.exec(html);
  result.experienceText = (m !== null) ? m[1] : "???";
  result.experience = helpers.quantityFromString(result.experienceText);

  // Turns
  re.compile(/Turns:\s*<[^>]*>\s*<[^>]*>\s*<[^>]*>\s*([^\n<]+)/gmi);
  m = re.exec(html);
  result.turns = (m !== null) ? m[1] : "???";

  // Rank
  re.compile(/Rank:\s*<[^>]*>\s*<[^>]*>\s*<[^>]*>\s*([^\n<]+)/gmi);
  m = re.exec(html);
  result.rank = (m !== null) ? m[1] : "???";

  // Last Attacked
  re.compile(/Last Attacked:\s*<[^>]+>\s*<[^>]+>([^\n<]+)/gmi);
  m = re.exec(html);
  result.lastAttacked = (m !== null) ? m[1] : "???";

  // Mails
  re.compile(/Mail:\s*<a\s*href="[^"]*"\s*style="font-size:\s*[^;]*;\s*color:\s*([^;]+);">([^\n<]+)/gmi);
  m = re.exec(html);
  result.mails = (m !== null) ? m[2] : "???";
  result.newMails = !!(m !== null && m[1] == "RED");

  return result;
};

/**
 * Get available races information
 * @param {String} html HTML content of the page to parse
 */
parser.parseRacesInfo = function (html) {
  var result = {};
  if (html === undefined || html === null || !html.length)
    return result;

  // Background colours
  var re = /<th\s*class="([^"]+)"\s*style="background-color:\s*([^;]+);\s*border:\s*([^ ]+)\s*[^;]*;">/gmi;
  var m;
  var colours = {};
  while ((m = re.exec(html)) !== null) {
    colours[m[1]] = {
      background: m[2],
      border: m[3]
    };
  }

  // Colours
  re.compile(/style="color:([^"]+)" value="Join ([^!]+)!/gmi);
  while ((m = re.exec(html)) !== null) {
    if (colours[m[2]] === undefined)
      colours[m[2]] = {};
    colours[m[2]].text = m[1];
  }

  // Images
  re.compile(/<td\s*class="([^"]+)"\s*align="[^"]+">\s*<img\s*src="([^"]+)"/gmi);
  var images = {};
  while ((m = re.exec(html)) !== null) {
    images[m[1]] = /*this.getKoCHost() +*/ m[2];
  }

  // Features
  re.compile(/<td\s*class="([^"]+)"\s*align="center"\s*>([^:]+):<br \/>\s*<b>\s*([^<]+)\s*<\/b>\s*<br \/>\s*<b>\s*([^<]+)\s*/gmi);
  var races = [];
  while ((m = re.exec(html)) !== null) {
    races.push({
      race: m[1],
      description: m[2],
      features: [
        m[3],
        m[4]
      ],
      image: images[m[1]],
      colours: colours[m[1]]
    });
  }
  return races;
};

/**
 * Get the New User Advisor content, when available
 * @param {String} html HTML content of the page to parse
 */
parser.parseNewUserAdvisor = function (html) {
  if (html === undefined || html === null || !html.length)
    return '';
  var re = /New\s*User\s*Advisor<[^>]*>\s*<[^>]*>\s*<[^>]*>\s*<[^>]*>\s*/gmi;
  var m = re.exec(html);
  if (m === null) return '';
  var startIndex = re.lastIndex;
  re = /<\/td>/gmi;
  re.lastIndex = startIndex;
  m = re.exec(html);
  if (m === null) return '';
  var endIndex = m.index;
  var rawResult = html.substring(startIndex, endIndex);
  var strippedResult = helpers.stripHtml(rawResult);
  return strippedResult.replace(/\s+/gmi, " ");
};

/**
 * Guess the age from the HTML on the page
 * @param {String} html HTML content of the page to parse
 * @return {Number} KoC Age
 */
parser.guessAge = function (html) {
  if (html === undefined || html === null || !html.length)
    return 0;
  var re = /age([0-9]+)\./gmi;
  var m = re.exec(html);
  if (m === null) return -1;
  return Number(m[1]);
};

/**
 * Parse the menu on the left side
 * @param {String} html HTML content of the page to parse
 * @return {Array} all the items in the menu (containing page, image and title)
 */
parser.parseMenu = function (html) {
  var result = [];
  if (html === undefined || html === null || !html.length)
    return result;
  var re = /<a.*href="([^"]+)">.*<img.*alt="([^"]+)".*src="([^"]+)"[^>]*.*width="137".*>/gmi;
  var m;
  while ((m = re.exec(html)) !== null) {
    result.push({
      page: m[1],
      title: m[2],
      image: m[3]
    });
  }
  return result;
};

/**
 * Get the number of times we can change the commander
 * and the statement to copy (image)
 * @return {Object} If succeeded, returns an object with 'success',
 * 'nbTimesCanChangeCommander', 'errorMessage' and 'statement' properties
 */
parser.parseCommanderChange = function (html) {
  var result = {};
  if (html === undefined || html === null || !html.length)
    return result;
  var re = /you may only do this\s*(.*)\s*times during the age/gmi;
  var m1 = re.exec(html);
  var nbTimesCanChange = (m1 === null) ? -1 : Number(helpers.stripHtml(m1[1]));
  re = /<p>\s*<font\s*color="RED"\s*>(.*)<\/font>\s*<p>/gmi;
  var m2 = re.exec(html);
  var errorMessage = (m2 === null) ? "" : m2[1];
  if (!errorMessage.length) {
    errorMessage = this.parseErrorMessage(html);
    if (errorMessage == "Unknown Error")
      errorMessage = "";
  }
  re = /action="commander_change\.php".*>\s*<img.*src="([^"]+)">/gmi;
  var m3 = re.exec(html);
  var statement = (m3 === null) ? "" : m3[1];
  return {
    success: (m1 !== null) && (m2 !== null) && (m3 !== null),
    nbTimesCanChangeCommander: nbTimesCanChange,
    errorMessage: errorMessage,
    statement: statement
  };
};

/**
 * Parse the error message on a forgot pass page
 * @param {String} html the HTML page to parse
 */
parser.parseForgotPass = function (html) {
  var result = {};
  if (html === undefined || html === null || !html.length)
    return result;
  var re = /<p>\s*<font\s*color="RED"\s*>(.*)<\/font>\s*<p>/gmi;
  var m = re.exec(html);
  var errorMessage = (m === null) ? "" : m[1];
  if (!errorMessage.length) {
    errorMessage = this.parseErrorMessage(html);
    if (errorMessage == "Unknown Error")
      errorMessage = "";
  }
  return helpers.stripHtml(errorMessage);
};

parser.parseOfficers = function (html) {

};

parser.parseFullStats = function (html) {
  var result = {};
  if (html === undefined || html === null || !html.length)
    return {
      success: false,
      error: "Empty page to parse"
    };
  // Main User Stats
  var reUserStats = /Name:[\s<tdrbpa>\/\\]*<td>\s*([^\s]+)\s*<\/td>[\s<tdrbpa>\/\\]*Commander:\s*<\/b>\s*<\/td>\s*<td[^>]*>\s*(.*)\s*<\/td>[\s<tdrbpa>\/\\]*Alliances:\s*<\/b>\s*<\/td>\s*<td>\s*(.*)\s*<\/td>[\s<tdrbpa>\/\\]*Race:[\s<tdrbpa>\/\\]*<td>\s*([^<\s]+)[\s<tdrbpa>\/\\]*Rank:[\s<tdrbpa>\/\\]*<td>([^<\s]+)[\s<tdrbpa>\/\\]*Highest Rank:[\s<tdrbpa>\/\\]*([^<]+)[\s<tdrbpa>\/\\]*Army\s*Size:[\s<tdrbpa>\/\\]*([^<\s]+)[\s<tdrbpa>\/\\]*Army Morale:[\s<tdrbpa>\/\\]*([^<\s]+)/gmi;
  var mUserStats = reUserStats.exec(html);
  if (mUserStats === null)
    return {
      success: false,
      error: "Failed to parse user stats"
    };
  result.success = true;
  // Gold
  var reGold = /Treasury\s*:\s*.*\s*<td>([^<]*)/gmi;
  var mGold = reGold.exec(html);
  var goldText = (mGold !== null) ? mGold[1] : "???";
  var gold = (mGold !== null) ? helpers.quantityFromString(mGold[1], 0) : 0;
  // Fortification / Buddy Status
  var reFortBuddy = /[\s<tdrbpa>\/\\]*Fortifications\s*:[\s<tdrbpa>\/\\]*>\s*([\s<0-9a-z=>\/\\]+)\s*<\/td>[\s<a-z=>\/\\"0-9]*Buddy\s*List\s*Status\s*:[\s<a-z=>\/\\"]*>\s*([\s<0-9a-z=>\/\\]+)\s*<\/td>/gmi;
  var mFortBuddy = reFortBuddy.exec(html);
  var fortification = "",
    buddyStatus = "";
  if (mFortBuddy !== null) {
    fortification = mFortBuddy[1];
    buddyStatus = mFortBuddy[2];
  }
  // Parse the commander
  var reCommander = /id=(\d*)">([^<]*)/gmi;
  var mCommander = reCommander.exec(mUserStats[2]);
  var commanderUsername = (mCommander === null) ? mUserStats[2] : mCommander[2];
  var commanderUserid = (mCommander === null) ? -1 : mCommander[1];
  // Parse the primary alliance
  var rePrimaryAlliance = /id=(\d*)">\s*([^<]+)\s*<\/a>\s*<\/b>\s*\(\s*Primary/gmi;
  var mPrimaryAlliance = rePrimaryAlliance.exec(mUserStats[3]);
  var primaryAlliance = mPrimaryAlliance !== null ? {
    name: mPrimaryAlliance[2],
    id: mPrimaryAlliance[1]
  } : {
    name: "",
    id: -1
  };
  // Parse the alliance(s)
  var reAlliances = /id=(\d*)">([^<]*)/gmi;
  var mAlliances;
  var alliances = [];
  while ((mAlliances = reAlliances.exec(mUserStats[3])) !== null) {
    alliances.push({
      name: mAlliances[2],
      id: mAlliances[1],
      primary: mAlliances[2] == primaryAlliance.name
    });
  }
  // Officers
  var reOfficers = /<a href="stats\.php\?id=(\d+)"\s*>\s*([^<]+)\s*<\/a>\s*<\/td>\s*<td[^>]*>\s*([^<]+)\s*<\/td>\s*<td[^>]*>\s*([^<]+)\s*<\/td>\s*<td[^>]*>\s*([^<]+)\s*<\/td>/gmi;
  var mOfficers;
  var officers = [];
  while ((mOfficers = reOfficers.exec(html)) !== null) {
    officers.push({
      username: mOfficers[2],
      userid: mOfficers[1],
      rank: mOfficers[3],
      armySize: mOfficers[4],
      race: mOfficers[5].trim()
    });
  }
  // Total Officers
  var reTotalOfficers = />(.*) officers total/gmi;
  var mTotalOfficers = reTotalOfficers.exec(html);
  var totalOfficersNb = mTotalOfficers === null ? 0 : mTotalOfficers[1];
  result.user = {
    username: mUserStats[1],
    commander: {
      username: commanderUsername,
      userid: commanderUserid
    },
    race: mUserStats[4],
    rank: mUserStats[5],
    highestRank: mUserStats[6],
    armySize: mUserStats[7],
    armyMorale: mUserStats[8],
    fortification: fortification,
    buddyStatus: buddyStatus,
    gold: gold,
    goldText: goldText,
    alliances: alliances,
    primaryAlliance: primaryAlliance,
    officers: officers,
    totalOfficersNb: Number(totalOfficersNb)
  };
  return result;
};

parser.parseArmory = function (html) {
  if (html === undefined || html === null || !html.length)
    return {
      success: false,
      error: "Empty page to parse"
    };
  // Weapon Categories
  var re = /class="subh"\s*align="left"\s*>\s*([^<]+)/gmi;
  var m;
  var weaponCategories = [];
  while ((m = re.exec(html)) !== null) {
    weaponCategories.push({
      name: m[1],
      index: m.index
    });
  }
  // Current Weapons
  var reCurrentWeapons = />([^<]+)<\/td>\s*<td\s*align="right">([^<]+)<\/td>\s*<td\s*align="[rightcenter]+"\s*>([^<]+)<\/td>\s*<td\s*align="center"\s*>\s*<ta/gmi;
  var reCurrentTools = />([^<]+)<\/td>\s*<td\s*align="right">([^<]+)<\/td>\s*<td\s*align="[rightcenter]+"\s*>([^<]+)<\/td>\s*<form/gmi;
  var currentWeapons = {};
  var mCurrentWeapons;
  var getCategoryForIndex = function (index, categories) {
    var i = 1;
    while (categories[i].index < index && i < categories.length)
      i++;
    return categories[--i].name;
  };
  // Current Weapons - Sell Price
  var reSellFor = /input\s*type="input"\s*name="([^"]+).*\s*.*\s*.*\s*.*Sell for ([^"]+)/gmi;
  var mSellFor;
  var weaponsSellFor = [];
  while ((mSellFor = reSellFor.exec(html)) !== null) {
    var rePrice = /([0-9,]+)/;
    var mPrice = rePrice.exec(mSellFor[2]);
    var price = (mPrice !== null) ? Number(mPrice[1].replace(/,/g, "")) : 0;
    weaponsSellFor.push({
      inputName: mSellFor[1],
      priceText: mSellFor[2],
      price: price
    });
  }
  // Repair
  var reRepair = /onSubmit="[^"]+.*\s*.*\s*.*\s*.*name="([^"]+).*value="([^"]+).*\s*.*\s*.*\s*.*Repair for (.*) Gold/gmi;
  var mRepair;
  var repairWeapons = {};
  while ((mRepair = reRepair.exec(html)) !== null) {
    var repairWeaponId = helpers.quantityFromString(mRepair[1]);
    repairWeapons[repairWeaponId] = {
      inputName: mRepair[1],
      defaultValue: helpers.quantityFromString(mRepair[2]),
      pricePerPoint: helpers.quantityFromString(mRepair[3]),
    };
  }
  // Repair all
  var reRepairAll = /name="([^"]+).*Repair all weapons for (.*) Gold/gmi;
  var mRepairAll = reRepairAll.exec(html);
  var repairAll;
  if (mRepairAll !== null) {
    repairAll = {
      inputName: mRepairAll[1],
      priceText: mRepairAll[2],
      price: helpers.quantityFromString(mRepairAll[2]),
    };
  }
  // Aggregate
  [reCurrentWeapons, reCurrentTools].forEach(function (regex) {
    var i = 0;
    while ((mCurrentWeapons = regex.exec(html)) !== null) {
      var index = mCurrentWeapons.index;
      var category = getCategoryForIndex(index, weaponCategories);
      if (currentWeapons[category] === undefined)
        currentWeapons[category] = [];
      var sell = {};
      var weaponId = -1;
      if (i < weaponsSellFor.length) {
        sell = weaponsSellFor[i];
        weaponId = helpers.quantityFromString(sell.inputName);
      }
      var quantity = helpers.quantityFromString(mCurrentWeapons[2]);
      currentWeapons[category].push({
        name: mCurrentWeapons[1],
        weaponId: weaponId,
        quantity: quantity,
        strength: mCurrentWeapons[3],
        sell: sell,
        repair: repairWeapons[weaponId]
      });
      i++;
    }
  });
  // Get available weapons
  var reBuyWeapons = /<tr>\s*<td>([^<]+)<\/td>\s*<td\s*align="right"\s*>([^<]+)<\/td>\s*<td\s*align="right"\s*>([^<]+)<\/td>\s*<td\s*align="center"\s*>\s*<input\s*type="text"\s*name="([^"]+)"\s*value="([^"])/gmi;
  var mBuyWeapons;
  var buyWeapons = {};
  while ((mBuyWeapons = reBuyWeapons.exec(html)) !== null) {
    var index = mBuyWeapons.index;
    var category = getCategoryForIndex(index, weaponCategories);
    if (buyWeapons[category] === undefined)
      buyWeapons[category] = [];
    buyWeapons[category].push({
      name: mBuyWeapons[1],
      strengthText: mBuyWeapons[2],
      strength: helpers.quantityFromString(mBuyWeapons[2]),
      price: helpers.quantityFromString(mBuyWeapons[3]),
      priceText: mBuyWeapons[3],
      inputName: mBuyWeapons[4],
      inputValue: helpers.quantityFromString(mBuyWeapons[5]),
      weaponId: helpers.quantityFromString(mBuyWeapons[4]),
    });
  }
  // Fortification & Siege
  var reUpgrades = /<td>([^<]+)<\/td>\s*<td\s*align="center">\s*<input.*value="([^"]+)[^>]*>\s*.*\s*.*name="([^"]+)".*value="([^"]+)/gmi;
  var mUpgrades;
  var upgrades = [];
  while ((mUpgrades = reUpgrades.exec(html)) !== null) {
    upgrades.push({
      current: mUpgrades[1],
      next: mUpgrades[2],
      inputName: mUpgrades[3],
      inputValue: mUpgrades[4]
    });
  }
  if (upgrades.length == 2) {
    upgrades = {
      fortification: upgrades[0],
      siegeTechnology: upgrades[1]
    };
  }
  // Military Effectiveness
  var reMilitaryEffectiveness = /<td><b>([^<]+)<\/b><\/td>\s*<td\s*align="right">([^<]+)<\/td>\s*<td\s*align="right">([^<]+)/gmi;
  var mMilitaryEffectiveness;
  var militaryEffectiveness = [];
  while ((mMilitaryEffectiveness = reMilitaryEffectiveness.exec(html)) !== null) {
    militaryEffectiveness.push({
      name: mMilitaryEffectiveness[1],
      value: mMilitaryEffectiveness[2],
      rank: mMilitaryEffectiveness[3]
    });
  }
  var personnel = this.parsePersonnel(html);
  return {
    currentWeapons: currentWeapons,
    buyWeapons: buyWeapons,
    upgrades: upgrades,
    militaryEffectiveness: militaryEffectiveness,
    personnel: personnel,
    repairAll: repairAll,
    turing: this.getTuring(html),
    error: this.parseErrorMessageRed(html),
  };
};

parser.parsePersonnel = function (html) {
  if (html === undefined || html === null || !html.length)
    return {
      success: false,
      error: "Empty page to parse"
    };
  // Personnel
  var rePersonnel = /<td.*><b>([^<]+)<\/b><\/td>\s*<td.*>([^<]+)<\/td>\s*<\//gmi;
  var mPersonnel;
  var personnel = {};
  while ((mPersonnel = rePersonnel.exec(html)) !== null) {
    personnel[mPersonnel[1]] = Number(mPersonnel[2]);
  }
  return personnel;
};

parser.parseTraining = function (html) {
  if (html === undefined || html === null || !html.length)
    return {
      success: false,
      error: "Empty page to parse"
    };
  var personnel = this.parsePersonnel(html);
  var reTrain = /<td>([^<]+)<\/td>\s*<td\s*align="right">([^<]+)<\/td>\s*.*name="([^"]+).*value="([^"]+)/gmi;
  var mTrain;
  var train = [];
  while ((mTrain = reTrain.exec(html)) !== null) {
    train.push({
      trainingProgram: mTrain[1],
      costPerUnitText: mTrain[2],
      costPerUnit: helpers.quantityFromString(mTrain[2]),
      inputName: mTrain[3],
      inputValue: helpers.quantityFromString(mTrain[4]),
    });
  }
  var reUpgrades = [
    /<th colspan=2 align="center">([^<]+)<[A-Za-z<>\s0-9"=\/]+<th\s*class="subh"\s*align="[^"]+"\s*>([^<]+)[A-Za-z<>\s0-9"=\/]+<th\s*class="subh"\s*>([A-Za-z\s<>\/\(\)0-9]+)<\/th>[A-Za-z<>\s0-9"=\/]+<td>([^<]+)[A-Za-z<>\s0-9"=\/]+<input\s*type="submit"\s*value="([^"]+)"[A-Za-z<>\s0-9"=\/]+<input\s*type="hidden"\s*name="([^"]+)"\s*value="([^"]+)/gmi,
    /<th colspan\s*=\s*[0-9"]+ align="center">([^<]+)<[A-Za-z<>\s0-9"=\/]+<th\s*class="subh"\s*align="[^"]+"\s*>([^<]+)[A-Za-z<>\s0-9"=\/]+<th\s*class="subh"\s*>[A-Za-z<>\s0-9"=\/]+<input\s*type="submit"\s*value="([^(]+)\(([^)]+)\)"[A-Za-z<>\s0-9"=\/]+<input\s*type="hidden"\s*name="([^"]+)"\s*value="([^"]+)[A-Za-z<>\s0-9"=\/]+<tr>\s*<td\s*colspan\s*=\s*[^>]+>([^<]+)/gmi,
  ];
  var mUpgrades;
  var upgrades = [];
  reUpgrades.forEach(function (reUpgrade) {
    while ((mUpgrades = reUpgrade.exec(html)) !== null) {
      if (mUpgrades[1].indexOf("Technological") < 0) {
        upgrades.push({
          title: mUpgrades[1],
          currentTitle: mUpgrades[2],
          upgradeName: helpers.stripHtml(mUpgrades[3]).replace(/\s+/g, ' ').trim(),
          currentLevel: mUpgrades[4],
          cost: mUpgrades[5],
          inputName: mUpgrades[6],
          inputValue: mUpgrades[7],
        });
      } else {
        upgrades.push({
          title: mUpgrades[1],
          currentTitle: mUpgrades[2],
          upgradeName: helpers.stripHtml(mUpgrades[3]).replace(/\s+/g, ' ').trim(),
          currentLevel: mUpgrades[7],
          cost: mUpgrades[4],
          inputName: mUpgrades[5],
          inputValue: mUpgrades[6],
        });
      }
    }
  });
  return {
    success: true,
    personnel: personnel,
    train: train,
    upgrades: upgrades,
    message: this.parseErrorMessageRed(html),
    turing: this.getTuring(html),
    // covertSkill: covertSkill,
    // conscription: conscription,
    // economicDevelopment: economicDevelopment,
    // technologicalDevelopment: technologicalDevelopment,
  };
};

parser.parseVerifyEmailError = function (html) {
  if (html === undefined || html === null || !html.length)
    return {
      success: false,
      error: "Empty page to parse"
    };
  var re = /<h2 style="color: red">([^<]+)/gmi;
  var m = re.exec(html);
  if (m !== null)
    return m[1];
  return "";
};

parser.parseMercenaries = function (html) {
  if (html === undefined || html === null || !html.length)
    return {
      success: false,
      error: "Empty page to parse"
    };
  var reMercs = /<td>([^<]+)<\/td>\s+<td align="right">([^<]+)<\/td>\s+<td align="right">([^<]+)<\/td>\s+.*name="([^"]+)".*value="([^"]+)/gmi;
  var mMercs;
  var hire = [];
  while ((mMercs = reMercs.exec(html)) !== null) {
    hire.push({
      mercenaryType: mMercs[1],
      costPerUnitText: mMercs[2],
      costPerUnit: helpers.quantityFromString(mMercs[2], 0),
      quantityAvailableText: mMercs[3],
      quantityAvailable: helpers.quantityFromString(mMercs[3], 0),
      inputName: mMercs[4],
      inputValue: helpers.quantityFromString(mMercs[5]),
    });
  }
  return {
    success: true,
    hire: hire,
    personnel: this.parsePersonnel(html),
    message: this.parseErrorMessageRed(html),
    turing: this.getTuring(html),
  };
};

parser.parseRecruit = function (html) {
  if (html === undefined || html === null || !html.length)
    return {
      success: false,
      error: "Empty page to parse"
    };
  // Recruiting Preference is always there
  var reRecruitPref = /input\s*type\s*=\s*"radio"\s*name\s*=\s*"([^"]+)"\s*value\s*=\s*"([^"]+)"\s*([^>]*)>([^<]+)/gmi;
  var mRecruitPref;
  var recruitPref = [];
  var recruitPrefName = "clickPref";
  while ((mRecruitPref = reRecruitPref.exec(html)) !== null) {
    recruitPrefName = mRecruitPref[1];
    recruitPref.push({
      name: mRecruitPref[4],
      value: mRecruitPref[2],
      checked: mRecruitPref[3].trim() == 'checked',
    });
  }
  // Hidden fields
  var reHidden = /input\s*type=["']*hidden['"]*\s*name="([^"]+)"\s*value="([^"]+)/gmi;
  var mHiddenFields;
  var hiddenFields = {};
  while ((mHiddenFields = reHidden.exec(html)) !== null) {
    hiddenFields[mHiddenFields[1]] = mHiddenFields[2];
  }
  if (html.indexOf("<h1>Invalid selection...</h1>") >= 0) {
    // Looks like you screwed up!
    var reChallenge = /action="recruit\.php".*\s*<script\s*type="text\/javascript"\s*src="([^"]+)"/gmi;
    var mChallenge = reChallenge.exec(html);
    var challenge_url = mChallenge[1];
    // response field
    var reChallengeFields = /textarea name="([^"]+)".*\s*<input type="hidden" name="([^"]+)/gmi;
    var mChallengeField = reChallengeFields.exec(html);
    var challengeField = mChallengeField === null ? "recaptcha_challenge_field" : mChallengeField[1];
    mChallengeField = reChallengeFields.exec(html);
    var challengeResponseField = mChallengeField === null ? "recaptcha_response_field" : mChallengeField[1];
    return {
      success: false,
      error: "Invalid Selection",
      challenge_url: challenge_url,
      hiddenFields: hiddenFields,
      recruitPreferences: recruitPref,
      recruitPreferencesInputName: recruitPrefName,
      challengeField: challengeField,
      challengeResponseField: challengeResponseField,
    };
  }
  var reRecruitJavascript = /document\.write\(\s*[']*<([^)]+)/gmi;
  var mRecruitJavascript = reRecruitJavascript.exec(html);
  if (mRecruitJavascript === null)
    return {
      success: false,
      error: "Could not parse the javascript to get the image",
    };
  var imageHtml = String(eval("String( '" + mRecruitJavascript[1] + ")"));
  var reRecruitImage = /img\s+src="([^"]+)/gmi;
  var mRecruitImage = reRecruitImage.exec(imageHtml);
  if (mRecruitImage === null)
    return {
      success: false,
      error: "Could not parse the image of the letter to type",
    };
  var image = mRecruitImage[1];
  // Army Morale
  var reMorale = /Your Army's Morale:[^0-9-+]*([0-9-+]+)/gmi;
  var mMorale = reMorale.exec(html);
  var morale = (mMorale !== null) ? mMorale[1] : "???";
  // Field name
  var reFieldName = /type="submit" value="[a-z]" name="([^"]+)/gmi;
  var mFieldName = reFieldName.exec(html);
  var fieldName = mFieldName === null ? "image_click_value" : mFieldName[1];
  return {
    success: true,
    image: image,
    morale: morale,
    hiddenFields: hiddenFields,
    recruitPreferences: recruitPref,
    recruitPreferencesInputName: recruitPrefName,
    fieldName: fieldName
  };
};

parser.parseAttackLog = function (html) {
  if (html === undefined || html === null || !html.length)
    return {
      success: false,
      error: "Empty page to parse"
    };
  var reOnYou = /<td.*>([^<]+)<\/td>\s*<td.*>([^<]+)<\/td>\s*<td.*><a\s*href="stats\.php\?id=([0-9]+)"\s*>([^<]+)<\/a><\/td>\s*<td.*>([^<]+)<\/td>\s*<td.*><a\s*href="detail\.php\?attack_id=([0-9]+)">([^<]+)<\/a><\/td>\s*<td.*>([^<]+)<\/td>\s*<td.*>([^<]+)<\/td>\s*<td.*>([^<]+)<\/td>\s*<td.*>([^<]+)<\/td>/gmi;
  var mOnYou;
  var attacksOnYou = [];
  while ((mOnYou = reOnYou.exec(html)) !== null) {
    attacksOnYou.push({
      time: mOnYou[1] + " " + mOnYou[2],
      enemy: {
        userid: mOnYou[3],
        username: mOnYou[4],
      },
      type: mOnYou[5],
      attack_id: mOnYou[6],
      result: mOnYou[7],
      enemyLosses: mOnYou[8],
      yourLosses: mOnYou[9],
      damageByEnemy: mOnYou[10],
      damageByYou: mOnYou[11],
    });
  }
  var reByYou = /<td.*>([^<]+)<\/td>\s*<td.*>([^<]+)<\/td>\s*<td.*><a\s*href="stats\.php\?id=([0-9]+)"\s*>([^<]+)<\/a><\/td>\s*<td.*><a\s*href="detail\.php\?attack_id=([0-9]+)">([^<]+)<\/a><\/td>\s*<td.*>([^<]+)<\/td>\s*<td.*>([^<]+)<\/td>\s*<td.*>([^<]+)<\/td>\s*<td.*>([^<]+)<\/td>/gmi;
  var mByYou;
  var attacksByYou = [];
  while ((mByYou = reByYou.exec(html)) !== null) {
    attacksByYou.push({
      time: mByYou[1] + " " + mByYou[2],
      enemy: {
        userid: mByYou[3],
        username: mByYou[4],
      },
      attack_id: mByYou[5],
      result: mByYou[6],
      enemyLosses: mByYou[7],
      yourLosses: mByYou[8],
      damageByEnemy: mByYou[9],
      damageByYou: mByYou[10],
    });
  }
  var reTotal = />(.*) attacks total \| page (.*) of ([^<]+)/gmi;
  var attacksOnYouTotal = 0,
    attacksByYouTotal = 0,
    attacksOnYouCurrentPage = 0,
    attacksOnYouMaxPage = 0,
    attacksByYouCurrentPage = 0,
    attacksByYouMaxPage = 0;
  var mTotal = reTotal.exec(html);
  if (mTotal !== null) {
    attacksOnYouTotal = mTotal[1];
    attacksOnYouCurrentPage = mTotal[2];
    attacksOnYouMaxPage = mTotal[3];
    mTotal = reTotal.exec(html);
    if (mTotal !== null) {
      attacksByYouTotal = mTotal[1];
      attacksByYouCurrentPage = mTotal[2];
      attacksByYouMaxPage = mTotal[3];
    }
  }
  return {
    success: true,
    attacksOnYou: {
      attacks: attacksOnYou,
      total: helpers.quantityFromString(attacksOnYouTotal),
      currentPage: helpers.quantityFromString(attacksOnYouCurrentPage),
      maxPage: helpers.quantityFromString(attacksOnYouMaxPage),
    },
    attacksByYou: {
      attacks: attacksByYou,
      total: helpers.quantityFromString(attacksByYouTotal),
      currentPage: helpers.quantityFromString(attacksByYouCurrentPage),
      maxPage: helpers.quantityFromString(attacksByYouMaxPage),
    },
  };
};

parser.parseIntelligence = function (html) {
  if (html === undefined || html === null || !html.length)
    return {
      success: false,
      error: "Empty page to parse"
    };
  // Intercepted
  var reIntercepted = /<td.*>([^<]+)<\/td>\s*<td.*>([^<]+)<\/td>\s*<td align="left">(.+)<\/td>\s*<td.*>([^<]+)<\/td>\s*<td.*>([^<]+)<\/td>\s*<td.*>([^<]+)<\/td>/gmi;
  var mIntercepted;
  var intercepted = [];
  while ((mIntercepted = reIntercepted.exec(html)) !== null) {
    intercepted.push({
      time: mIntercepted[1] + ' ' + mIntercepted[2],
      enemy: helpers.parseUser(mIntercepted[3]),
      missionType: mIntercepted[4],
      numberOfSpies: mIntercepted[5],
      numberOfSpiesCaught: mIntercepted[6],
    });
  }
  // Pages
  var rePages = />(.*) [operationsfiles]+ total \| page (.+) of ([^<]+)/gmi;
  var interceptedTotal = 0,
    interceptedCurrent = 0,
    interceptedMax = 0,
    filesTotal = 0,
    filesCurrent = 0,
    filesMax = 0;
  var mTotal = rePages.exec(html);
  if (mTotal !== null) {
    interceptedTotal = helpers.quantityFromString(mTotal[1]);
    interceptedCurrent = helpers.quantityFromString(mTotal[2]);
    interceptedMax = helpers.quantityFromString(mTotal[3]);
    mTotal = rePages.exec(html);
    if (mTotal !== null) {
      filesTotal = helpers.quantityFromString(mTotal[1]);
      filesCurrent = helpers.quantityFromString(mTotal[2]);
      filesMax = helpers.quantityFromString(mTotal[3]);
    }
  }
  // Files
  var reFiles = /<td align="left">(.*)<\/td>\s*<td.*>([^<]+)<\/td>\s*<td.*><a\s*href="intelfile\.php\?asset_id=([0-9]+)/gmi;
  var mFiles;
  var files = [];
  while ((mFiles = reFiles.exec(html)) !== null) {
    files.push({
      enemy: helpers.parseUser(mFiles[1]),
      numberOfReports: helpers.quantityFromString(mFiles[2]),
      asset_id: helpers.quantityFromString(mFiles[3]),
    });
  }
  return {
    success: true,
    intercepted: {
      operations: intercepted,
      total: interceptedTotal,
      currentPage: interceptedCurrent,
      maxPage: interceptedMax,
    },
    files: {
      reports: files,
      total: filesTotal,
      currentPage: filesCurrent,
      maxPage: filesMax,
    }
  };
};

parser.parseBattleReport = function (html) {
  if (html === undefined || html === null || !html.length)
    return {
      success: false,
      error: "Empty page to parse"
    };
  var re = /<td class="report"[^>]*>([a-zA-Z<>':0-9\/=",.!-?\s]+)<\/td>/gmi;
  var m = re.exec(html);
  if (m === null) {
    return {
      success: false,
      error: "Unable to parse the battle report",
      details: html,
    };
  }
  return {
    success: true,
    report: m[1].trim(),
  };
};

/**
 * Parse the intelligence log of a particular user, listing all the intel reports
 * @param {String} html
 * @returns {Object} an object with success, reports, currentPage, maxPage and total
 */
parser.parseIntelFile = function (html) {
  if (html === undefined || html === null || !html.length)
    return {
      success: false,
      error: "Empty page to parse"
    };

  // Details
  var re = /<td align="right">([^<]+)<\/td>\s*<td align="left">([^<]+)<\/td>\s*<td align="right">([^<]+)<\/td>\s*<td align="right">([^<]+)<\/td>\s*<td align="center">.*inteldetail\.php\?report_id=([0-9]+)">([^<]+)/gmi;
  var m;
  var reports = [];
  while ((m = re.exec(html)) !== null) {
    reports.push({
      time: m[1] + " " + m[2],
      missionType: m[3],
      numberOfSpies: helpers.quantityFromString(m[4]),
      reportId: m[5],
      result: m[6],
    });
  }

  if (reports.length) {

    // Pages
    var rePages = />([^ ]+) reports total \| page ([^ ]+) of ([^<]+)/gmi;
    m = rePages.exec(html);
    var currentPage = 0, maxPage = 0, total = 0;
    if (m !== null) {
      total = helpers.quantityFromString(m[1]);
      currentPage = helpers.quantityFromString(m[2]);
      maxPage = helpers.quantityFromString(m[3]);
    }

    return {
      success: true,
      reports: reports,
      currentPage: currentPage,
      maxPage: maxPage,
      total: total,
    };
  }
  else {
    return {
      success: false,
      error: parser.parseErrorMessage(html),
    };
  }
};

// Parse Intel Details
parser.parseIntelDetail = function (html) {
  if (html === undefined || html === null || !html.length)
    return {
      success: false,
      error: "Empty page to parse"
    };

  // Story
  var storyHeader = "Covert Mission Report";
  var storyStart = html.indexOf( storyHeader ) + storyHeader.length;
  var storyEnd = html.indexOf( "<br>", storyStart );
  var story = helpers.stripHtml(html.substr(storyStart, storyEnd-storyStart)).trim();

  //Military Stats
  var reMilitaryStats = /<td>([ a-zA-Z0-9,:]+)<\/td>\s*<td align="right">([ a-zA-Z0-9,?]+)<\/td>\s*<\/tr>/gmi;
  var mMilitaryStats;
  var militaryStats = [];
  while ((mMilitaryStats = reMilitaryStats.exec(html)) !== null)
    militaryStats.push({
      name: mMilitaryStats[1],
      valueAsText: mMilitaryStats[2],
      value: helpers.quantityFromString(mMilitaryStats[2]),
    });

  // Treasury
  var reTreasury = /Treasury<\/th>\s*<\/tr>\s*<tr>\s*<td align="center">([ a-zA-Z0-9,?]+)<\/td>/gmi;
  var mTreasury = reTreasury.exec(html);
  var treasuryText = (mTreasury === null) ? '???' : mTreasury[1];
  var treasury = helpers.quantityFromString(treasuryText);

  // Army Size & Weapons
  var reWeapons = /<td>([^<]+)<\/td>\s*<td[^>]+>([^<]+)<\/td>\s*<td[^>]+>([^<]+)<\/td>\s*<td[^>]+>([^<]+)<\/td>/gmi;
  var mWeapons;
  var weapons = [];
  var armySize = {};
  while((mWeapons=reWeapons.exec(html))!==null){
    if(mWeapons[1]=='Mercenaries')
      armySize.mercenaries = {
        attack: helpers.quantityFromString(mWeapons[2]),
        defense: helpers.quantityFromString(mWeapons[3]),
        untrained: helpers.quantityFromString(mWeapons[4]),
      };
    else if(mWeapons[1]=='Soldiers')
      armySize.soldiers = {
        attack: helpers.quantityFromString(mWeapons[2]),
        defense: helpers.quantityFromString(mWeapons[3]),
        untrained: helpers.quantityFromString(mWeapons[4]),
      };
    else
      weapons.push({
        name: mWeapons[1],
        type: mWeapons[2],
        quantityText: mWeapons[3],
        quantity: helpers.quantityFromString(mWeapons[3]),
        strength: mWeapons[4],
      });
  }

  return {
    story: story,
    army: armySize,
    stats: militaryStats,
    treasuryText: treasuryText,
    treasury: treasury,
    weapons: weapons,
    success: mTreasury !== null,
  };
};

module.exports = parser;