/** Text-based single-space sheet, including plain text and bbc options */

sheets.SafeText = {
  id: 'SafeText',

  options: {

  },

  // Common functions
  prepost: sheets.System.prepost,
  glue: chargen.System.glue,
  leftpad: sheets.System.leftpad,
  textualise: chargen.System.textualise,
  modifier: chargen.System.modifier,

  // Main generator
  generate: function (chr) {
    var result = '';
    result += sheets.System.htmlheader(this.glue([
      text['class_'+chr.classes.heroic.id] + ' ' + chr.lv + ', ' + text['race_'+chr.races.base.id],
      chr.misc.name
      ], ' - '), 'body {font-family: lucida console, monospace;}');

    result += this.genHeader(chr) + this.linebreak;
    result += this.genMisc(chr) + this.linebreak + this.linebreak;
    result += this.genStat(chr) + this.linebreak + this.linebreak;
    result += this.genBaseAtk(chr) + this.linebreak + this.linebreak;
    result += this.genPowers(chr) + this.linebreak + this.linebreak;
    result += this.genSkill(chr) + this.linebreak + this.linebreak;
    result += this.genFeatures(chr) + this.linebreak + this.linebreak;
    result += this.genFeats(chr) + this.linebreak + this.linebreak;
    result += this.genEquipments(chr) + this.linebreak + this.linebreak;

    result += sheets.System.htmlfooter();

    return result;
  },

  // Title: "Celia Vanillin --- Human Wizard 8 --- $2240, xp 14520"
  genHeader: function(chr) {
    var text = window.text;

    return this.glue([
      chr.misc.name,

      text['race_'+chr.races.base.id] + ' ' +
      text['class_'+chr.classes.heroic.id] + ' ' +
      chr.lv,

      this.glue([
        this.prepost('$',chr.misc.money),//, ' '+text.gp),
        this.prepost(text.xp+' ', chr.misc.xp)
      ])
    ], this.separator);
  },

  /* Good Female, Ioun ----- Medium, Height 5'7", Weight 93 lb
   * (Desc)
   * Normal Vision, Perception 17, Insight 17
   * Languages Common, Deep, Draconic, Giant, Primordal
   */
  genMisc: function(chr) {
    var result = '';
    var text = window.text;

    result = this.glue([
      text['alignment_'+chr.alignment] + (chr.misc.deity ? ' ('+chr.misc.deity+')' : ''),
      text[chr.misc.gender]
    ]);
    result += this.long_separator;
    result += this.glue([
      text['size_' + chargen.SIZE[chr.size || 0]],
      this.prepost(text.Sheets.height+': ', chr.misc.height),
      this.prepost(text.Sheets.weight+': ', chr.misc.weight)
    ]);
    result += this.linebreak;

    if (chr.misc.desc) result += chr.misc.desc + this.linebreak + this.linebreak;

    result += text['vision_'+chargen.VISION[chr.vision]] + ', '
      + text.skill_perception + ': ' + (10+(+chr.skills.perception.getValue())) + ', '
      + text.skill_insight + ': ' + (10+(+chr.skills.insight.getValue())) + ', '
      + this.linebreak
      ;

    result += text.languages + ': ';
    // Flatten lang
    let lang = [ l for each ([l, state] in Iterator(chr.languages)) if (state >= chargen.LIST.SELECTED) ];
    // And list them in alphabatical order
    result += this.glue(this.textualise('language_', lang).sort());
    // Get scripts
    result += ' ('+this.glue(this.textualise('script_', sheets.System.getScriptsFromLangs(chr.languages).sort()))+')';
    result += this.linebreak;

    result += text.speed + ': ' + chr.stats.speed.getValue() + ', '
      + text.init + ': ' + chargen.System.modifier(chr.stats.initiative.getValue())
      + this.linebreak
      ;

    return result;
  },

  /*
   * HP 50 ----- Bloodied 25 ----- Healing Surge 12 x 7
   * Str  8 -1 | Dex 13 +1 | Wis 13 +1
   * Con 12 +1 | Int 20 +5 | Cha 16 +3
   *  Fort 18 -+- Ref 22 --+- Will 22 -+- AC 22
   */
  genStat: function(chr) {
    var result = '';
    var text = window.text;
    var txt = text.Sheets.SafeText;

    result +=
        text.hp + ' ' + chr.stats.hp + this.long_separator
      + text.bloodied + ' ' + chr.stats.bloodied + this.long_separator
      + text.healingsurge + ' ' + chr.stats.hshp.getValue() + txt.healingsurge_connector + chr.stats.hs.getValue()
      + this.linebreak
      ;
    result += this.linebreak;

    let col1w = (''+Math.max(chr.strMod, chr.conMod)).length;
    let col2w = (''+Math.max(chr.dexMod, chr.intMod)).length;
    let col3w = (''+Math.max(chr.wisMod, chr.chaMod)).length;

    result +=
        txt.ability_separator[0]
      + this.genAbility('str', col1w) + txt.ability_separator[1]
      + this.genAbility('dex', col2w) + txt.ability_separator[2]
      + this.genAbility('wis', col3w)
      + this.linebreak
      ;
    result +=
        txt.ability_separator[0]
      + this.genAbility('con', col1w) + txt.ability_separator[1]
      + this.genAbility('int', col2w) + txt.ability_separator[2]
      + this.genAbility('cha', col3w)
      + this.linebreak
      ;
   result += this.linebreak;
   result +=
        txt.defense_separator[0]
      + text.fort + ' ' + chr.stats.fort.getValue() + txt.defense_separator[1]
      + text.ref  + ' ' + chr.stats.ref .getValue() + txt.defense_separator[2]
      + text.will + ' ' + chr.stats.will.getValue() + txt.defense_separator[3]
      + text.ac + ' ' + chr.stats.ac.getValue()
      + this.linebreak
      ;

    return result;
  },

  genAbility: function(ability, l) {
    return text[ability]
      + ' ' + ( chr.abilities[ability] ? this.leftpad('0', chr.abilities[ability], 2) : '--' )
      + ' ' + this.modifier(this.leftpad(chr[ability+'Mod'], l))
//      + ' ('+ this.leftpad(this.modifier(chr.skills[ability]), 3) + ')'
      ;
  },

  /* == Basic Attacks ==
   *  Str +3 | Con +5 | Dex +5 | Int +9 | Wis + 5 | Cha +7
   * Magic Missile ----- +11 vs Ref , 2d4+9 Force (17+2d8) Ranged 20
   * +2 Staff of the war mage ----- +5 vs AC , d8-1 (7+2d8)
   */
  genBaseAtk: function(ability) {
    var result = '';
    var text = window.text;
    var txt = text.Sheets.SafeText;

    result += this.seection_title_left + text.section_BasicAttacks + this.section_title_right
            + this.linebreak
            ;
    result += '&nbsp;';
    result += text.str + ' ' + chr.abilities.strAtk.getValue() + ' | ';
    result += text.con + ' ' + chr.abilities.conAtk.getValue() + ' | ';
    result += text.dex + ' ' + chr.abilities.dexAtk.getValue() + ' | ';
    result += text.int + ' ' + chr.abilities.intAtk.getValue() + ' | ';
    result += text.wis + ' ' + chr.abilities.wisAtk.getValue() + ' | ';
    result += text.cha + ' ' + chr.abilities.chaAtk.getValue() + this.linebreak;
    result += this.linebreak;

    let lines = [];
    for each (let attack in chr.attacks) {
      let line = '';
      line += attack.rules.title.getDesc(attack);
      line += ' : ';
      line += attack.dmg('', 'Value', txt.attack_connector);
      if (attack.normalrange || (attack.range && attack.range != 'weapon') ) {
        line += ' ';
        line += attack.normalrange || attack.range;
        if (attack.longrange)
          line += '/' + attack.longrange;
        line += '';
      }
      if (lines.indexOf(line) < 0) lines.push(line); // Eliminate duplicate lines
    }
    result += lines.join(this.linebreak);

    return result;
  },

  genFeatures: function(chr) {
    var result = '';
    var text = window.text;

    result += this.seection_title_left + text.section_Features + this.section_title_right
            + this.linebreak
            ;

    let features = [];
    let applied_features = [];
    for each (let type in ['features', 'feats', 'powers', 'equipments'])
      for each (let [name, slot] in Iterator(chr[type]))
        if (slot && slot.selection&& slot.selection.rules) {
          let rules = slot.selection.rules;
          if (rules.feature) {
            let f = rules.feature.getValue(slot.selection);
            if (f) features.push( {
              title: rules.title.getValue(slot.selection),
              type: f[0],
              feature: f.slice(2)
            } );
          } else if (type == 'features' || type == 'feats') {
            // Add to applied list if have any updating functions
            if (slot.selection.onSetPower || slot.selection.resetPower)
              applied_features.push(rules.title.getValue(slot.selection));
            else
              for (let fieldname in slot.selection)
                if (startsWith(fieldname, 'update')) {
                  applied_features.push(rules.title.getValue(slot.selection));
                  break;
                }
          }
        }

    if (!features) return '';

    features.sort(function(a,b){
      if (a.type == b.type)
        return a.feature > b.feature ? 1 : 0;
      else
        return a.type > b.type ? 1 : 0;
    } );

    let last_type = '?';
    for each (let feature in features) {
      if (feature.type != last_type) {
        last_type = feature.type;
        result += '----- ' + text['feature_type_'+last_type] + this.linebreak;
      }
      result += feature.title + ' : ' + feature.feature + this.linebreak;
    }

    if (applied_features.length) {
      result += this.linebreak + '----- ' + text['feature_type_applied'] + this.linebreak;
      result += this.glue(applied_features);
    }

    return result;
  },

  genFeats: function(chr) {
    var result = '';
    var text = window.text;

    result += this.seection_title_left + text.section_Feats + this.section_title_right
            + this.linebreak
            ;

    let count = 0;
    for each (let [name, slot] in Iterator(chr.feats))
      if (slot && slot.selection) {
        let rules = slot.selection.rules;
        result += slot.selection_name;// rules.title.getValue(slot.selection);
        result += ' : ';
        if (rules.summary) result += rules.summary.getValue(slot.selection);
        else result += rules.desc.getValue(slot.selection);
        result += this.linebreak;
        ++count;
      }

    if (count == 0) return '';

    return result;
  },

  genEquipments: function(chr) {
    var result = '';
    var text = window.text;

    result += this.seection_title_left + text.section_Equipments + this.section_title_right
            + this.linebreak
            ;

    let count = 0;
    for each (let [name, slot] in Iterator(chr.equipments))
      if (slot && slot.selection) {
        let rules = slot.selection.rules;
        result += text['slot_'+name] + ' : ';
        result += slot.selection_name + this.linebreak;
        ++count;
      }

    if (count == 0) return '';

    return result;
  },

  /* === At-wills ===
   *
   * >>> Stinking Cloud 5 [Zone] (Poison) - Wizard Implement
   * Burst 2 in Ranged 20
   * +11 vs Fort --> d10+7+1d6 poison (23+)
   */
  genPowers: function(chr) {
    var result = '';
    var last_type = '';
    var text = window.text;
    let equipments = chr.equipments;
    let updator = new cgevent.powerSlotBuilder(chr);

    result += this.linebreak;

    for each (slot in updator.show_order()) {
      if (!slot.selection) continue;

      if (slot.type != last_type) {
        result += this.linebreak
                + this.seection_title_left + text[slot.type+'_power'] + this.section_title_right
                + this.linebreak + this.linebreak + this.linebreak
                ;
        last_type = slot.type;
      }

      result += this.getPower(slot.selection, equipments) + this.linebreak;
    }
    return result;
  },

  POWER_PARTS: ['prerequisite','requirement', 'trigger',
                '', 'hit', 'miss','effect', 'sustain', 'remote',
                '2', 'hit2', 'miss2', 'effect2',
                '3', 'hit3', 'miss3', 'effect3',
                'weapon', 'special'],

  /* >>> Stinking Cloud <<< [Zone] (Poison) Wizard 5 Implement
   * Each creature in Burst 2 in Ranged 20
   *   (+1 Flaming silver sword)
   * +11 vs Fort --> d10+7+1d6 poison (23+)
   *   (+2 Elven high wand)
   * +12 vs Fort --> d10+7 poison (17+)
   * -- Effect: Zone block line of sight, creature entering or starting takes 1d10+7 Poison damage.
   * -- Remote: (Move) Zone can be moved up to 6 squares.
   * -- Sustain: (Minor) Zone lasts until end of next turn.
   */
  getPower: function(power, equipments) {
    var result = '';
    var text = window.text;
    var txt = text.Sheets.SafeText;
    let [eq, eq_type] = power.getUsable(equipments);

    let details = new Array();

    if (eq.length <= 1) {
      let e = (eq.length == 1) ? equipments[eq[0]].selection : undefined;
      power.setEq(e, eq_type[0] || undefined);
      details[0] = this.breakdownPower(power, e );
    } else {
      for (let i = 0; i < eq.length; i++) {
        let e = equipments[eq[i]].selection;
        power.setEq(e, eq_type[i]);
        details.push(this.breakdownPower(power, e));
      }
    }

    // Stinking Cloud -------- [Zone] (Poison)
    result += txt.power_separator[0] + power.rules.title.getValue(power);
    result += txt.power_separator[1];
    if (power.energy) result += ' ' + this.glue(this.textualise('', power.energy), ',') + '';
    if (power.effect) result += ' [' + this.glue(this.textualise('', power.effect), ',') + ']';
    // Wizard 5 Implement
    result += txt.power_separator[2] + text[power.owner];
    if (power.level && (+power.level == power.level)) result += ' ' + power.level;
    if (power.accessory) result += txt.power_separator[3] + this.glue(this.textualise('accessory_', power.accessory), ' ');
    result += txt.power_separator[4] + this.linebreak;

    let fields = ['target','damage','target2','damage2','target3','damage3'].concat(this.POWER_PARTS);
    if (details.length > 1) {
      let common = [];
      let different = [];
      let l = details.length;
      for each (let field in fields) {
        let match = details[0][field] || undefined;
        for (let i = 1; i < l; i++) {
          let f = details[i][field] || undefined;
          if (match != f) {
            match = false;
            different.push(field);
            break;
          }
        }
        if (match && match !== false) common.push(field);
      }
      if (different.length <= 0) {
        details[0].title = [d.title for each (d in details) if (d.title)].join(', ');
        details = [details[0]];
      }
      result += this.genPowerSection(details, common, different);
    } else {
      result += this.genPowerSection(details, intersectArray(fields, keys(details[0])), []);
    }

    result += txt.power_section_separator[3] + this.linebreak;

    power.setEq(); // reset power for correct mouseover hint
    return result;
  },

  // Power body for solo or different choices
  genPowerSection: function(details, common, different) {
    var result = '';
    var text = window.text;
    var txt = text.Sheets.SafeText;

    var firstline = [];
    if (details[0].type == 'utility') firstline.push(text['frequency_'+details[0].frequency]);
    if (details[0].action != 'standard') firstline.push(text['action_'+details[0].action]);
    clog(common);

    for each (let f in ['target', 'target2', 'target3'])
      if (common.indexOf(f) >= 0) {
        firstline.push(details[0][f]);
        common.splice(common.indexOf(f), 1);
      }
    if (firstline.length) result = firstline.join(', ') + this.linebreak;

    for each (let f in ['damage3', 'damage2', 'damage'])
      if (different.indexOf(f) < 0) different.unshift(f);

    for each (let detail in details) {
      if (detail.title) result += txt.power_section_separator[0] + detail.title + txt.power_section_separator[1] + this.linebreak;
      for each (let field in different)
        if (detail[field]) {
          if (startsWith(field, 'target'))
            result += detail[field] + this.linebreak;
          else if (startsWith(field, 'damage')) {
            result += detail[field] + this.linebreak;
          } else
            result += text['hint_part_'+field] + ': ' + detail[field] + this.linebreak;
        }
    }

    let part2 = '';
    for each (let field in common) {
      if (details[0][field] && !startsWith(field, 'damage'))
        part2 += text['hint_part_'+field] + ': ' + details[0][field] + this.linebreak;
    }
    for each (let conditional in details[0].conditional)
      part2 += conditional.title + ': ' + conditional.text + this.linebreak;
    if (part2 && result.split(this.linebreak).length > 2 ) part2 = txt.power_section_separator[2] + part2;
    result += part2;

    return result;
  },

  /** Break a power down into different display components for easier comparison & display */
  breakdownPower: function(power, eq) {
    var result = { conditional: [] };
    var text = window.text;
    var tmp = undefined;

    if (eq)
      result.title = eq.rules.title.getValue(power);

    result.type = power.type;
    result.frequency = power.frequency;
    result.action = power.action;

    for each (let part in this.POWER_PARTS) {
      if (!part || +part == part) {
        // Burst 2 in Ranged 20
        tmp = power.tar ? power.tar(part, 'Value') : undefined;
        if (tmp) result['target'+part] = tmp;
        // +12 vs Fort --> d10+7 poison (17+)
        tmp = power.dmg ? power.dmg(part, 'Value', ' --&gt; ') : undefined;
        if (tmp) result['damage'+part] = tmp;
      } else {
        if (power.rules[part])
          result[part] = power.rules[part].getValue(power);
      }
    }

    for (let fieldname in power.rules)
      if (startsWith(fieldname, 'if_')) {
        let [group, feature] = fieldname.slice(3).split('_', 2);
        let rules = chargen.Features[group][feature].prototype.rules;
        let [title, txt] = [rules.title.getValue(power), power.rules[fieldname].getValue(power)];
        if (txt) result.conditional.push({ title: title, text: txt });
      }
    return result;
  },

  /* == Skills ==
   * + 8 . Acrobatics
   * +16 T Arcana
   */
  genSkill: function(ability) {
    var result = '';
    var text = window.text;
    result +=
        this.seection_title_left + text.section_Skills + this.section_title_right
      + this.linebreak
      ;
    for each (let [skill, ability] in Iterator(chargen.Skills)) {
      let tool = ' ';
      let trained = chr.trainedSkills[skill] >= chargen.LIST.SELECTED ? 'T' : '.';
      let rank = chr.skills[skill].getValue();
      if (rank == '') rank = 0;
      result += ( rank >= 0 ? ('+' + this.leftpad(rank, 2)) : ('-' + this.leftpad(-rank, 2)) )
             + tool + trained
             + ' ' +text['skill_'+skill] + this.linebreak;
    }
    return result;
  },


  separator: ' --- ',
  long_separator: ' ----- ',
  seection_title_left: '=== ',
  section_title_right: ' ===',
  linebreak: '<br>'
}