/* Initial / generic interface builders. */


/**************** Interface builders **************/

/*
 * Create level list and ability up boxes by tier.
 */
cgevent.updateLv = function() {
  var result = '';
  var chr = chargen.char;
  var text = window.text;
  var stat = chargen.STATS;
  var lv = [0, 4, 8, 14, 18, 24, 28, 34, 38]; // Ability up level

  let ability_ups = chr.abilities.ups;
  let ability_up_count = ability_ups.length/2;
  if (ability_up_count)
    result += '<div hint="ability_up_desc"><label class="f_left">'+text['ability_up']+' &nbsp;</label>';
  for (let i=1; i <= ability_up_count; i++) {
    let au_index = (i-1)*2;
    if (!ability_ups[au_index]) {
      ability_ups[au_index] = 'str';
      ability_ups[au_index+1] = 'con';
      cgevent.need_update = true;
//        cdebug(['need_update','ability',au_index]);
    }
    let choice = ability_ups[au_index]+','+ability_ups[au_index+1];
    result += '<label class="f_left">'+lv[i]+': <select hint="ability_up_desc" id="ability_up_'+i+'" onchange="cgevent.setAbilityUp('+i+',event,\''+choice+'\');cgevent.update();">';
    for (let j = 0; j < 6; j++)
      for (let k = j+1; k < 6; k++) {
        let value = stat[j]+','+stat[k];
        result += '<option value="'+value+'"'
        if (value == choice) result += ' selected';
        result += '>'+text[stat[j]]+' &amp; '+text[stat[k]]+'</option>';
      }
    result += "</select></label>";
  }
  if (ability_up_count)
    result += '</div>';

  return result+'<div class="f_stop"></div>';
};

cgevent.regx_enabledSelectedList = /type="checkbox">/g;
/*
 * Show race options, i.e. language options and features
 */
cgevent.updateRace = function(race) {
  let result = '';
  let lang_count = 0, total = 0;
  var text = window.text;

  [result, , lang_count, , total] = this.buildSelectableList('language_', chr.languages, 'cgevent.onLanguageSelect');

  if (lang_count >= chr.counts.language_count) {
    result = result.replace(cgevent.regx_enabledSelectedList, 'type="checkbox" disabled>');
  }
  lang_count += total;
  total += chr.counts.language_count;
  let count = '<span id="lang_count">'+lang_count+'</span>/'+total+' ';
  if (lang_count > total) count = '<span class="error">' + count + '</span>';

  result = '<div><span class="f_left">'+text['languages']+' '+ count + ' &nbsp;</span>' +
           result+'</div><div class="f_stop"></div>';

  // Get features below langs
  var temp = this.updateFeatures(race);
  if (temp.length > 6)
    result += '<div>'+temp+'</div>';

  return result;
};

/*
 * Show class options, i.e. skill options and features
 */
cgevent.updateClass = function(cls) {
  let result = '';
  let skill_count = 0, total = 0;
  var text = window.text;

  [result, , skill_count, , total] = this.buildSelectableList('skill_', chr.trainedSkills, 'cgevent.onSkillSelect', chr.skills);

  if (skill_count >= cls.skill_slot) {
    result = result.replace(cgevent.regx_enabledSelectedList, 'type="checkbox" disabled>');
  }/* else if (skill_count == 0 && total >= cls.skill_slot*4/5) {
    // Select for user if less click this way. Disabled because not user friendly.
    // 'Select all' checkbox is better, but not worth the effort
    result = result.replace(cgevent.regx_disabledSelectedList, 'type="checkbox" checked>');
    for each (let [skill,] in Iterator(chr.trainedSkills))
      cgevent.onSkillSelect({target:{value:skill,checked:true}});
    cgevent.need_update = true;
  }*/
  skill_count += total;
  total += cls.skill_slot;
  let count = '<span id="skill_slot">'+skill_count+'</span>/'+total+' ';
  if (skill_count > total) count = '<span class="error">' + count + '</span>';

  result = '<div><span class="f_left">' + text['skills'] + ' ' + count + ' &nbsp;</span>' +
           result+'</div>';
  result += '<div class="f_stop"></div>';

  // Get features below skills
  let temp = this.updateFeatures(cls);
  if (temp.length > 6)
    result += '<div>'+temp+'</div>';

  return result;
};


/*
 * Show current proficiencies and body slots
 */
cgevent.updateEquipments = function(chr) {
  var text = window.text;
  var result = '';

  // Proficiencies
  let [temp] = this.buildSelectableList(chargen.Equipments.BaseArmour, chr.armour, 'cgevent.onDoNothing', 'chargen.Equipments.BaseArmour');
  result += '<tr><td>' + text['armour_prof'] + '</td><td>' + temp + '</td></tr>';

  [temp] = this.buildSelectableList(chargen.Equipments.BaseWeapon, chr.proficiencies, 'cgevent.onDoNothing', 'chargen.Equipments.BaseWeapon');
  result += '<tr><td>' + text['weapon_prof'] + '</td><td>' + temp + '</td></tr>';

  // Eq slots
  result +=  this.updateSlot(new this.equipmentSlotBuilder(chr));

  // Basic attacks
  result += '<tr><td>';
  result += text.section_BasicAttacks;
  result += '</td><td>';

  let lines = [];
  for each (let attack in chr.attacks) {
    let line = '';
    attack.setEq();
    line += attack.rules.title.getDesc(attack);
    line += ' &nbsp; &nbsp; ';
    line += attack.dmg('', 'Desc', ' &nbsp; &nbsp; ');
    if (attack.normalrange || (attack.range && attack.range != 'weapon') ) {
      line += ' &nbsp; &nbsp; ';
      line += attack.normalrange || attack.range;
      if (attack.longrange)
        line += '/' + attack.longrange;
    }
    if (lines.indexOf(line) < 0) lines.push(line); // Eliminate duplicate lines
  }
  result += lines.join('<br>');
  result += '</td></tr>';

  return result;
}




/*
 * Generic slot UI builder
 */
cgevent.updateSlot = function(updator) {
  var result = '';
  var text = window.text;
  var chr = chargen.char;
  let accesskey = updator.accessKey;

  let show_order = updator.show_order();

  for each (let slot in show_order) {
    let opt = '';
    let item = slot.selection || null;
    if (item && item.setEq) item.setEq();
    let hint = updator.getSlotHint(slot);
    let summary = updator.getSummary(slot);

    // Fixed, unpickable slot
    if (item && slot.noOptions) {
      opt = this.buildOptions(item);
      slot.selection_name = item.rules.title.getDesc(item);
      if (item.choice) {
        let txtkey = item.options instanceof Array ? item.choice : item.options[item.choice];
        slot.selection_name += ' (' + ( item.text[txtkey] || text[txtkey] || txtkey ) + ')' ;
      }
      result += '<tr'+updator.getSlotClass(slot)+'><td'+hint+'><span>'
              + slot.selection_name
              + '</span></td><td'+hint+'>'
              + '<div class="f_right">' + opt +'</div>' + summary
              + '</td></tr>';
    } else {
      // Unfixed slot
      let list = slot.options;
      let valid_count = item ? 1 : 0;
      // Check & remove invalid feat
      if (item && (list.indexOf(chargen[updator.Types][item.group][item.id]) < 0 || (item.validate && !item.validate(chr, slot)) || (slot.validate && !slot.validate(item, chr)) ) ) {
        slot.selection = item = null;
        delete slot.selection;
        cgevent.need_update = true;
        return; // Doesn't matter now
      }

      // Sort pickable items by group and check that we do have something to pick.
      let noshow_list = updator.noshow_list();
      let showlist = {};
      let count = 0;
      for each (let p in list) {
        let proto = p.prototype;
        if (noshow_list.indexOf(proto.id) >= 0) continue;
        else if ( (slot.validate && !slot.validate(proto, chr)) ) continue;
        let owner = proto.owner;
        if (!showlist[owner]) showlist[owner] = [];
        showlist[owner].push(proto);
        ++count;
      }
      if (!slot.selection && !count) continue; // No point showing an empty list

      // Show slot name / picked item as first choice
      let slot_result = '';
      slot_result += '<tr'+updator.getSlotClass(slot)+'><td'+hint+'><select onchange="'+updator.getSlotChangeHandler(slot)+'"';
      if (accesskey) { slot_result += ' accesskey="'+accesskey+'"'; accesskey=null; }
      if (!item) slot_result += ' class="pickme"';
      slot_result += '>';
      if (!item)
        slot_result += '<option value="">'+updator.getSlotPickText(slot)+'</option>';
      else {
        opt = this.buildOptions(item);
        slot.selection_name = item.rules.title.getDesc(item);
        if (item.choice) {
          let txtkey = item.options instanceof Array ? item.choice : item.options[item.choice];
          slot.selection_name += ' (' + ( item.text[txtkey] || text[txtkey] || txtkey ) + ')' ;
        }
        slot_result += '<option value="" checked '
                + ' hint="cgevent.generateHint(chargen.'+updator.Types+'.'+item.group+'.'+item.id+'.prototype);"'+'>'
                + slot.selection_name + '</option>';
        slot_result += '<option value="">' + text.unselect + '</option>';
      }

      // Make a final sort of pickable items and show
      showlist = updator.sortGroupedItemList(showlist);
      for each (let [group, items] in Iterator(showlist)) {
        let group_count = 0;
        let group_result = '';
        group_result += '<optgroup label="'+text[group]+'">';
        for each (let p in items) {
          let fullname = p.group+'.'+p.id;
          let disabled = (p.validate && !p.validate(chr, slot));
          if (!disabled) { ++valid_count; ++group_count; }
          group_result += '<option value="'+fullname+'"'
                  + (disabled ? ' disabled' : '')
                  + updator.getOptionClass(slot, p)
                  + ' hint="cgevent.generateHint(chargen.'+updator.Types+'.'+fullname+'.prototype);"'
                  + '>'+updator.formatOption(slot, p)+'</option>';
        }
        group_result += '</optgroup>';
        if (group_count) slot_result += group_result;
      }

      if (valid_count) {
        slot_result += '</select></td><td'+hint+'>';
        if (opt) slot_result += '<div'+hint+' class="f_right">' + opt + '</div>';
        if (summary) slot_result += summary;
        slot_result += '</td></tr>';
      } else {
        slot_result = '';
      }
      result += slot_result;
    }
  }

  return result;
};

/* Updator for use with feat slots */
cgevent.featSlotBuilder = function(chr) { this.chr = chr; };
let (p = cgevent.featSlotBuilder.prototype) {
  p.accessKey = 'F';
  p.Types = 'Feats';
  p.noshow_list = function() {
    return [slot.selection.id for each (slot in this.chr.feats) if (slot.selection && !slot.selection.multi)];
  };
  p.show_order = function() {
    return [slot for each (slot in chr.feats)].sort(this.sortFeatSlot);
  };
  p.getSlot = function(name) { return chr.feats[name]; }
  p.getSlotHint = function(slot) { return ' hint="chargen.char.feats.' + slot.id + ';"'; }
  p.getSlotClass = function(slot) { return ''; }
  p.getSummary = function(slot) {
    if (!slot.selection) return '';
    let summary = '';
    let feat = slot.selection;
    if (feat.text.summary) summary = feat.text.summary;
    else if (feat.text.feature) summary = feat.text.feature.slice(2);
    else summary = feat.text.desc;
    return chargen.Rule.parse(summary).getDesc(p);
  }
  p.getSlotChangeHandler = function(slot){ return "cgevent.setFPE('feat','"+slot.id+"',this.value); this.blur(); cgevent.update();"; }
  p.getSlotPickText = function(slot){ return window.text['choice']; }
  p.sortGroupedItemList = function(list){ return list; }
  p.getOptionClass = function(slot, option) { return ''; }
  p.formatOption = function(slot, option) { return option.rules.title.getDesc(option); }
  /* Custom power sorting function */
  p.sortFeatSlot = function(a, b) {
    if (a.noOptions && !b.noOptions) return -1;
    else if (!a.noOptions && b.noOptions) return 1;
    let aid = a.noOptions ? a.selection.text.title : a.id;
    let bid = b.noOptions ? b.selection.text.title : b.id;
    return aid > bid ? 1 : (aid < bid ? -1 : 0);
  };
}

/* Updator for use with power slots */
cgevent.powerSlotBuilder = function(chr) { this.chr = chr; };
let (p = cgevent.powerSlotBuilder.prototype) {
  p.accessKey = 'P';
  p.Types = 'Powers';
  p.noshow_list = function() {
    return [slot.selection.id for each (slot in this.chr.powers) if (slot.selection)];
  };
  p.show_order = function() {
    return [slot for each (slot in chr.powers)].sort(this.sortPowerSlot);
  };
  p.getSlot = function(name) { return chr.powers[name]; }
  p.getSlotHint = function(slot) { return ' hint="chargen.char.powers.' + slot.id + ';"'; }
  p.getSlotClass = function(slot) {
    if (slot.selection) {
      let power = slot.selection
      return ' class="'+(power.type == 'utility' ? (power.frequency+'_utility') : (slot.type + '_power'))+'"';
    } else {
      return ' class="' + slot.type + '_power"';
    }
  }
  p.getSummary = function(slot) { return slot.selection ? cgevent.buildPowerSummary(slot.selection) : (slot.level || '') ; }
  p.getSlotChangeHandler = function(slot){ return "cgevent.setFPE('power','"+slot.id+"',this.value); this.blur(); cgevent.update();"; }
  p.getSlotPickText = function(slot){ return window.text[slot.type+'_power']; }
  p.sortGroupedItemList = function(list){
    for each (let [pgroup, powers] in Iterator(list))
      list[pgroup] = powers.sort(this.sortPower); // Sort powers in-group
    return list;
  }
  p.getOptionClass = function(slot, option) { return ''; }
  p.formatOption = function(slot, option) { return option.level + ' ' + option.text.title; }
  /* Custom power sorting function */
  p.sortPowerSlot = function(a, b) {
    var ascore = cgevent.POWER_RANK[a.type] + (a.noOptions ? 0 : 50);
    var bscore = cgevent.POWER_RANK[b.type] + (b.noOptions ? 0 : 50);
    if (ascore != bscore) return ascore - bscore;
    let aid = a.noOptions ? a.selection.text.title : a.id;
    let bid = b.noOptions ? b.selection.text.title : b.id;
    return aid > bid ? 1 : (aid < bid ? -1 : 0);
  };
  /* Sort selectable power list */
  p.sortPower = function(a, b) {
    if (a.level && a.level == b.level) {
      let aid = a.text.title || a.id;
      let bid = b.text.title || b.id;
      return aid > bid ? 1 : (aid < bid ? -1 : 0);
    } else {
      return b.level - a.level; // Higher level goes first
    }
  };
}
/* Custom power diplay rank */
cgevent.POWER_RANK = { atwill:100, encounter:200, daily:300, utility:400 };

/* Updator for use with equipment slots */
cgevent.equipmentSlotBuilder = function(chr) { this.chr = chr; };
let (p = cgevent.equipmentSlotBuilder.prototype) {
  p.accessKey = 'E';
  p.Types = 'Equipments';
  p.noshow_list = function() { return []; };
  p.show_order = function() {
    return [slot for each (slot in chr.equipments)]; // This order is fine, thanks
  };
  p.getSlot = function(name) { return chr.equipments[name]; }
  p.getSlotHint = function(slot) { return ' hint="chargen.char.equipments.' + slot.id + ';"'; }
  p.getSlotClass = function(slot) { return ''; }
  p.getSummary = function(slot) { return slot.selection ? cgevent.buildEquipmentSummary(slot.selection, chr) : ''; }
  p.getSlotChangeHandler = function(slot){ return "cgevent.setFPE('equipment','"+slot.id+"',this.value); this.blur(); cgevent.update();"; }
  p.getSlotPickText = function(slot){ return window.text['slot_'+slot.type]; }
  p.sortGroupedItemList = function(list){ return list; }
  p.getOptionClass = function(slot, option) {
    if (!option.checkProficiency) return '';
    else return option.checkProficiency(chr) ? '' : ' class="unproficient"';
  }
  p.formatOption = function(slot, option) { return option.text.title; }
}


/**************** Interface component builders **************/

/* Sort a selectable list */
cgevent.selectableListSort = function(a, b) {
  if ( a.status >= chargen.LIST.MIXED || b.status == chargen.LIST.MIXED ||
       a.status <= chargen.LIST.UNAVAILABLE || b.status <= chargen.LIST.UNAVAILABLE )
    if (a.status == b.status)
      return a.text > b.text ? 1 : (a.text < b.text ? -1 : 0);
    else
      return a.status - b.status;
  else
    return a.text > b.text ? 1 : (a.text < b.text ? -1 : 0);
};

/** Build a list of checkbox according to list
   * @prefix text prefix (string) or base group of prototype object (e.g. chargen.Equipments.BaseArmour). Group will not be sorted.
   * @list Associated list of item -> chargen.LIST to be rendered.
   * @handler name of onclick event
   * @reference if text prefix, an associated list to be displayed after each item; otherwise, string path to the base group
   *  */
cgevent.buildSelectableList = function(prefix, list, handler, reference) {
  let result = '';
  let available_count = 0, selected_count = 0, mixed_count = 0, mandatory_count = 0, total_count = 0;
  var text = window.text;
  if (!prefix) prefix = '';

  if (typeof prefix == 'string')
    var sorted = [ { item:item, status:status, text:text[prefix+item] }
                  for each ([item, status] in Iterator(list))
                  if (status >= chargen.LIST.AVAILABLE) ].sort(cgevent.selectableListSort);
  else
    var sorted = [ { item:item, status:status, text:prefix[item].prototype.text.title }
                  for each ([item, status] in Iterator(list))
                  if (status >= chargen.LIST.AVAILABLE) ];
  //sorted = sorted.sort(cgevent.selectableListSort);
  for each (let i in sorted) {
    let item = i.item;
    let status = i.status;
    let txt = i.text;
    let hint = (typeof prefix == 'string') ? prefix + item + '_desc' : 'cgevent.generateHint(' + reference + '[\'' + item + '\'].prototype);';
    result += '<label class="f_left" hint="' + hint + '">'
            + '<input value="'+item+'" onchange="'+handler+'(event);cgevent.update();" type="checkbox"';
    ++ total_count;
    if (status >= chargen.LIST.SELECTED) {
      result += ' checked';
    }
    if (status >= chargen.LIST.MIXED || status <= chargen.LIST.UNAVAILABLE) {
      result += ' disabled';
      if (status == chargen.LIST.MIXED) {
        // Custom style doesn't work in Firefox *sigh*
      }
      if (status == chargen.LIST.MIXED) ++ mixed_count;
      else if (status == chargen.LIST.MANDATORY) ++ mandatory_count;
    } else if (status == chargen.LIST.SELECTED ) ++ selected_count;
      else if (status == chargen.LIST.AVAILABLE) ++ available_count;
    result += '><span>' + txt;
    if (reference && reference[item] !== undefined) {
      result += ' '+chargen.System.modifier(reference[item].getValue());
    }
    result += '</span> &nbsp;';
  }
  return [result, available_count, selected_count, mixed_count, mandatory_count, total_count];
};


/*
 * Generate race/class/feat/feature options
 * target: something with 'features' object array
 */
cgevent.updateFeatures = function(target) {
  let result = '';
  let features = target.features;
  let dynamic = target.dynamic_features || [];
  if (features) {
    let feature_list = chargen.char.features;
    for each (let feature in features.concat(dynamic) ) {
      if (!feature_list[feature.prototype.id]) continue;
      feature = feature_list[feature.prototype.id].selection;
      result += '<div hint="cgevent.generateHint(chargen.char.features.'+feature.id+'.selection);" class="f_left side_padding">'
              + (this.buildOptions(feature) || text.Features[feature.group][feature.id].title ) + '</div>';
    }
  }
  return result+'<div class="f_stop"></div>';
};


/* Sort by item's text property */
cgevent.textSort = function(a, b) {
  return a.text > b.text ? 1 : (a.text < b.text ? -1 : 0);
};

/*
 Build select input out of options
 */
cgevent.buildOptions = function(subject) {
  var text = window.text;

  let id = subject.id;
  let type = subject._type;
  var result = '';

  // Reusable strings
  let hint = type=='feature' ? ' hint="cgevent.generateHint(chargen.Features.'+subject.group+'.'+id+'.prototype);"' : '';
  let Types = chargen.System.cap(type)+'s';
  let route = 'chargen.char.'+Types.toLowerCase()+'.'+(subject.slot ? subject.slot.id : subject.id)+'.selection';

  // Build options select
  for each (let i in chargen.CHOICES) {
    let options = subject['options'+i] || null;
    if (options) {
      let choice = subject['choice'+i] || null;
      result += '<label'+hint+'>' + ( subject.text['options'+i] || subject.rules.title.getValue(subject) )
              + ': <select onchange="' + route + '.choice'+i+'=this.value; this.blur(); cgevent.update();">';
      for each (let [value,txtkey] in Iterator(options)) {
        if (options instanceof Array) value = txtkey;
        result += '<option value="' + value + '"' + (choice == value?' selected':'') + '>'
        result += subject.text[txtkey] || text[txtkey] || txtkey;
        result += '</option>';
      }
      result += '</select></label>';

      // No choice? Set first option as choice
      if (!choice)
        for each (let [value,txtkey] in Iterator(options)) {
          subject['choice'+i] = (options instanceof Array) ? options[value] : value;
          cgevent.need_update = true;
          break;
        }
    }
  }
  // Done
  return result;
};




/**************** Interface initialisers **************/

/*
 Fill deity list
 */
cgevent.listDeity = function(id) {
  let result = '';
  var text = window.text;

  for each (let [book, gods] in Iterator(chargen.DEITIES)) {
    if (book) result += '<optgroup label="'+text['book_'+book]+'">';
    for each (let god in gods) {
      result += '<option value="'+god+'" hint="deity_'+god+'_desc">'
      result += (text['deity_'+god+'_icon'] || '') + ' '
      result += text['deity_'+god]+'</option>';
    }
    if (book) result += '</optgroup>';
  }

  $(id).innerHTML = result;
};


/*
 Fill select of given id with level list
 */
cgevent.listLevel = function(id) {
  let result = '';
  var text = window.text;
  // Level list
  result += '<a class="tabButton" href="#" onclick="return cgevent.hideTabs(\'level\', \'lv_h\');" accesskey="L">'+text['heroic']+'</a><span id="lv_h" class="tabOption">';
  for (let i =  1; i <= 10; i++) result += '<label><input type="radio" name="lv" value="'+i+(i==1?'" checked':'"')+' onchange="chargen.char.lv=+this.value; cgevent.update();"><span>'+i+'</span>&nbsp;</label>';
  result += '</span>';
  result += '<a class="tabButton" href="#" onclick="return cgevent.hideTabs(\'level\', \'lv_p\');">'+text['paragon']+'</a><span id="lv_p" class="tabOption" style="display:none">';
  for (let i = 11; i <= 20; i++) result += '<label><input type="radio" name="lv" value="'+i+'" onchange="chargen.char.lv=+this.value; cgevent.update();"><span>'+i+'</span>&nbsp;</label>';
  result += '</span>';
  result += '<a class="tabButton" href="#" onclick="return cgevent.hideTabs(\'level\', \'lv_e\');">'+text['epic']+'</a><span id="lv_e" class="tabOption" style="display:none">';
  for (let i = 21; i <= 30; i++) result += '<label><input type="radio" name="lv" value="'+i+'" onchange="chargen.char.lv=+this.value; cgevent.update();"><span>'+i+'</span>&nbsp;</label>';
  result += '</span>';
  $(id).innerHTML = result;
};

/*
 Fill select of given id with race / class list
  this.listRaceClass(chargen.Races, 'race', 'race', 'cgevent.onBaseRaceChange(event);');
 */
cgevent.listRaceClass = function(list, prefix, id, onchange) {
  var result = '';
  var text = window.text;
  let i = -1;
  let accesskey = (prefix == 'race') ? 'R' : (prefix == 'class') ? 'H' : '';

  let grouped_list = { phb:[] };
  for each (let rc in list) {
    let p = rc.prototype;
    if (!grouped_list[p.book]) {
      grouped_list[p.book] = [];
    }
    grouped_list[p.book].push(rc);
  }

  for each (let [book, list] in Iterator(grouped_list)) {
    result += '<a class="tabButton" href="#" onclick="return cgevent.hideTabs(\''+id+'\', \''+prefix+'_'+book+'\');"';
    if (accesskey) result += ' accesskey="'+accesskey+'"';
    result += '>'+text['book_'+book]+'</a><span class="tabOption" id="'+prefix+'_'+book+'"';
    if (accesskey) {
      accesskey = null;
    } else {
      result += ' style="display:none"';
    }
    result += '>';
    for each (let rc in list) {
      let p = rc.prototype;
      result += '<label hint="'+prefix+'_'+p.id+'_desc"><input type="radio" name="'+id+'_choice" value="'+p.id+'"'+(++i==0?(' checked="checked"'):'')+' onchange="'+onchange+'cgevent.update();"><span>'+text[prefix+'_'+p.id]+'</span> &nbsp;</label> ';
    }
    result += '</span>';
  }

  $(id).innerHTML = result;
};

/*
 Fill select of given id with race list
 */
cgevent.listParagon = function(list, prefix, id) {
  let result = '';
  var text = window.text;

  if (!chargen.char || chargen.char.lv < 11)
    result += '<div><div class="tier">&nbsp;</div><label><input type="radio" name="paragon_choice" value="" checked/><span>'+text['no_paragon']+'</span></label></div>';
  result += '<div><div class="tier">'+text['paragon']+': </div>&nbsp;';
  for each (let paragon in list) {
    if (typeof(paragon) == 'function') {
      result += '<label><input type="radio" name="paragon" value="'+paragon.prototype.id+'" checked/><span>'+text[prefix+'_'+paragon.prototype.id]+'</span></label>';
    }
  }
  result += '</div>';

  this.listRaceClass(chargen.Classes, 'class', id, 'cgevent.onParagonChange(event);');
  result += '<div><div class="tier">'+text['heroic']+': </div>'
            + $(id).innerHTML.replace(' checked','').replace(/value=/g, 'disabled="disabled" value=')
            + '</div>';

  $(id).innerHTML = result;
};