text: "use strict";
  
  let collection = {};
  let names      = {};
  let types      = new Map();
  
  const Files   = require('@util/files');
  const Content = require('@app/content')(collection, names, types);
  const Combat  = require('@app/combat');
  const Random  = require('@util/random');
  
  const FLAGS              = require('@constants').FLAGS;
  const PROPERTIES         = require('@constants').PROPERTIES;
  const CHARACTER_STATE    = require('@constants').CHARACTER_STATE;
  const FIGHT_ACTIONS      = require('@constants').FIGHT_ACTIONS;
  const BURN_BONUS         = require('@constants').BURN_BONUS;
  
  // Spells should be better than normal damage because you're expending a resource
  // This is how much that resource is effectively worth.
  const SPELL_DAMAGE_BONUS = 1.75;
  
  /**
   * Base spell class.
   */
  class Spell {
    constructor(info) {
      this.type               = _.get(info, 'type', '');
      this._displayName       = _.get(info, 'displayName', '');
      this._description       = _.get(info, 'description', '');
      this.school             = _.get(info, 'school', '');
      this._mpCost            = _.get(info, 'mpCost', 0);
      this.minDamage          = _.get(info, 'minDamage', 0);
      this.maxDamage          = _.get(info, 'maxDamage', 0);
      this.gold               = _.get(info, 'gold', 0);
      this.canCastFighting    = _.get(info, 'canCastFighting', false);
      this.doesDamage         = _.get(info, 'doesDamage', false);
      this.properties         = _.get(info, 'properties', []);
      this.redisplaySpellMenu = _.get(info, 'redisplaySpellMenu', true);
    }
  
    /**
     * Gets all the names this spell can go by, including aliases.
     */
    get allNames() {
      return [this._displayName];
    }
  
    /**
     * Get the display name of this spell.
     *
     * @param {Character} character - The character getting the display name of this spell.
     *
     * @return {string}
     */
    getDisplayName(character) {
      return this._displayName;
    }
  
    /**
     * Get the description for this spell.
     * Allows for custom logic on a per-character basis.
     *
     * @param {Character} character - The character getting the description.
     *
     * @return {array}
     */
    getDescription(character) {
      return this._description;
    }
  
    /**
     * Get the MP cost of casting this spell.
     *
     * @param {Character} character - The character casting the spell.
     *
     * @return {integer}
     */
    getMpCost(character) {
      // Damaging spells are free when battlecasting
      if (character.hasFlag(FLAGS.IS_BATTLECASTING) && this.doesDamage) {
        return 0;
      }
  
      return (character.hasFlag(FLAGS.IS_FOCUSED) && this.doesDamage)
        ? Math.ceil(this._mpCost * 1.5)
        : this._mpCost;
    }
  
    /**
     * Get the cost to purchase this spell.
     *
     * @param {Character} character - The character looking to buy this spell.
     *
     * @return {integer}
     */
    getCost(character) {
      return Math.ceil(this.gold * character.location.spellCostMultiplier);
    }
  
    /**
     * Get the description of the cost to purchase this item.
     *
     * @param {Character} character - The character looking to buy this item.
     *
     * @return {string}
     */
    getCostDescription(character) {
      return `${this.getCost(character)}g`;
    }
  
    /**
     * If the provided character can afford this spell.
     *
     * @param {Character} character - The character looking to buy the spell.
     * @param {integer} quantity - The quantity of this spell to purchase.
     *
     * @return {boolean}
     */
    canBePurchasedBy(character, quantity = 1) {
      return character.gold >= (this.gold * quantity);
    }
  
    /**
     * Subtract the cost of this spell from the provided character.
     *
     * @param {Character} character - The character buying the spell.
     * @param {integer} quantity - The quantity of this spell to purchase.
     */
    subtractCostFrom(character, quantity = 1) {
      if ( ! this.canBePurchasedBy(character)) {
        throw new Error(`Character cannot afford ${this.type}.`);
      }
  
      character.gold -= (this.gold * quantity);
    }
  
    /**
     * Cast this spell, dawg!  Returns an array of messages explaining what happened.
     *
     * @param {Character} character - The character casting the spell.
     *
     * @return {string} Message generated by casting the spell.
     */
    cast(character) {
      if (CHARACTER_STATE.IDLE === character.state) {
        return this.castIdle(character);
      }
      else if (CHARACTER_STATE.ENCOUNTER === character.state) {
        return this.castEncounter(character);
      }
      else if (CHARACTER_STATE.FIGHTING === character.state) {
        const info = {
          action: FIGHT_ACTIONS.CAST,
          spell: this.type
        };
        Combat.fight(info, character);
        return "";
      }
      else {
        throw new Error("Attempting to cast a spell in an invalid state!");
      }
    }
  
    /**
     * Get the damage this spell does.
     *
     * @param {Character} character - The character casting the spell.
     *
     * @return {integer}
     */
    getSpellDamage(character) {
      const caster = character, target = character.enemy;
  
      let damage = this.getRandomDamage(this.minDamage, this.maxDamage);
  
      // Damage Absorption
      damage *= caster.getDamageAbsorption(Math.ceil(caster.spellPower * SPELL_DAMAGE_BONUS), target.defence);
  
      // Focused
      damage *= caster.hasFlag(FLAGS.IS_FOCUSED) ? 2 : 1;
  
      // Burn
      damage *= this.properties.includes(PROPERTIES.BURN_ATTACK) && target.hasFlag(FLAGS.BURNED_TURNS) ? BURN_BONUS : 1;
  
      return Math.max(1, Math.floor(damage));
    }
  
    /**
     * Get random spell damage between two numbers.
     *
     * @param {integer} min - The minimum amount of damage to inflict.
     * @param {integer} max - The maximum amount of damage to inflict.
     *
     * @return {integer}
     */
    getRandomDamage(min, max) {
      return Random.between(this.minDamage, this.maxDamage);
    }
  
    /**
     * Cast a spell while in idle state (usually has to do with location).
     *
     * @param {Character} character - The character casting the spell.
     *
     * @return {string} Message generated by casting this spell.
     */
    castIdle(character) {
      return `Casting ${this.getDisplayName(character)} won't help right now.`;
    }
  
    /**
     * Cast a spell while in an encounter.
     *
     * @param {Character} character - The character casting the spell.
     *
     * @return {string} Message generated by casting this spell.
     */
    castEncounter(character) {
      return character.encounter.castSpell(this, character);
    }
  
    /**
     * Cast a spell while in combat.
     *
     * @param {Character} character - The character casting the spell.
     *
     * @return {array} Any messages generated while casting.
     */
    castFighting(character) {
      character.mp -= this.getMpCost(character);
      character.stamina--;
  
      return [`:confused: You cast ${this.getDisplayName(character)}, but it seems to have no effect.  (-${this.getMpCost(character)} MP)`];
    }
  }
  
  /**
   * Utility class for searching and creating new spell objects.
   */
  class Spells extends Content {}
  
  module.exports = {
    Spell,
    Spells
  };
  
  /**
   * @type array The collection of spells.
   */
  Files.loadContent(`${CONTENT_FILES_PATH}/spells/`, `${CONTENT_FILES_PATH}/spells/`, collection);
  
  /**
   * @type object The names of the spells, for quick reference.
   */
  Files.getNames(collection, names);
  
  /**
   * @type objec The types of the spells, keyed by name, for quick reference.
   */
  Files.getTypes(collection, types);
  