/* -------------------------------------------- */

import { DialogCreateSigneDraconique } from "./dialog-create-signedraconique.js";
import { RdDItemCompetence } from "./item-competence.js";
import { Misc } from "./misc.js";
import { RdDCarac } from "./rdd-carac.js";
import { RdDDice } from "./rdd-dice.js";
import { RdDNameGen } from "./rdd-namegen.js";
import { RdDResolutionTable } from "./rdd-resolution-table.js";
import { RdDRollResolutionTable } from "./rdd-roll-resolution-table.js";
import { RdDRollTables } from "./rdd-rolltables.js";
import { RdDUtility } from "./rdd-utility.js";
import { TMRRencontres } from "./tmr-rencontres.js";
import { TMRUtility } from "./tmr-utility.js";

const rddRollNumeric = /^(\d+)\s*([\+\-]?\d+)?\s*(s)?/;

/* -------------------------------------------- */
export class RdDCommands {

  static init() {
    if (!game.system.rdd.commands) {
      const rddCommands = new RdDCommands();
      rddCommands.registerCommand({ path: ["/aide"], func: (content, msg, params) => rddCommands.help(msg), descr: "Affiche l'aide pour toutes les commandes" });
      rddCommands.registerCommand({ path: ["/help"], func: (content, msg, params) => rddCommands.help(msg), descr: "Affiche l'aide pour toutes les commandes" });
      rddCommands.registerCommand({ path: ["/table", "queues"], func: (content, msg, params) => RdDRollTables.getQueue(true), descr: "Tire une Queue de Dragon" });
      rddCommands.registerCommand({ path: ["/table", "ideefixe"], func: (content, msg, params) => RdDRollTables.getIdeeFixe(true), descr: "Tire une Idée fixe" });
      rddCommands.registerCommand({ path: ["/table", "desir"], func: (content, msg, params) => RdDRollTables.getDesirLancinant(true), descr: "Tire un Désir Lancinant" });
      rddCommands.registerCommand({ path: ["/table", "ombre"], func: (content, msg, params) => RdDRollTables.getOmbre(true), descr: "Tire une Ombre de Dragon" });
      rddCommands.registerCommand({ path: ["/table", "tetehr"], func: (content, msg, params) => RdDRollTables.getTeteHR(true), descr: "Tire une Tête de Dragon pour Hauts Revants" });
      rddCommands.registerCommand({ path: ["/table", "tete"], func: (content, msg, params) => RdDRollTables.getTete(true), descr: "Tire une Tête de Dragon" });
      rddCommands.registerCommand({ path: ["/table", "souffle"], func: (content, msg, params) => RdDRollTables.getSouffle(true), descr: " Tire un Souffle de Dragon" });
      rddCommands.registerCommand({ path: ["/table", "comp"], func: (content, msg, params) => RdDRollTables.getCompetence(true), descr: "Tire une compétence au hasard" });
      rddCommands.registerCommand({ path: ["/table", "tarot"], func: (content, msg, params) => RdDRollTables.getTarot(true), descr: "Tire une carte du Tarot Draconique" });
      rddCommands.registerCommand({ path: ["/nom"], func: (content, msg, params) => RdDNameGen.getName(msg, params), descr: "Génère un nom aléatoire" });

      rddCommands.registerCommand({
        path: ["/tmra"], func: (content, msg, params) => rddCommands.getTMRAleatoire(msg, params),
        descr: `Tire une case aléatoire des Terres médianes
          <br><strong>/tmra forêt</strong> détermine une 'forêt' aléatoire
          <br><strong>/tmra</strong> détermine une case aléatoire dans toutes les TMR` });
      rddCommands.registerCommand({
        path: ["/tmrr"], func: (content, msg, params) => rddCommands.getRencontreTMR(params),
        descr: `Détermine une rencontre dans un type de case
          <br><strong>/tmrr foret</strong> lance un d100 et détermine la rencontre correspondante en 'forêt'
          <br><strong>/tmrr forêt 47</strong> détermine la rencontre en 'forêt' pour un jet de dé de 47`
      });

      rddCommands.registerCommand({
        path: ["/xp", "comp"], func: (content, msg, params) => rddCommands.getCoutXpComp(msg, params),
        descr: `Détermine le coût d'expérience pour augmenter une compétence. Exemples:
        <br>/xp comp -6 1: pour passer de -6 à +1
        <br>/xp comp +4: pour atteindre le niveau 4 (depuis +3)`
      });

      rddCommands.registerCommand({
        path: ["/xp", "carac"], func: (content, msg, params) => rddCommands.getCoutXpCarac(msg, params),
        descr: `Détermine le coût d'expérience pour augmenter une caractéristique. Exemples:
        <br>/xp carac 15: coût pour atteindre 15 (depuis 14)`
      });

      rddCommands.registerCommand({
        path: ["/rdd"], func: (content, msg, params) => rddCommands.rollRdd(msg, params),
        descr: `Effectue un jet de dés dans la table de résolution. Exemples:
          <br><strong>/rdd</strong> ouvre la table de résolution
          <br><strong>/rdd 10 3</strong> effectue un jet 10 à +3
          <br><strong>/rdd 15 -2</strong> effectue un jet 15 à -2
          <br><strong>/rdd 15 0 s</strong> effectue un jet 15 à 0, avec significative requise
          <br><strong>/rdd Vue Vigilance -2</strong> effectue un jet de Vue/Vigilance à -2 pour les tokens sélectionnés
          <br><strong>/rdd vol déser +2</strong> effectue un jet de Volonté/Survie en désert à +2 pour les tokens sélectionnés
          `
      });
      rddCommands.registerCommand({ path: ["/ddr"], func: (content, msg, params) => rddCommands.rollDeDraconique(msg), descr: "Lance un Dé Draconique" });

      rddCommands.registerCommand({
        path: ["/payer"], func: (content, msg, params) => RdDUtility.afficherDemandePayer(params[0], params[1]),
        descr: `Permet de payer un montant. Exemples:
          <br><strong>/payer 5s 10d</strong> permet d'envoyer un message pour payer 5 sols et 10 deniers
          <br><strong>/payer 10d</strong> permet d'envoyer un message pour payer 10 deniers`
      });
      rddCommands.registerCommand({
        path: ["/astro"], func: (content, msg, params) => RdDUtility.afficherHeuresChanceMalchance(RdDCommands.toParamString(params)),
        descr: `Affiche les heures de chance et de malchance selon l'heure de naissance donnée en argument. Exemples pour l'heure de la Lyre:
          <br><strong>/astro 7</strong>
          <br><strong>/astro Lyre</strong>
          <br><strong>/astro Lyr</strong>`
      });

      rddCommands.registerCommand({
        path: ["/signe", "+"], func: (content, msg, params) => rddCommands.creerSignesDraconiques(),
        descr: "Crée un signe draconique et l'ajoute aux haut-rêvants choisis."
      });

      rddCommands.registerCommand({
        path: ["/signe", "-"], func: (content, msg, params) => rddCommands.supprimerSignesDraconiquesEphemeres(),
        descr: "Supprime les signes draconiques éphémères"
      });

      rddCommands.registerCommand({
        path: ["/stress"], func: (content, msg, params) => RdDUtility.distribuerStress(params[0], params[1], params[2]),
        descr: `Distribue du stress aux personnages. Exemples:
          <br><strong>/stress 6</strong> : Distribue 6 points des Stress à tout les personnages joueurs, sans raison renseignée
          <br><strong>/stress 6 Tigre</strong> : Distribue 6 points des Stress à tout les personnages joueurs, à cause d'un Tigre Vert
          <br><strong>/stress 6 Glou Paulo</strong> : Distribue 6 points de Stres à l'acteur connecté au joueur Paulo, à cause d'un Glou`
      });

      game.system.rdd.commands = rddCommands;
    }
  }
  constructor() {
    this.commandsTable = {};
  }

  static toParamString(params) {
    return params.length == 1 ? params[0] : params.reduce((a, b) => `${a} ${b}`, '');
  }

  /* -------------------------------------------- */
  registerCommand(command) {
    this._addCommand(this.commandsTable, command.path, '', command);
  }

  /* -------------------------------------------- */
  _addCommand(targetTable, path, fullPath, command) {
    if (!this._validateCommand(targetTable, path, command)) {
      return;
    }
    const term = path[0];
    fullPath = fullPath + term + ' '
    if (path.length == 1) {
      command.descr = `<strong>${fullPath}</strong>: ${command.descr}`;
      targetTable[term] = command;
    }
    else {
      if (!targetTable[term]) {
        targetTable[term] = { subTable: {} };
      }
      this._addCommand(targetTable[term].subTable, path.slice(1), fullPath, command)
    }
  }

  /* -------------------------------------------- */
  _validateCommand(targetTable, path, command) {
    if (path.length > 0 && path[0] && command.descr && (path.length != 1 || targetTable[path[0]] == undefined)) {
      return true;
    }
    console.warn("RdDCommands._validateCommand failed ", targetTable, path, command);
    return false;
  }


  /* -------------------------------------------- */
  /* Manage chat commands */
  processChatCommand(commandLine, content, msg) {
    // Setup new message's visibility
    let rollMode = game.settings.get("core", "rollMode");
    if (["gmroll", "blindroll"].includes(rollMode)) msg["whisper"] = ChatMessage.getWhisperRecipients("GM");
    if (rollMode === "blindroll") msg["blind"] = true;
    msg["type"] = 0;

    let command = commandLine[0];
    let params = commandLine.slice(1);

    return this.process(command, params, content, msg);
  }

  process(command, params, content, msg) {
    return this._processCommand(this.commandsTable, command, params, content, msg);
  }

  _processCommand(commandsTable, name, params, content = '', msg = {}, path = "") {
    let command = commandsTable[name];
    path = path + name + " ";
    if (command && command.subTable) {
      if (params[0]) {
        return this._processCommand(command.subTable, params[0], params.slice(1), content, msg, path)
      }
      else {
        this.help(msg, command.subTable);
        return true;
      }
    }
    if (command && command.func) {
      const result = command.func(content, msg, params);
      if (result == false) {
        RdDCommands._chatAnswer(msg, command.descr);
      }
      return true;
    }
    return false;
  }

  /* -------------------------------------------- */
  async help(msg) {
    this.help(msg, undefined);
  }
  async help(msg, table) {
    let list = []
    this._buildSubTableHelp(list, table || this.commandsTable);

    let html = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/settings/dialog-aide-commands.html", { commands: list });
    let d = new Dialog(
      {
        title: "Commandes disponibles dans le tchat",
        content: html,
        buttons: {},
      },
      {
        width: 600, height: 500,
      });

    d.render(true);
  }

  /* -------------------------------------------- */
  static _chatAnswer(msg, content) {
    msg.whisper = [game.user.id];
    msg.content = content;
    ChatMessage.create(msg);
  }

  /* -------------------------------------------- */
  _buildSubTableHelp(list, table) {
    for (let [name, command] of Object.entries(table)) {
      if (command) {
        if (command.subTable) {
          this._buildSubTableHelp(list, command.subTable);
        } else {
          list.push(command.descr);
        }
      }
    }
    return list.sort();
  }

  /* -------------------------------------------- */
  async getRencontreTMR(params) {
    if (params.length == 1 || params.length == 2) {
      return TMRRencontres.rollRencontre(params[0], params[1])
    }
    else {
      return false;
    }
  }

  /* -------------------------------------------- */
  async rollRdd(msg, params) {
    if (params.length == 0) {
      RdDRollResolutionTable.open();
    }
    else {
      let flatParams = params.reduce((a, b) => `${a} ${b}`);
      const numericParams = flatParams.match(rddRollNumeric);
      if (numericParams) {
        const carac = Misc.toInt(numericParams[1]);
        const diff = Misc.toInt(numericParams[2] || 0);
        const significative = numericParams[3] == 's'
        await this.rollRdDNumeric(msg, carac, diff, significative);
        return;
      }

      let actors = canvas.tokens.controlled.map(it => it.actor).filter(it => it);
      if (actors && actors.length > 0) {
        let length = params.length;
        let diff = Number(params[length - 1]);
        if (Number.isInteger(Number(diff))) {
          length--;
        }
        else {
          diff = 0;
        }
        const caracName = params[0];
        const compName = length > 1 ? params.slice(1, length).reduce((a, b) => `${a} ${b}`) : undefined;
        for (let actor of actors) {
          await actor.rollCaracCompetence(caracName, compName, diff);
        }
        return;
      }
      else {
        ui.notifications.warn("Sélectionnez au moins un personnage pour lancer les dés")
      }
    }
  }

  /* -------------------------------------------- */
  async rollRdDNumeric(msg, carac, diff, significative = false) {
    let rollData = {
      caracValue: carac,
      finalLevel: diff,
      showDice: true,
      diviseurSignificative: significative ? 2 : 1,
      show: { title: "Table de résolution"}
    };
    await RdDResolutionTable.rollData(rollData);
    RdDCommands._chatAnswer(msg, await RdDResolutionTable.buildRollDataHtml(rollData));
  }

  /* -------------------------------------------- */
  async rollDeDraconique(msg) {
    let ddr = await RdDDice.rollTotal("1dr + 7", { showDice:true });
    RdDCommands._chatAnswer(msg, `Lancer d'un Dé draconique: ${ddr}`);
  }

  async getTMRAleatoire(msg, params) {
    if (params.length < 2) {
      let type = params[0];
      const tmr = await TMRUtility.getTMRAleatoire(type ? (it => it.type == type) : (it => true));
      RdDCommands._chatAnswer(msg, `Case aléatoire: ${tmr.coord} - ${tmr.label}`);
    }
    else {
      return false;
    }
  }


  /* -------------------------------------------- */
  getCoutXpComp(msg, params) {
    if (params && (params.length == 1 || params.length == 2)) {
      let to = params.length == 1 ? Number(params[0]) : Number(params[1]);
      let from = params.length == 1 ? to - 1 : Number(params[0]);
      RdDCommands._chatAnswer(msg, `Coût pour passer une compétence de ${from} à ${to}: ${RdDItemCompetence.getDeltaXp(from, to)}`);
    }
    else {
      return false;
    }
  }

  /* -------------------------------------------- */
  getCoutXpCarac(msg, params) {
    if (params && params.length == 1) {
      let to = Number(params[0]);
      RdDCommands._chatAnswer(msg, `Coût pour passer une caractéristique de ${to - 1} à ${to}: ${RdDCarac.getCaracXp(to)}`);
    }
    else {
      return false;
    }
  }

  async creerSignesDraconiques() {
    DialogCreateSigneDraconique.createSigneForActors();
    return true;
  }

  async supprimerSignesDraconiquesEphemeres() {
    game.actors.forEach(actor => {
      const ephemeres = actor.filterItems(item => Misc.data(item).type = 'signedraconique' && Misc.data(item).data.ephemere)
        .map(item => item.id);
      if (ephemeres.length > 0) {
        actor.deleteEmbeddedDocuments("Item", ephemeres);
      }
    });
    return true;
  }
}