/* $Id: warbook.js,v 1.16 2008/01/25 01:57:32 altblue Exp $
 *
 * Copyright 2006, 2007 Marius Feraru <altblue@n0i.net>
 * All rights reserved.
 *
 * This file is part of WBx (Warbook Enhancer)
 *
 * WBx is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details:
 * <http://www.gnu.org/licenses/>
 *
 */

// should be customized by the GreaseMonkey script user ;-)
if (!window.WBxHeroLevel)   WBxHeroLevel    = 0;
if (!window.WBxHeroType)    WBxHeroType     = '';
if (!window.WBxSearchRetry) WBxSearchRetry  = 3;
if (!window.WBxSearchDelay) WBxSearchDelay  = 5;
if (!window.WBxSearchInc)   WBxSearchInc    = 200;
if (!window.WBxSearchDelta) WBxSearchDelta  = 1000;

WBx = {

  gold:     0,  // automatically detected
  acres:    0,
  soldiers: 0,
  mana:     0,

  weak: {
      min: 1, // min. level
      max: 2, // is automatically set to (this.level + 1)
    retry: WBxSearchRetry, // if no results, how many times to retry with the same size?
    delay: WBxSearchDelay, // search delay (seconds)
      inc: WBxSearchInc,   // size increment
    delta: WBxSearchDelta  // how far from minimum attack size to start searching?
  },

  attackSize: { // attack limits (automatically set)
    min: 0,
    max: 0,
    current: 0,
    currentTry: 0
  },

  searchResults: { // automatically computed
    minSize:  Infinity,
    maxSize:  0,
    minLevel: Infinity,
    maxLevel: 0
  },
  searchRunning: false, // "weak search" switch
  searchTimer:   null,  // "weak search" XHR timer

  initialize: function() {
    this.setHero(WBxHeroLevel, WBxHeroType);

    this.body = $(document.body);
    this.page = window.location.pathname.replace('/index', '').replace('/','-','g').camelize();

    this.updateKingdomParams(this.body);
    this.processMessages();

    var m = 'enhance' + this.page;
    if (this[m]) this[m]();

    this.addMenu();
    // throw new Error('page = ' + this.page);
  },

  enhanceKingdomManage: function() { // expand your kingdom
    this.autoFillMax();
  },

  enhanceArmy: function() { // build your army
    this.autoFillMax();
  },

  enhanceDiplomacyPlayerSearch: function() { // user search form/results
    // sort user search results
    var players = this.body.select('div.player').map(function(d){
      return this.getPlayerInfo(d)
    }, this);

    players.each(function(p){ this.stylishPlayer(p) }, this);

    players.sort(function(a, b) {
      return a.level < b.level ? -1 : a.level > b.level ? 1
        : (a.size < b.size ? -1 : a.size > b.size ? 1 : 0);
    });

    for (var i = players.length - 2; i >= 0; i--) {
      var cp = players[i].wrapper, np = players[i+1].wrapper;
      np.parentNode.insertBefore(cp.parentNode.removeChild(cp), np);
    }
  },

  enhanceDiplomacyUserPage: function() { // user page
    this.presetAttackForce();
    this.addAttackForceControls();
    this.showAttackPower();
    $H(this.army).keys().map(function(t) {
      return this.body.down('input[name="' + t + '"]');
    }, this).invoke('observe', 'change', this.showAttackPower.bind(this));
    var p = this.parseUserPage(this.body);
    if (p) document.title = [p.name, p.level, p.type, p.acres].join(' | ');
  },

  enhanceHelp: function() { // help :)
    this.body.select('div.info').invoke('show');
    this.body.xselect('.//a[contains(@onclick, "toggle")]').each(function(a) {
      var h = new Element('strong'); h.update(a.textContent);
      a.hide().insert({before: h}).remove();
    });
  },

  enhanceKingdom: function() { // home
    var hinfo = this.body.xselect('.//div[contains(@class,"info")]/a[@href="/hero/"]/..')[0];
    if (!hinfo) return;
    var m = hinfo.textContent.match(/Your Hero is a Level\s+(\d+)\s+(\S+)/);
    if (m) this.setHero(m[1].toInteger(), m[2]);
  },

  enhanceHero: function(root) { // your hero
    root = root || this.body;

    var ps = root.down('div.profile > div.stats'), m;
    if (ps && (m = ps.textContent.match(/Level\s+(\d+)\s+(\S+)/))) {
      this.setHero(m[1].toInteger(), m[2]);
    }

    var pi = root.down('div.profile_info');
    if (pi) {
      $H({ attack: 'Attack', defense: 'Defense', spell: 'Spell Power' }).each(function(p){
        var d = pi.xselect('.//div[text()="' + p.value + ':"]/following-sibling::div[1]')[0];
        if (d) this[p.key] = d.textContent.toInteger();
      }, this);
      // be fuzzy
      var m = root.innerHTML.match(/Next Level: (\d+) exp remaining/);
      if (m) this.exp = m[1].toInteger();
    }
  },

  enhanceDiplomacy: function() { // make allies
  },

  enhanceDiplomacyLeaderboard: function() { // view the leaderboard
  },

  enhanceAllianceAlliancePage: function() { // alliance page
  },

  enhanceHelpOptions: function() { // options
  },

  updateKingdomParams: function(root, scrubbed) {
    $w('gold acres soldiers mana').each(function(attr) {
      var v = $( (scrubbed ? 'scrubbed-' : '') + attr );
      if (v) {
        this[attr] = v.textContent.toInteger();
        if (scrubbed) {
          var pv = $(attr);
          if (pv) pv.update(this[attr].toLocaleString());
        }
      }
    }, this);
    if (this.acres) {
      this.attackSize.min = Math.ceil(this.acres/3);
      this.attackSize.max = Math.floor(this.acres*3);
    }
  },

  addMenu: function() {
    var links = [
      {
        title: 'Expand your Kingdom',
        icon:  '/images/land.png',
        href:  '/kingdom/manage'
      },
      {
        title: 'Build your Army',
        icon:  '/images/army.png',
        href:  '/army/'
      },
      {
        title: 'Conquer Enemy Lands',
        icon:  '/images/war.png',
        href:  '/diplomacy/playerSearch/'
      },
      {
        title: 'Your Hero',
        icon:  '/images/soldier.png',
        href:  '/hero/'
      },
      {
        title: 'Make Allies',
        icon:  '/images/foreign.png',
        href:  '/diplomacy/'
      },
      {
        title: 'View the Leaderboard',
        icon:  '/images/leaderboard.png',
        href:  '/diplomacy/leaderboard/?type=friends'
      },
      {
        title: 'Get Some Help',
        icon:  '/images/help.png',
        href:  '/help/'
      },
      {
        title: 'Kingdom Options',
        icon:  '/images/options.png',
        href:  '/help/options/'
      },
    ];
    if (this.level) {
      links.push({
        title: 'Find Weak Targets',
        icon:  '/images/pirates.png',
        href:  '#',
        listener: 'toggleSearchListener'
      });
    }
    var menu = new Element('div', {id: 'menu'});
    links.each(function(i){
      var name = i.icon.replace(/^.+\/([^\/]+)\.png$/, '$1');
      var a = new Element('a', {href: i.href, title: i.title, id: 'menu-' + name });
      a.insert( new Element('img', {src: i.icon, width: 32, height: 32, alt: name }) );
      menu.insert(a);
      if (i.listener) a.observe('click', this[i.listener].bindAsEventListener(this));
    },this);
    this.body.insert(menu);
  },

  // parse player information, given a <div class="player"> block
  getPlayerInfo: function(player) {
    var info = { wrapper: player };
    player.select('span').each(function(el){
      var text = el.textContent;
      // level + type?
      var m = /^\s*Level\s+([0-9,.]+)\s+(\S+)/.exec(text);
      if (m) {
        el.className = 'level';
        el.innerHTML = 'Level ' + m[1];
        info.levelRef = el;
        info.level    = m[1].toInteger();
        info.type     = m[2];
        info.typeRef  = document.createElement('em');
        info.typeRef.className = 'type';
        info.typeRef.innerHTML = info.type;
        el.parentNode.insertBefore(info.typeRef, el.nextSibling);
        el.parentNode.insertBefore(document.createTextNode(' '), el.nextSibling);
        return;
      }
      // kingdom size?
      var m = /^\s*([0-9,.]+)\s+acres/.exec(text);
      if (m) {
        info.sizeRef = el;
        el.className = 'size';
        info.size    = m[1].toInteger();
        return;
      }
    },this);

    player.select('a').each(function(el){
      if (!el.href) return;
      // userPage?
      if (el.href.indexOf('/userPage') > -1) {
        el.className = 'user-page';
        info.userPage = el;
        info.userName = el.textContent;
        return;
      }
      // alliancePage?
      if (el.href.indexOf('/alliancePage') > -1) {
        el.className = 'alliance-page';
        info.alliancePage = el;
        info.allianceName = el.textContent.clean();
        if (!info.allianceName) {
          info.allianceName = decodeURIComponent(el.href.toQueryParams().target).clean();
          el.innerHTML = info.allianceName.escapeHTML();
        }
        return;
      }
    },this);

    var s = this.searchResults;
    if (s.minSize  > info.size)  s.minSize  = info.size;
    if (s.maxSize  < info.size)  s.maxSize  = info.size;
    if (s.minLevel > info.level) s.minLevel = info.level;
    if (s.maxLevel < info.level) s.maxLevel = info.level;

    return info;
  },

  parseUserPage: function(doc) {
    var m = /^Level\s+(\d+)\s+(\S+)/.exec(doc.down('div.stats').textContent.clean());
    return {
                name: doc.down('div.alias').textContent.clean(),
               acres: doc.down('div.info_box.info_data.primary.first').textContent.toInteger(),
          alertLevel: doc.down('div.info_box.info_data.primary.third').textContent.clean(),
      resourcesLevel: doc.down('div.info_box.info_data.secondary.third').textContent.clean(),
         isProtected: !!(doc.down('div.info_box.info_data.secondary.second').textContent.clean() !== 'None'),
               level: (m ? m[1].toInteger() : 0),
                type: (m ? m[2] : 'UNKNOWN')
    };
  },

  stylishPlayer: function(p) { // colorize player div in user search results
    if (this.level)
      p.levelRef.style.color = this.level / 2 >= p.level ? 'green' : this.level > p.level ? 'yellow' : 'red';
    if (p.alliancePage)
      p.alliancePage.style.color = 'magenta';
  },

  parseSearchResults: function(t) {
    this.searchRunning = false;
    var html = new Element('div');
    html.hide().update(t.responseText.scrubHTMLBody());
    this.body.insert(html);
    this.updateKingdomParams(html, true);
    var players = html.select('div.player').map(
                    function(d){ return this.getPlayerInfo(d) },
                    this
                  ).filter(
                    function(p){
                      return (p.level >= this.weak.min)
                        && (p.level <= this.weak.max)
                        && !p.alliancePage
                        && !$('sr-' + p.userName);
                    },
                    this
                  );
    var msg = '@min=' + this.attackSize.current + ' → ' + players.length + '✓';
    if (players.length > 0) {
      this.attackSize.currentTry = 0;
    } else {
      msg += ' (try: ' + ++this.attackSize.currentTry + ')';
    }
    this.searchResultsStatus.update(msg);

    if (players.length > 0) {
      players.sort(function(a, b) {
        return a.level < b.level ? -1 : a.level > b.level ? 1
          : (a.size < b.size ? -1 : a.size > b.size ? 1 : 0);
      });
      players.each(function(p){this.addSearchResult(p);}, this);
    } else {
      if (this.attackSize.currentTry >= this.weak.retry)
        this.increaseAttackSize();
    }
    html.remove();
  },

  drawSearchResultsTable: function() {
    if (this.searchResultsTable) return;

    // rows colorization
    this.weakPallete = ( new Color(0,51,0) ).blendTo(
      ( new Color(51,0,0) ),
      ( 1 + this.weak.max - this.weak.min )
    );
    this.weakPallete.unshift(new Color(0,0,255));

    this.searchResultsTable = new Element('table', {id: 'search-results'});
    // var cols = $w('Name Type Level Acres Alliance Alert Resources');
    var cols = $w('Name Type Level Acres Alert Resources');
    cols.each(function(col){
      this.searchResultsTable.insert(new Element('colgroup', {id: 'sr' + col}));
    }, this);

    var thead = new Element('thead'), theadRow = new Element('tr');
    thead.insert(theadRow);
    cols.each(function(col){ th = new Element('th'); th.insert(col); theadRow.insert(th); });
    this.searchResultsTable.insert(thead);

    this.searchResultsBody = [thead]; // fake, to be used for "appending" byLevelTBODYz

    var tfoot = new Element('tfoot');
    this.searchResultsTable.insert(tfoot);
    var tfootRow = new Element('tr');
    tfoot.insert(tfootRow);
    var tfootCell = new Element('td', {colspan: cols.length});
    tfootRow.insert(tfootCell);

    var sc = new Element('span', {id: 'search-controls'});
    tfootCell.insert(sc);

    this.searchResultsStatus = new Element('span', {id: 'search-status'});
    this.searchResultsStatus.update('loading…');
    tfootCell.insert(this.searchResultsStatus);

    var cas = new Element('a', {href:'#', id:'current-attack-size', title:'increase current search size'});
    var clr = new Element('a', {href:'#', id:'hide-search-results', title:'hide these results'});
    var rev = new Element('a', {href:'#', id:'show-search-results', title:'show all results'});
    var hlt = new Element('a', {href:'#', id:'pause-search',        title:'pause searching'});
    var rsm = new Element('a', {href:'#', id:'resume-search',       title:'resume searching'});
    cas.observe('click', this.increaseAttackSizeListener.bindAsEventListener(this));
    clr.observe('click', this.hideSearchResultsListener.bindAsEventListener(this));
    rev.observe('click', this.showAllSearchResultsListener.bindAsEventListener(this));
    hlt.observe('click', this.pauseSearchListener.bindAsEventListener(this));
    rsm.observe('click', this.resumeSearchListener.bindAsEventListener(this));
    cas.update(this.attackSize.current);
    clr.update('cls ✔');
    rev.update('rev');
    hlt.update('stop ✗');
    rsm.update('start »').hide();
    var sep = ' | ';
    sc.insert(cas).insert(sep).insert(clr).insert(sep).insert(hlt).insert(rsm).insert(sep).insert(rev).insert(sep);

    $('content_box').insert({top: this.searchResultsTable});
  },

  drawSearchResultsTBody: function(level) {
    if (this.searchResultsBody[level])
      return this.searchResultsBody[level];

    this.searchResultsBody[level] = new Element('tbody', {id: 'search-results-level-' + level});
    this.searchResultsBody[level].style.backgroundColor = this.weakPallete[level].toString();

    var prev, i = level;
    while (--i >= 0 && !prev)
      if (this.searchResultsBody[i])
        prev = this.searchResultsBody[i];
    prev.insert({after: this.searchResultsBody[level]});

    return this.searchResultsBody[level];
  },

  addSearchResult: function(p) {
    var td, tdref = new Element('td'), tr = new Element('tr', {id: 'sr-' + p.userName});
    tr.addClassName('level' + p.level);

    var userpage = p.userPage.href, uh = this.getHero(p.type);

    // "level" color
    var lc = this.level / 2 >= p.level ? 'green' : this.level > p.level ? 'yellow' : 'red';
    // "defense" color
    var def = uh ? uh.defense : 0;
    var dc  = def < 2 ? 'green' : def < 4 ? 'yellow' : 'red';
    
    // Name
    td = tdref.cloneNode(false);
    td.insert( '<img src="/images/' + p.type.lc() + '.png" height="20" width="20" alt="" /> ' );
    p.userPage.style.color = lc;
    td.insert( p.userPage.remove() );
    if (p.alliancePage) {
      p.alliancePage.className = 'alliance';
      td.insert(' [').insert( p.alliancePage.remove() ).insert(']');
    }
    tr.insert(td);
    // Type
    td = tdref.cloneNode(false);
    td.insert( p.type );
    td.style.color = dc;
    tr.insert(td);
    // Level
    td = tdref.cloneNode(false);
    td.style.fontWeight = 'bold';
    td.style.color = lc;
    td.insert( p.level );
    tr.insert(td);
    // Acres
    td = tdref.cloneNode(false);
    td.insert( p.size );
    tr.insert(td);
    // Alliance
    /*
    td = tdref.cloneNode(false);
    if (p.alliancePage) {
      p.alliancePage.style.color = 'magenta';
      td.insert( p.alliancePage.remove() );
    }
    tr.insert(td);
    */
    // Alert
    var alertTD = tdref.cloneNode(false);
    tr.insert(alertTD);
    // Resources
    var resourcesTD = tdref.cloneNode(false);
    tr.insert(resourcesTD);

    // don't waste bandwidth with "too powerful" targets ;-)
    //if (p.level <= this.level)
      this.loadUserDetails(userpage, alertTD, resourcesTD);

    this.drawSearchResultsTBody(p.level);
    this.searchResultsBody[p.level].insert(tr);
  },

  loadUserDetails: function(url, alertTD, resourcesTD) {
    new Ajax.Request(url, {
      method: 'GET',
      onSuccess: this.injectUserDetails.bind(this,alertTD,resourcesTD),
      onFailure: this.failedUserDetails.bind(this,alertTD,resourcesTD,'FAILED','FAILED')
    });
  },

  failedUserDetails: function(at, rt, al, rl) {
    at.update(al);
    at.style.color = this.getAlertLevel(al).color;
    rt.update(rl);
    rt.style.color = this.getResourcesLevel(rl).color;
    setTimeout(function(){
        this.hide();
      }.bind( at.up('tr').addClassName('error') ),
      1000
    );
  },

  injectUserDetails: function(alertTD, resourcesTD, t) {
    var html = new Element('div');
    html.hide().update(t.responseText.scrubHTMLBody());
    this.body.insert(html);
    var p = this.parseUserPage(html);
    html.remove();
    if (!p) p = {alertLevel:'FAILED', resourcesLevel:'FAILED'};
    var al = this.getAlertLevel(p.alertLevel),
        rl = this.getResourcesLevel(p.resourcesLevel);
    var isAlerted  = !!(al.order > 3);
    var isStripped = !!(rl.order < 1);
    if ( p.isProtected || isAlerted || isStripped )
      return this.failedUserDetails(alertTD, resourcesTD, p.alertLevel, p.resourcesLevel);

    alertTD.update(p.alertLevel);
    alertTD.style.color = al.color;
    resourcesTD.update(p.resourcesLevel);
    resourcesTD.style.color = rl.color;
  },

  increaseAttackSize: function(delta) {
    delta = parseInt(delta);
    if (delta) {
      this.attackSize.current += delta;
    } else {
      this.attackSize.current = this.searchResults.maxSize + this.weak.inc;
    }
    if (this.attackSize.current <= this.attackSize.min)
      this.attackSize.current = this.attackSize.min + this.weak.delta;
//      this.attackSize.current = this.attackSize.min + Math.ceil(this.acres * this.weak.delta);
    this.attackSize.currentTry = 0;
    $('current-attack-size').update(this.attackSize.current);
  },

  resumeSearchListener: function(ev) {
    Event.stop(ev);
    if (!this.searchResultsTable) { // first time run
      this.attackSize.current = this.attackSize.min + this.weak.delta;
//      this.attackSize.current = this.attackSize.min + Math.ceil(this.acres * this.weak.delta);
      this.drawSearchResultsTable();
    }
    this.searchTimer = setInterval( this.resumeSearch.bind(this), this.weak.delay * 1000 );
    this.resumeSearch();
    $('resume-search').hide();
    $('pause-search').show();
  },
  pauseSearchListener: function(ev) {
    Event.stop(ev);
    this.pauseSearch();
    $('pause-search').hide();
    $('resume-search').show();
  },
  toggleSearchListener: function(ev) {
    return (this.searchRunning || this.searchTimer)
      ? this.pauseSearchListener(ev) : this.resumeSearchListener(ev);
  },
  increaseAttackSizeListener: function(ev) {
    Event.stop(ev);
    this.increaseAttackSize(this.weak.inc);
  },
  hideSearchResultsListener: function(ev) {
    Event.stop(ev);
    this.searchResultsTable.select('tbody > tr').invoke('hide');
  },
  showAllSearchResultsListener: function(ev) {
    Event.stop(ev);
    this.searchResultsTable.select('tbody > tr').invoke('show');
  },

  resumeSearch: function() {
    if (this.searchRunning) return; // don't let multiple search "threads" overlap
    this.searchRunning = true;
    this.searchResultsStatus.update('@min=' + this.attackSize.current + '…');
    new Ajax.Request(
      '/diplomacy/playerSearch?searchBy=acres&min=' + this.attackSize.current,
      {
        method: 'GET',
        onSuccess: this.parseSearchResults.bind(this),
        onFailure: this.pauseSearch.bind(this)
      }
    );
  },
  pauseSearch: function() {
    if (this.searchTimer) {
      clearInterval(this.searchTimer);
      delete this.searchTimer;
    }
    this.searchRunning = false;
    this.searchResultsStatus.update('search paused');
  },

  presetAttackForce: function() {
    var armySize = 0,
        attackArmySize = 0,
        units = ['soldiers','knights','pikemen','elites'];

    units.each(function(t) {
      var fld = this.body.down('input[name="' + t + '"]');
      if (!fld) return;
      this.army[t].number = fld.getAttribute('value').toInteger();
      armySize += this.army[t].number;
      // do NOT attack with defensive troops ... 
      if (this.army[t].attack < this.army[t].defense) {
        fld.value = 0;
      } else {
        attackArmySize += this.army[t].number;
      }
    }, this);

    // ... unless you have no other choice :(
    var deltaSize = 0, deltaPercent = 0;
    // You must with at least XXX army: armySize > acres
    if (attackArmySize < this.acres)
      deltaSize =this.acres - attackArmySize;
    // You must attack with at least 20% of your troops!
    var percent = Math.ceil( armySize / 5 );
    if (attackArmySize < percent)
      deltaPercent = percent - attackArmySize;
    var delta = Math.max(deltaSize,deltaPercent);
    if (delta > 0 && armySize > this.acres) {
      if (this.army.elites.number >= delta) { // add "elites" only
        this.body.down('input[name="elites"]').value = delta;
      } else { // add "pikemen" too
        this.body.down('input[name="elites"]').value = this.army.elites.number;
        this.body.down('input[name="pikemen"]').value = delta - this.army.elites.number;
      }
    }
  },

  addAttackForceControls: function() {
    $H(this.army).keys().each(function(name) {
      var fld = this.body.down('input[name="' + name + '"]');
      if (!fld) return;
      var ctrl = new Element('span');
      ctrl.addClassName('attack-controls');
      ctrl.insert('  ');
      var min = new Element('a', {href: '#'});
      min.update('none').observe('click', this.setInputValue.bindAsEventListener(this,name,0));
      ctrl.insert(min).insert(' - ');
      var max = new Element('a', {href: '#'});
      max.update('all').observe('click', this.setInputValue.bindAsEventListener(this,name,this.army[name].number));
      ctrl.insert(max);
      fld.insert({after: ctrl});
    }, this);
  },

  setInputValue: function(ev, name,value) {
    if (ev) Event.stop(ev);
    var fld = this.body.down('input[name="' + name + '"]');
    if (fld) {
      fld.value = value;
      this.showAttackPower();
    }
  },

  // compute and show attack power
  showAttackPower: function() {
    var aps = $('attack-power');
    if (!aps) {
      aps = new Element('strong', {id: 'attack-power'});
      this.body.down('input[name="commit"]')
                .insert({after: aps})
                .insert({after: ' <em>Basic</em> attack power: '});
    }
    var attackPower = 0;
    $H(this.army).keys().each(function(t) {
      attackPower += this.army[t].attack * this.body.down('input[name="' + t + '"]').value.toInteger();
    }, this);
    aps.update(attackPower);
  },

  autoFillMax: function() {
    var elements = document.getElementsByTagName('form');
    for (var i = 0, l = elements.length; i < l; i++) {
      var el = elements[i];
      var match = /\(max ([0-9,.]+)/.exec(el.innerHTML);
      if (match == null) continue;
      var inputs = el.getElementsByTagName('input');
      for (var j = 0, jl = inputs.length; j < jl; j++) {
        var input = inputs[j];
        if (input.type === 'text') {
          input.value = match[1].replace(/[,.]/g, '');
        }
      }
    }
  },

  processMessages: function() {
    var m = $('messages');
    if (!m) return;

    // show *all* messages
    var mm = $('moreMessages');
    if (mm) {
      mm.previous('a').remove();
      mm.insert({before: mm.innerHTML}).remove();
    }

    var c = {troops:0, acres:0, gold:0, wins:0, losses:0}, match;

    var cRE = new RegExp('You lost the battle, losing ([0-9,]+) troops, ([0-9,]+) acres, and ([0-9,]+) gold', 'g'); 
    while ((match = cRE.exec(m.innerHTML)) != null) {
      c.losses++;
      c.troops += match[1].toInteger();
      c.acres  += match[2].toInteger();
      c.gold   += match[3].toInteger();
    }

    cRE = new RegExp('casts a fireball that rips through your lands, annihilating ([0-9,]+) soldiers, ([0-9,]+) knights, ([0-9,]+) pikemen, and ([0-9,]+) elites', 'g'); 
    while ((match = cRE.exec(m.innerHTML)) != null) {
      c.losses++;
      c.troops += match[1].toInteger() + match[2].toInteger() + match[3].toInteger() + match[4].toInteger();
    }

    cRE = new RegExp('magically steals ([0-9,]+) acres from you', 'g'); 
    while ((match = cRE.exec(m.innerHTML)) != null) {
      c.losses++;
      c.acres += match[1].toInteger();
    }

    cRE = new RegExp('You won the battle, but lost ([0-9,]+) troops', 'g'); 
    while ((match = cRE.exec(m.innerHTML)) != null) {
      c.wins++;
      c.troops += match[1].toInteger();
    }

    if (c.wins > 0 || c.losses > 0)
      m.insert({top: '<div class="lost">You lost <b>' + c.troops
        + '</b> troops, <b>' + c.acres + '</b> acres, and <b>'
        + c.gold + '</b> gold.</div>'
      });

  },

  setHero: function(level, type) {
    this.level = level;
    this.type  = type;
    var uh = this.getHero(type);
    Object.extend(this, uh || {
      attack:  0,
      defense: 0,
      spell:   0,
      army: {
        soldiers: {attack: 1, defense: 1},
        knights:  {attack: 3, defense: 1},
        pikemen:  {attack: 1, defense: 3},
        elites:   {attack: 1, defense: 1, name: 'FIXME'}
      }
    });
    this.weak.max = this.level  ? this.level + 10 : 2;
  },

  getBuildings: function() {
    return WBInfo.buildings;
  },
  getBuilding: function(id) {
    return WBInfo.buildings[id];
  },
  getHeroes: function() {
    return WBInfo.heroes;
  },
  getHero: function(id) {
    return WBInfo.heroes[id];
  },
  getAlertLevels: function() {
    return WBInfo.alertLevels;
  },
  getAlertLevel: function(id) {
    return WBInfo.alertLevels[id] || {order: 5, color: '#F00'};
  },
  getResourcesLevels: function() {
    return WBInfo.resourcesLevels;
  },
  getResourcesLevel: function(id) {
    return WBInfo.resourcesLevels[id] || {order: 4, color: '#F00'};
  }

};


WBInfo = {

  buildings: {
    Mines:              {bonus: 'gold',    factor: 30,  limit: 0  },
    Barracks:           {bonus: 'cost',    factor: 2,   limit: 50 },
    Forts:              {bonus: 'defense', factor: 1.5, limit: 45 },
    'Training Grounds': {bonus: 'attack',  factor: 1.5, limit: 45 },
    Amplifiers:         {bonus: 'spell',   factor: 2,   limit: 50 },
    Barriers:           {bonus: 'magic',   factor: 3,   limit: 75 }
  },

  heroes: {
    Automator: {
      attack:  3,
      defense: 3,
      spell:   0,
      army: {
        soldiers: {attack: 1, defense: 1},
        knights:  {attack: 3, defense: 1},
        pikemen:  {attack: 1, defense: 3},
        elites:   {attack: 9, defense: 9, name: 'Machina'} // FIXME
      }
    },
    General: {
      attack:  3,
      defense: 3,
      spell:   0,
      army: {
        soldiers: {attack: 1, defense: 1},
        knights:  {attack: 3, defense: 1},
        pikemen:  {attack: 1, defense: 3},
        elites:   {attack: 9, defense: 9, name: 'Elite'} // FIXME
      }
    },
    Magician: {
      attack:  1,
      defense: 0,
      spell:   4,
      army: {
        soldiers: {attack: 1, defense: 1},
        knights:  {attack: 3, defense: 1},
        pikemen:  {attack: 1, defense: 3},
        elites:   {attack: 9, defense: 9, name: 'Wizard'} // FIXME
      }
    },
    Mogul: {
      attack:  1,
      defense: 2,
      spell:   1,
      army: {
        soldiers: {attack: 1, defense: 1},
        knights:  {attack: 3, defense: 1},
        pikemen:  {attack: 1, defense: 3},
        elites:   {attack: 3, defense: 5, name: 'Legionnaire'}
      }
    },
    Necromancer: {
      attack:  2,
      defense: 2,
      spell:   2,
      army: {
        soldiers: {attack: 1, defense: 1},
        knights:  {attack: 3, defense: 1},
        pikemen:  {attack: 1, defense: 3},
        elites:   {attack: 3, defense: 3, name: 'Zombie'}
      }
    },
    Slayer: {
      attack:  4,
      defense: 1,
      spell:   2,
      army: {
        soldiers: {attack: 1, defense: 1},
        knights:  {attack: 3, defense: 1},
        pikemen:  {attack: 1, defense: 3},
        elites:   {attack: 9, defense: 9, name: 'Assassin'} // FIXME
      }
    },
    Visionary: {
      attack:  0,
      defense: 5,
      spell:   1,
      army: {
        soldiers: {attack: 1, defense: 1},
        knights:  {attack: 3, defense: 1},
        pikemen:  {attack: 1, defense: 3},
        elites:   {attack: 2, defense: 8, name: 'Defender'}
      }
    },
    Warmonger: {
      attack:  5,
      defense: 0,
      spell:   1,
      army: {
        soldiers: {attack: 1, defense: 1},
        knights:  {attack: 3, defense: 1},
        pikemen:  {attack: 1, defense: 3},
        elites:   {attack: 9, defense: 9, name: 'Berserker'} // FIXME
      }
    }
  },

  alertLevels: {
    None:         {order: 0, color: '#0C0'},
    Low:          {order: 1, color: '#0C0'},
    Medium:       {order: 2, color: '#CC0'},
    High:         {order: 3, color: '#F90'},
    'Full Alert': {order: 4, color: '#C00'}
  },

  resourcesLevels: {
    Stripped:  {order: 0, color: '#C00'},
    Scarce:    {order: 1, color: '#F90'},
    Average:   {order: 2, color: '#CC0'},
    Plentiful: {order: 3, color: '#0C0'}
  }

}; // WBInfo


function Color(r,g,b) {
    this.r = r || 0;
    this.g = g || 0;
    this.b = b || 0;
    function blendTo(endColor, steps) {
      var pallete = [this], step = steps - 1;
      pallete[steps - 1] = endColor;
      steps--; // skip start/end colors
      sR = (endColor.r - this.r) / steps;
      sG = (endColor.g - this.g) / steps;
      sB = (endColor.b - this.b) / steps;
      while (step-- > 1) {
        pallete[step] = new Color(
          parseInt( this.r + (sR * step) ),
          parseInt( this.g + (sG * step) ),
          parseInt( this.b + (sB * step) )
        );
      }
      return pallete;
    };
    this.blendTo = blendTo;
    this.toString = function() {
      return 'rgb(' + this.r + ',' + this.g + ',' + this.b + ')';
    }
};

WBx.initialize();

