chargen.System.Equipment = {
  eqBySlot: { offhand:[], natural:[], ranged:[] }, // Equpiment by slot.
  weaponByGroup: {}, // Weapon by weapon group

  initEquipment : function(pgroup, pname, p) {
    let base = p; // Base class
    if (typeof p == 'string') {
      let pos = p.indexOf(' ');
      let type = p.slice(0, pos);
      let summary = trim(p.slice(pos+1));
      p = function(){};
      chargen.Equipments[pgroup][pname] = base = p;
      p = p.prototype;
      p.id = pname;
      p.type = type;
      p.summary = summary;
      this['parse_'+type](base);
    } else {
      p = p.prototype;
      if (p._initialised) return;
      p.id = pname;
    }

    if (p._initialised) return;
    p._initialised = true;
    p._type = 'equipment';
    p._Types = 'Equipments';
    if (!p.book)  p.book   = 'phb';
    if (!p.group) p.group = pgroup;

    if (!p.level) p.level = 0;

    if (p.type == 'armour' || p.type == 'shield') {
      if (!p.owner) p.owner = 'base_armour';
      if (!p.check) p.check = 0;
      if (!p.speed) p.speed = 0;
      if (!p.checkProficiency) p.checkProficiency = this.armourCheckProficiency;
      if (!p.updateSkills) p.updateSkills = this.armourSkillsUpdator;
    }

    if (p.type == 'weapon') {
      if (p.classes.melee) {
        p.slot = 'melee';
        if (p.property.offhand)
          this.eqBySlot.offhand.push(base);
        if (p.classes.ranged)
          this.eqBySlot.ranged.push(base);
        if (p.classes.unarmed)
          this.eqBySlot.natural.push(base);
      } else if (p.classes.ranged) {
        p.slot = 'ranged';
      } else {
        p.slot = 'weapon';
//        console.warning("Weapon "+pname+" is unsloted.");
      }
      if (!p.checkProficiency) p.checkProficiency = this.weaponCheckProficiency;
      if (!p.validate) {
        p.validate = this.validateWeapon;
        p.validateCheckHands = this.validateCheckHands; // Used by validateWeapon()
      }
      if (!p.is) p.is = this.weaponCheckIs;
      if (!p.onSetPower) p.onSetPower = this.weaponSetPower;
      if (!p.updateAttacks) p.updateAttacks = this.weaponAddBasicAttack;
      if (!p.adjustSize) p.adjustSize = this.weaponAdjustSize;
    } else if (p.type == 'armour') {
      if (!p.slot) p.slot = 'body';
      if (!p.updateFFPStats) p.updateFFPStats = this.armourStatsUpdator;

    } else if (p.type == 'shield') {
      if (!p.slot) p.slot = 'arms';
      if (!p.hand) p.hand = 1;
      if (!p.updateFFPStats) p.updateFFPStats = this.shieldStatsUpdator;
    }

    if (p.hand && !p.validate) p.validate = this.validateCheckHands;

    if (!p.slot) p.slot = 'misc';
    if (!p.price) p.price = 0;

    if (!this.eqBySlot[p.slot]) this.eqBySlot[p.slot] = [];
    this.eqBySlot[p.slot].push(base);

    if (!p.text) p.text = text.Equipment[pgroup][pname];
    if (!p.rules) p.rules = chargen.Rule.parseAll(p.text, p);
    if (!p.reset) p.reset = this.reset;
    if (p.init) p.init();
  },

  /* Reset eq stat to initial, unadjusted state */
  reset: function() {
    this.text = cloneObject(text.Equipment[this.group][this.id]);
    this.rules = chargen.Rule.parseAll(this.text, this);
    if (this.critical) this.rules.critical = chargen.Rule.parse(this.critical);
    let proto = chargen.Equipments[this.group][this.id].prototype;
    for each (let field in ['damage','normalrange','longrange'])
      if (proto[field]) this[field] = proto[field];
  },

  regx_Weapon: /(\w+\s+\w+)\s+(one|two)\s*(\+\d+)\s*(\d*d\d+)\s*(\d+\/\d+)?\s*(\$\d+)?\s*(\d+\.?\d?lb)?\s*(.*)/,
  regx_Spaces: /\s+/,
  // Weapon property list, used to dististuish weapon group and property keywords
  list_WeaponProp: ['heavythrown','lightthrown','highcrit','loadfree','loadminor','offhand','reach','small','versatile'],
  list_WeaponGroup: ['axe', 'bow', 'crossbow', 'flail', 'hammer', 'heavyblade', 'lightblade', 'mace', 'pick', 'polearm', 'sling', 'spear', 'staff', 'unarmed'],
  // Weapon summary parser
  parse_weapon: function(base) {
    let p = base.prototype;
    let summary = p.summary;
    // "superior melee one +3 1d10 10/20 $30 6lb heavyblade versatile";
    let [, category, hand, proficiency, damage, range, price, weight, keywords] = this.regx_Weapon.exec(summary);
    if (!p.category) p.category = category;
    let [cls1, cls2] = p.category.split(this.regx_Spaces);
    if (!p.owner) p.owner = 'weapon_' + cls1;
    if (!p.proficiency) p.proficiency = +proficiency.slice(1);
    if (!p.damage) p.damage = damage;
    if (range && !p.normalrange) p.normalrange = range.split('/')[0];
    if (range && !p.longrange) p.longrange = range.split('/')[1];
    if (price && !p.price) p.price = price;
    if (weight && !p.weight) p.weight = weight;
    if (keywords && (!p.property || !p.classes) ) {
      keywords = keywords.split(this.regx_Spaces);
      if (!p.property) p.property = {};
      if (!p.classes) p.classes = {};
      if (!p.weapon_group) p.weapon_group = {};
      p.classes[cls1] = p.classes[cls2] = true;
      for each (let word in keywords) {
        if (this.list_WeaponProp.indexOf(word) >= 0)
          p.property[word] = true;
        else if (this.list_WeaponGroup.indexOf(word) >= 0)
          p.classes[word] = p.weapon_group[word] = true;
        else
          p.classes[word] = true;
      }
      // Additional prop
      if (p.property.lightthrown || p.property.heavythrown) p.classes.thrown = p.classes.ranged = true;
      for each (let word in keys(p.property).concat(keys(p.classes))) {
        if (!this.weaponByGroup[word]) this.weaponByGroup[word] = [];
        this.weaponByGroup[word].push(p);
      }
      // Prop special processing
      if (p.property.highcrit) p.critical = '+{{(1+chr.counts.tier)+"W";||weapon}}';
      if (p.property.versatile) {
        // Versatile can switch hands and need special handling with small size
        text.Equipment.BaseWeapon[p.id].options_handcount = text.hand_howmany;
        p.updateProfs = function(chr) {
          if (chr.stats.size < 0 && !this.is('small')) this.choice_handcount = 2;
        },
        p.__defineGetter__('options_handcount', function() {
          if (chr.stats.size < 0 && !this.is('small')) return undefined;
          else return { 1: 'hand_1_handed', 2: 'hand_2_handed' };
        });
        p.__defineGetter__('hand', function() { return +this.choice_handcount || ( (window.chr && chr.stats.size < 0 && !this.is('small')) ? 2 : 1) ; });
        if (p.onSetPower) {
          cerror('Event chain not implemented');
        }
        p.weaponSetPower = this.weaponSetPower;
        p.onSetPower = function(param) {
          this.weaponSetPower(param); // proficiency and damage rolls
          /*
          if (param.eq != this || this.choice_handcount < 2) return;
          if (chr.stats.size >= 0 || this.is('small'))
             chargen.System.Commons.addBonusToAll(param.power, 'hit_damage', 1, this, 'versatile');
             */
          if (this.choice_handcount >= 2 && (chr.stats.size >= 0 || this.is('small')) )
            chargen.System.Commons.addBonusForEq(param.power, 'hit_damage', 1, this, 'versatile', this);
        }
      } else {
        if (!p.hand) p.hand = hand == 'one' ? 1 : (hand == 'two') ? 2 : 0;
      }
    }
  },

  validateWeapon : function(chr, slot) {
    if (chr.stats.size < 0) {
      if ( this.hand >= 2 && !this.is('versatile') && !this.is('small') ) return false; // Small size
    }
    return this.validateCheckHands(chr, slot);
  },

  validateCheckHands : function(chr, slot) {
    let dhands = this.hand - (slot.selection ? slot.selection.hand : 0);
    if (slot.id == 'melee' || slot.id == 'offhand' || slot.id == 'arms')
      if (chr.counts.melee_hands + dhands > chr.counts.hands) return false;
    if (slot.id == 'ranged' || slot.id == 'arms')
      if (chr.counts.ranged_hands + dhands > chr.counts.hands) return false;
    return true;
  },

  weaponCheckProficiency : function(chr) {
    let p = chr.proficiencies;
    // simple melee, Longsword
    let prof = (p[this.category] && p[this.category] >= chargen.LIST.SELECTED) ||
               (p[this.id] && p[this.id] >= chargen.LIST.SELECTED);
    if (!prof) {
      // axe, lightblade
      for each (let [prof,] in Iterator(this.classes))
        if (p[prof] && p[prof] >= chargen.LIST.SELECTED) return true;
      // "simple lightblade"  If it gets more complicated I'll have to refactor the whole proficiency system someday...
      let cat = this.category.split(' ')[0];
      for each (let [prof,] in Iterator(this.classes)) // Not optimised.  Not worth the while...
        if (p[cat+' '+prof] && p[cat+' '+prof] >= chargen.LIST.SELECTED) return true;
      return false;
    } else
      return true;
  },
  weaponCheckIs: function(keyword) {
    keyword = keyword.toLowerCase();
    return this.property[keyword] || this.classes[keyword] || this.id.toLowerCase() == keyword;
  },

  weaponSetPower: function(param) {
//    if (this != param.eq) return;
    let power = param.power;
    if (power.accessory && power.accessory.indexOf('weapon') >= 0) {
      if (this.proficiency && this.checkProficiency(param.char)) {
        chargen.System.Commons.addBonusForEq(power, 'attack', this.proficiency, this, 'proficiency', this);
        // TODO: Enhancement bonus, blocked by multi-type bonus source
      }
      if (power.equipment == this && power.rules.hit_damage) power.rules.hit_damage.Op('set weapon', '*'+this.damage);
      if (power.equipment2 == this && power.rules.hit_damage2) power.rules.hit_damage2.Op('set weapon', '*'+this.damage);
      if (power.equipment3 == this && power.rules.hit_damage3) power.rules.hit_damage3.Op('set weapon', '*'+this.damage);
      if (power.equipment == this && power.rules.critical) power.rules.critical.Op('set weapon', '*'+this.damage);
    }
  },

  weaponAdjustSize: function(deviation) {
    if (deviation == 0) return;
    let table = chargen[(this.hand == 1 || this.is('versatile')) ? 'ONE_HANDED_WEAPON_DAMAGE' : 'TWO_HANDED_WEAPON_DAMAGE'];
    let index = table.indexOf(this.damage);
//    if (!index) { cerror('Cannot adjust damage dice of ' + this.rules.title.getDesc(this)); return; }
    if (index >= 0) index += deviation;
    this.damage = table[index];
  },

  weaponAddBasicAttack: function(chr) {
    if (this.is('melee'))
      chr.attacks.push(new chargen.System.Equipment.MeleeWeaponBasicAttack(this));
    if (this.is('ranged'))
      chr.attacks.push(new chargen.System.Equipment.RangedWeaponBasicAttack(this));
  },

  armourCheckProficiency : function(chr) {
    return chr.armour[this.id] >= chargen.LIST.SELECTED;
  },
  armourStatsUpdator : function(chr) {
    chr.stats.ac.addBonus(this.ac, this, 'armour');
    if (!this.checkProficiency(chr)) {
      for each (let stat in chargen.STATS) chr.abilities[stat+'Atk'].addBonus(-2, this, 'proficiency');
      chr.stats.ref.addBonus(-2, this, 'proficiency');
    }
    if (this.speed) chr.stats.speed.addBonus(this.speed, this, 'armour');
    if (this.category == 'heavy') {
      chr.stats.ac.removeBonus('dex', 'ability');
      chr.stats.ac.removeBonus('int', 'ability');
    }
  },
  shieldStatsUpdator : function(chr) {
    if (this.checkProficiency(chr)) {
      chr.stats.ac.addBonus(this.ac, this, 'shield');
      chr.stats.ref.addBonus(this.ref, this, 'shield');
    }
  },
  armourSkillsUpdator : function(chr) {
    if (this.check) for each (let skill in chargen.armourPenaltySkills) chr.skills[skill].addBonus(this.check, this, 'armour');
  }
};
let (eq = chargen.System.Equipment) {
  // Weapon basic attack object
  eq.WeaponBasicAttack = function(weapon) {
    this.equipment = weapon;
    this.critical = weapon.critical;
    this.hit_damage = weapon.damage+'+strM';
    this.title = weapon.rules.title;
  };
  eq.WeaponBasicAttack.prototype = {
    attack_type : 'melee',
    range : 'weapon',
    attack : 'strAtk',
    defense : 'ac',
    power : [],
    accessory : ['weapon'],
    energy : [],
    effect : [],
    reset : function() {
      this.rules = {
        title : this.title,
        hit_damage : chargen.Rule.parseDamage(this.hit_damage, this),
        attack : (this.char.abilities[this.attack]) ? cloneObject(this.char.abilities[this.attack]) : new chargen.Rule.Number(this.attack),
        defense : chargen.Rule.parse('{{'+this.defense+'}}'),
        critical : this.equipment.rules.critical || undefined
      };
    },
    setEq : function(eq) {
      this.reset();
      this.char._updates('resetPower', this );
      this.char._updates('onSetPower', {eq: this.equipment, char: this.char, power: this} );
    },
    tar : chargen.System.Power.shortcut_target,
    are : chargen.System.Power.shortcut_area,
    atk : chargen.System.Power.shortcut_attack,
    dmg : chargen.System.Power.shortcut_damage,
    hit : chargen.System.Power.shortcut_hit
  };
  eq.MeleeWeaponBasicAttack = function(weapon) {
    this.inheritFrom = eq.WeaponBasicAttack;
    this.inheritFrom(weapon);
  };
  eq.MeleeWeaponBasicAttack.prototype = eq.WeaponBasicAttack.prototype;
  eq.RangedWeaponBasicAttack = function(weapon) {
    this.inheritFrom = eq.WeaponBasicAttack;
    this.inheritFrom(weapon);
    this.attack_type = 'ranged';
    this.normalrange = weapon.normalrange;
    this.longrange = weapon.longrange;
    if (!weapon.is('heavythrown')) {
      this.hit_damage = weapon.damage+'+dexM';
      this.attack = 'dexAtk';
    };
  };
  eq.RangedWeaponBasicAttack.prototype = eq.WeaponBasicAttack.prototype;

  // Setup standard slots
  for each (let slot in chargen.EQ_SLOTS) {
    if (!eq[slot+'Slot']) {
      eq[slot+'Slot'] = function(){};
      eq[slot+'Slot'].prototype = {
        type  : slot,
        bonus_options : [],
        get options() {
          return eq.eqBySlot[this.type].concat(this.bonus_options);
        }
      };
    }
  };
}




let (eq = chargen.Equipments, text = window.text) {

  eq.BaseWeapon = {};
  eq.BaseArmour = {};
/*
  let con = chargen.Rule.Constant;
*/
  for each (let weapon in ['improvised melee', 'improvised ranged', 'simple melee', 'simple ranged', 'military melee', 'military ranged', 'superior melee', 'superior ranged',
    'axe','hammer','spear', 'military lightblade', 'military heavyblade']) {
    let txt = text['weapon_'+weapon];
    text.Equipment.BaseWeapon[weapon] = { title: txt };
    eq.BaseWeapon[weapon]   = { prototype: {
      _initialised: true,
      text: { title: txt },
      rules: { title: new chargen.Rule.Constant(txt) }
    } };
  }

  eq.BaseArmour.Cloth = function(){};
  let (p = eq.BaseArmour.Cloth.prototype) {
    p.type = 'armour';
    p.category = 'light';
    p.ac = 0;
    p.price = 1;
    p.weight = 4;
  }

  eq.BaseArmour.Leather = function(){};
  let (p = eq.BaseArmour.Leather.prototype) {
    p.type = 'armour';
    p.category = 'light';
    p.ac = 2;
    p.price = 25;
    p.weight = 15;
  }

  eq.BaseArmour.Hide = function(){};
  let (p = eq.BaseArmour.Hide.prototype) {
    p.type = 'armour';
    p.category = 'light';
    p.ac = 3;
    p.check = -1;
    p.price = 30;
    p.weight = 25;
  }

  eq.BaseArmour.Chain = function(){};
  let (p = eq.BaseArmour.Chain.prototype) {
    p.type = 'armour';
    p.category = 'heavy';
    p.ac = 6;
    p.check = -1;
    p.speed = -1;
    p.price = 40;
    p.weight = 40;
  }

  eq.BaseArmour.Scale = function(){};
  let (p = eq.BaseArmour.Scale.prototype) {
    p.type = 'armour';
    p.category = 'heavy';
    p.ac = 7;
    p.speed = -1;
    p.price = 45;
    p.weight = 45;
  }

  eq.BaseArmour.Plate = function(){};
  let (p = eq.BaseArmour.Plate.prototype) {
    p.type = 'armour';
    p.category = 'heavy';
    p.ac = 8;
    p.check = -2;
    p.speed = -1;
    p.price = 50;
    p.weight = 50;
  }

  eq.BaseArmour.LightShield = function(){};
  let (p = eq.BaseArmour.LightShield.prototype) {
    p.type = 'shield';
    p.category = 'light';
    p.ac = 1;
    p.ref = 1;
    p.price = 5;
    p.weight = 6;
  }

  eq.BaseArmour.HeavyShield = function(){};
  let (p = eq.BaseArmour.HeavyShield.prototype) {
    p.type = 'shield';
    p.category = 'heavy';
    p.ac = 2;
    p.ref = 2;
    p.check = -2;
    p.price = 10;
    p.weight = 15;
  }

  eq.BaseWeapon.Club = "weapon simple melee one +2 1d6 $1 3lb mace";
  eq.BaseWeapon.Dagger = "weapon simple melee one +3 1d4 5/10 $1 1lb lightblade offhand lightthrown";
  eq.BaseWeapon.Javelin = "weapon simple melee one +2 1d6 10/20 $5 2lb spear heavythrown";
  eq.BaseWeapon.Mace = "weapon simple melee one +2 1d8 $5 6lb mace versatile";
  eq.BaseWeapon.Sickle = "weapon simple melee one +2 1d6 $2 2lb lightblade offhand";
  eq.BaseWeapon.Spear = "weapon simple melee one +2 1d8 $5 6lb spear versatile";
  eq.BaseWeapon.Greatclub = "weapon simple melee two +2 2d4 $1 10lb mace";
  eq.BaseWeapon.Morningstar = "weapon simple melee two +2 1d10 $10 8lb mace";
  eq.BaseWeapon.Quarterstaff = "weapon simple melee two +2 1d8 $5 4lb staff";
  eq.BaseWeapon.Scythe = "weapon simple melee two +2 2d4 $5 10lb heavyblade";

  eq.BaseWeapon.Battleaxe = "weapon military melee one +2 1d10 $15 6lb axe versatile";
  eq.BaseWeapon.Flail = "weapon military melee one +2 1d10 $10 5lb flail versatile";
  eq.BaseWeapon.Handaxe = "weapon military melee one +2 1d6 5/10 $5 3lb axe offhand heavythrown";
  eq.BaseWeapon.Longsword = "weapon military melee one +3 1d8 $15 4lb heavyblade versatile";
  eq.BaseWeapon.Scimitar = "weapon military melee one +2 1d8 $10 4lb heavyblade highcrit";
  eq.BaseWeapon.ShortSword  = "weapon military melee one +3 1d6 $10 2lb lightblade offhand";
  eq.BaseWeapon.ThrowingHammer = "weapon military melee one +2 1d6 5/10 $5 2lb hammer offhand heavythrown";
  eq.BaseWeapon.Warhammer = "weapon military melee one +2 1d10 $15 5lb hammer versatile";
  eq.BaseWeapon.WarPick  = "weapon military melee one +2 1d6 $15 6lb pick highcrit versatile";

  eq.BaseWeapon.Falchion = "weapon military melee two +2 2d4 $25 7lb heavyblade highcrit";
  eq.BaseWeapon.Glaive = "weapon military melee two +2 2d4 $25 10lb heavyblade polearm reach";
  eq.BaseWeapon.Greataxe = "weapon military melee two +2 1d12 $30 12lb axe highcrit";
  eq.BaseWeapon.Greatsword = "weapon military melee two +3 1d10 $30 8lb heavyblade";
  eq.BaseWeapon.Halberd = "weapon military melee two +2 1d10 $25 12lb axe polearm reach";
  eq.BaseWeapon.HeavyFlail = "weapon military melee two +2 2d6 $25 10lb flail";
  eq.BaseWeapon.Longspear = "weapon military melee two +2 1d10 $10 9lb spear polearm reach";
  eq.BaseWeapon.Maul = "weapon military melee two +2 2d6 $30 12lb hammer";

  eq.BaseWeapon.BastardSword  = "weapon superior melee one +3 1d10 $30 6lb heavyblade versatile";
  eq.BaseWeapon.Katar  = "weapon superior melee one +3 1d6 $3 1lb lightblade offhand highcrit";
  eq.BaseWeapon.Rapier  = "weapon superior melee one +3 1d8 $25 2lb lightblade";
  eq.BaseWeapon.SpikedChain   = "weapon superior melee two +3 2d4 $30 10lb flail reach";

  eq.BaseWeapon.Unarmed = "weapon improvised melee one +0 1d4 unarmed";
  eq.BaseWeapon.AnyOne = "weapon improvised melee one +0 1d4 improvised";
  eq.BaseWeapon.AnyTwo = "weapon improvised melee two +0 1d8 improvised";

  eq.BaseWeapon.HandCrossbow  = "weapon simple ranged one +2 1d6 10/20 $25 2lb crossbow loadfree";
  eq.BaseWeapon.Sling = "weapon simple ranged one +2 1d6 10/20 $1 0lb sling loadfree";
  eq.BaseWeapon.Crossbow = "weapon simple ranged two +2 1d8 15/30 $25 4lb crossbow loadminor";
  eq.BaseWeapon.Longbow = "weapon military ranged two +2 1d10 20/40 $30 3lb bow loadfree";
  eq.BaseWeapon.Shortbow = "weapon military ranged two +2 1d8 15/30 $25 2lb bow loadfree small";
  eq.BaseWeapon.Shuriken = "weapon superior ranged one +3 1d4 6/12 $1 0.5lb lightblade lightthrown";
  eq.BaseWeapon.AnyLight = "weapon improvised ranged one +0 1d4 5/10 improvised lightthrown";
  eq.BaseWeapon.AnyHeavy = "weapon improvised ranged one +0 1d4 5/10 improvised heavythrown";
}