var cgevent = {
  PTBUY: [0,0,0,0,0,0,0,0,0,0,0,1,2,3,5,7,9,12,16], // Point-Buy cost

  mousemove_timeout: null,
  hint_node: document.getElementById("ui_hint"),

  mousemove: function (evt) {
    var target = evt.target;
    var hint = null;
    if (target.hasAttribute('hint')) {
      hint = target.getAttribute('hint');
    } else if (target.parentNode && target.parentNode.hasAttribute && target.parentNode.hasAttribute('hint')) {
      hint = target.parentNode.getAttribute('hint');
    }
    if (this.mousemove_timeout) {
      clearTimeout(this.mousemove_timeout);
      this.mousemove_timeout = null;
    }
    if (hint) {
      var text = window.text;
      if (hint[hint.length-1] == ';') {
        hint = eval(hint);
        if (hint) {
          if (!isString(hint))
            if (hint.hint) {
              hint = hint.hint;
            } else
              hint = '';
        } else
          hint = '';
      } else {
        var text = window.text;
        if (hint.substr(hint.length-5) == '_desc') {
          var title = text[hint.substr(0, hint.length-5)];
          var icon = text[hint.substr(0, hint.length-5)+'_icon'];
//          var flav = text[hint.substr(0, hint.length-5)+'_flav'];
          hint =
              (icon ? '<span class="f_right">'+icon+'</span>' : '')
            + (title ? '<b>'+title+'</b><br>':'')
//            + (flav ? '<i>'+flav+'</i><br>':'')
            + text[hint];
        } else if (text[hint])
          hint = text[hint];
      }
      if (hint != $("ui_hint").innerHTML)
        cgevent.hint_node.innerHTML = hint;
    } else {
      if (!this.mousemove_timeout && $("ui_hint").innerHTML)
        this.mousemove_timeout = setTimeout('$("ui_hint").innerHTML=""', 500);
    }
  },

  /*
   Ability score box on key press
   */
  abilityKP: function (evt) {
    if (evt.keyCode == 38) { // Up
      var target = evt.target, v = +target.value;
      if (v == target.value) {
        target.value = Math.min(99, v+1);
        target.onchange();
      }
    } else if (evt.keyCode == 40) { // Down
      var target = evt.target, v = +target.value;
      if (v == target.value) {
        target.value = Math.max(0, v-1);
        target.onchange();
      }
    }
  },

  /*
    Set ability up.  For easier use, subsequence ups will be updated as well.
   */
  setAbilityUp: function(slot, evt, old_choice) {
    var chr = chargen.char;
    var au = chr.abilities.ups;
    var target = evt.currentTarget;
    let [a1, a2] = target.value.split(',',2);
    let [o1, o2] = old_choice.split(',',2);

    let l = au.length;
    for (slot = (slot-1)*2; slot < l; slot += 2) {
      if (au[slot] != o1 || au[slot+1] != o2) break;
      au[slot] = a1;
      au[slot+1] = a2;
    }
  },

  /* Custom deity */
  onDeityChange: function(evt) {
    chargen.char.misc.deity = evt.target.value;
    $('deity_list').selectedIndex = 0;
    cgevent.update();
  },

  /* Non-custom deity */
  onDeityListChange: function(evt) {
    chargen.char.misc.deity = evt.target.value;
    $('deity').value = evt.target.value ? text['deity_'+evt.target.value] : '';
    cgevent.update();
  },

  /* Select / unselect a skill */
  onLanguageSelect: function(evt) {
    chargen.char.races.base.languages[evt.target.value] = evt.target.checked;
  },

  /* Select / unselect a skill */
  onSkillSelect: function(evt) {
    chargen.char.classes.heroic.skills[evt.target.value] = evt.target.checked;
  },

  onDoNothing: function(evt) {
  },

  /*
   Set base race
   */
  onBaseRaceChange: function(evt) {
    let opt = evt.currentTarget;
    chargen.char.setRaceClass("races", "base", new chargen.Races[opt.value]());
  },

  /*
   Set base class
   */
  onHeroicClassChange: function(evt) {
    let opt = evt.currentTarget;
    chargen.char.setRaceClass("classes", "heroic", new chargen.Classes[opt.value]());
  },

  onParagonChange: function(evt) {
    alert('Not implemented');
  },

  /*
   Set a feat, power, or equipment slot with given power
   */
  setFPE: function (ffp, name, new_power) {
    if (new_power) {
      let [type, id] = new_power.split('.', 2);
      var p = chargen[chargen.System.cap(ffp)+'s'][type][id];
      p = new p();
      if (p.options) for each (let [value,txt] in Iterator(p.options)) { p.choice = value; break;}
    } else {
      var p = null;
    }
    chargen.char['set'+chargen.System.cap(ffp)](name, p);
  },

  /*
   Hide all options tab of given element
   */
  hideTabs: function (elem, show_elem) {
    let tabs = document.evaluate(".//*[@class='tabOption']", $(elem) || document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0; i < tabs.snapshotLength; i++) {
      var tab = tabs.snapshotItem(i);
      hide(tab);
    }
    if (show_elem) show(show_elem);
    return false;
  },

  /*
   Expand the correct race and class tabs
   */
  ui_show_raceclass: function() {
    this.hideTabs('ui_raceclass');
    show('race_' + chargen.char.races.base.book);
    let lv = chargen.char.lv;
    show(lv < 11 ? 'lv_h' : lv < 21 ? 'lv_p' : 'lv_e');
    show('class_' + chargen.char.classes.heroic.book);
  },


  /*************************** Rule enforcements *************************/

  // UI's hook on character object
  characterHook : {
  },

  // Temporary info, no automatic reset
  temp : {}

}

  /************************* Updators *****************************/

  /*
   UI initialisation. For easier maintainence, many UI controls are populated dynamically from base code
  */
  cgevent.initialise = function(evt) {
    let chr = chargen.char;
    chr.hooks.ui = this.characterHook;

    // Sync ability scores.  The ability scores will stay in a normal refresh, although everything else will be lost.
    for each (let stat in chargen.STATS) chr.abilities[stat+'Base'] = parseInt($(stat+'In').value, 10);
    // TODO: Update misc info from form input
    // TODO: Update options from form input

    document.getElementsByTagName('body')[0].setAttribute('onmousemove', 'cgevent.mousemove(event)');
//    $('deity').value = text.deity_;
    this.listDeity('deity_list');
    this.listRaceClass(chargen.Races, 'race', 'race', 'cgevent.onBaseRaceChange(event);');
    this.listLevel('level');
    this.listRaceClass(chargen.Classes, 'class', 'hero', 'cgevent.onHeroicClassChange(event);');
    this.listParagon([], 'paragon', 'paragon');
  };

  /**
   Post char update hooks
   */
  cgevent.postUpdate = function (chr) {
    // Hook up stuffs for ui building and for hint showing
    for each (let slot in chr.powers) {
      if (slot.selection) slot.selection.slot = slot;
      if (!slot.hint) slot.__defineGetter__('hint', cgevent.slotHint);
    };
    for each (let slot in chr.feats) {
      if (slot.selection) slot.selection.slot = slot;
      if (!slot.hint) slot.__defineGetter__('hint', cgevent.slotHint);
    };
    for each (let slot in chr.equipments) {
      if (slot.selection) slot.selection.slot = slot;
      if (!slot.hint) slot.__defineGetter__('hint', cgevent.slotHint);
    };
  };


  /*
   Global, catch-it-all character update function
   This chargen works by recalculating all stats from base value (modified by ui)
   Although primitive and inefficient, as this is a pet project I concern more about maintainability
   I do try to avoid otherwise inefficient code.  Hope performance is not bad for you.  It is perfect for me.
  */
  cgevent.update = function() {
    var chargen = window.chargen;
    var text = window.text;
    var chr = chargen.char;

    // Rebuild character in response to changes
    window.chr = chr; // For easier hint building

    // Update character
    this.need_update = true;
//    cdebug(['need_update','manual ui call']);
    let updated = 0;
    let updates = {};
    while (this.need_update && ++updated < 10) {
      this.need_update = false;
      chr.update();
      chr._updates('updateValidations', chr); // Setup slot options and other validating stuffs
      this.postUpdate(chr);
      updates.feature_level = this.updateLv();
      updates.feature_race = this.updateRace(chargen.char.races.base);
      updates.feature_hero = this.updateClass(chargen.char.classes.heroic);
      updates.feats = this.updateSlot(new this.featSlotBuilder(chr));
      updates.powers = this.updateSlot(new this.powerSlotBuilder(chr));
      updates.equipments = this.updateEquipments(chr);
    }
    delete this.need_update;

    // Migrate changes to ui
//    document.body.style.display='none'; // Minimise reflow; disabled to prevent flashing
    for each (let [id, html] in Iterator(updates))
      if (html != this.temp[id])
        document.getElementById(id).innerHTML = this.temp[id] = html;
//    document.body.style.display='';

    // Generate summary
    var result = '';
    result += '<div class="f_right"><a href="#ui_raceclass" onclick="cgevent.ui_show_raceclass()">'
           +  text['race_'+chr.races.base.id] + ' '
           +  text['class_'+chr.classes.heroic.id] + ' '
           +  chr.lv + '</a></div>';
    result += '<a href="#ui_scores" onclick="setTimeout(\'$(\\\'strIn\\\').focus()\', 50)">';
    for each (let stat in chargen.STATS) result += text[stat]+' '+chr.abilities[stat]+' &nbsp;';
    result += '</a>';
    result += '<br>';
    // Stats
    result += text.hp+': '+chr.stats.hp + ' (HS ' + chr.stats.hshp.getValue() + ' x'+ chr.stats.hs.getValue() + ') | ';
    result += text.spd+': '+chr.stats.speed.getValue() + ' | ';
    result += text.init+': ' + chargen.System.modifier(chr.stats.initiative.getValue()) + ' || ';
    result += text.ac +': ' + chr.stats.ac.getValue() + ' |  '
            + text.fort+': '+chr.stats.fort.getValue() + ' | '
            + text.ref+': '+chr.stats.ref.getValue() + ' | '
            + text.will+': '+chr.stats.will.getValue();
    // Skills
    result += '<span class="narrow">';
    for each (let [skill,] in Iterator(chargen.Skills))
      result += '<div class="f_left">' + text['skill_'+skill] + ':' + chargen.System.modifier(chr.skills[skill].getValue()) + ' &nbsp; </div>';
    result += '</span>';
    // Summary
    result += '<div class="f_right">';
    result += '<a href="#ui_feats">'  + text.section_Feats + ' ' + chr.counts.feats_count + '/' + chr.counts.feats_total + '</a>';
    result += ' &nbsp; ';
    result += '<a href="#ui_powers">' + text.section_Powers + ' ' + chr.counts.powers_count + '/' + chr.counts.powers_total + '</a>';
    result += ' &nbsp; ';
    result += '<a href="#ui_equipment">' + text.section_Equipments + ' ' + chr.counts.equipments_count + '</a>';


    $('summary').innerHTML = result;

    // Calculate ability score point buy cost
    var total = 0;
    var min = 99;
    var scores = [];
    for each (let stat in chargen.STATS) {
      let score = chr.abilities[stat+'Base'];
      if (score < min) min = score;
      scores.push(score);
      if (cgevent.PTBUY[score])
        total += cgevent.PTBUY[score];
      $(stat).innerHTML = chr.abilities[stat] + '<br>(' + chargen.System.modifier(chr[stat+'Mod']) + ')';
    }
    if (min > 8) total += Math.min(2, min - 8);
    $('ptbuy').value = $('ptbuy').innerHTML = total;
    if (scores.sort().toSource().replace(/ /g,'') == '[10,11,12,13,14,16]') {
      hide('ui_ptbuy');
      show('ui_standard_array');
    } else {
      hide('ui_standard_array');
      show('ui_ptbuy');
    }
  };