'use strict';

const uuid = require('uuid/v4');
const Attributes = require('./Attributes');
const Character = require('./Character');
const Config = require('./Config');
const Logger = require('./Logger');
const Scriptable = require('./Scriptable');
const CommandQueue = require('./CommandQueue');

/**
 * @property {number} id   Area-relative id (vnum)
 * @property {Area}   area Area npc belongs to (not necessarily the area they're currently in)
 * @property {Map} behaviors
 * @extends Character
 * @mixes Scriptable
 */
class Npc extends Scriptable(Character) {
  constructor(area, data) {
    super(data);
    const validate = ['keywords', 'name', 'id'];

    for (const prop of validate) {
      if (!(prop in data)) {
        throw new ReferenceError(`NPC in area [${area.name}] missing required property [${prop}]`);
      }
    }

    this.area = data.area;
    this.script = data.script;
    this.behaviors = new Map(Object.entries(data.behaviors || {}));
    this.equipment = new Map();
    this.defaultEquipment = data.equipment || {};
    this.defaultItems = data.items || [];
    this.description = data.description;
    this.entityReference = data.entityReference; 
    this.id = data.id;
    this.keywords = data.keywords;
    this.quests = data.quests || [];
    this.uuid = data.uuid || uuid();
    this.commandQueue = new CommandQueue();
  }

  /**
   * Move the npc to the given room, emitting events appropriately
   * @param {Room} nextRoom
   * @param {function} onMoved Function to run after the npc is moved to the next room but before enter events are fired
   * @fires Room#npcLeave
   * @fires Room#npcEnter
   * @fires Npc#enterRoom
   */
  moveTo(nextRoom, onMoved = _ => _) {
    const prevRoom = this.room;
    if (this.room) {
      /**
       * @event Room#npcLeave
       * @param {Npc} npc
       * @param {Room} nextRoom
       */
      this.room.emit('npcLeave', this, nextRoom);
      this.room.removeNpc(this);
    }

    this.room = nextRoom;
    nextRoom.addNpc(this);

    onMoved();

    /**
     * @event Room#npcEnter
     * @param {Npc} npc
     * @param {Room} prevRoom
     */
    nextRoom.emit('npcEnter', this, prevRoom);
    /**
     * @event Npc#enterRoom
     * @param {Room} room
     */
    this.emit('enterRoom', nextRoom);
  }

  hydrate(state) {
    super.hydrate(state);
    state.MobManager.addMob(this);

    this.setupBehaviors(state.MobBehaviorManager);

    for (let defaultItemId of this.defaultItems) {
      Logger.verbose(`\tDIST: Adding item [${defaultItemId}] to npc [${this.name}]`);
      const newItem = state.ItemFactory.create(this.area, defaultItemId);
      newItem.hydrate(state);
      state.ItemManager.add(newItem);
      this.addItem(newItem);
    }

    for (let [slot, defaultEqId] of Object.entries(this.defaultEquipment)) {
      Logger.verbose(`\tDIST: Equipping item [${defaultEqId}] to npc [${this.name}] in slot [${slot}]`);
      const newItem = state.ItemFactory.create(this.area, defaultEqId);
      newItem.hydrate(state);
      state.ItemManager.add(newItem);
      this.equip(newItem, slot);
    }
  }

  get isNpc() {
    return true;
  }
}

module.exports = Npc;