/**
 * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
 * @extends {Actor}
 */

import { RdDUtility } from "./rdd-utility.js";
import { TMRUtility } from "./tmr-utility.js";
import { RdDRollDialog } from "./rdd-roll-dialog.js";
import { RdDTMRDialog } from "./rdd-tmr-dialog.js";
import { Misc } from "./misc.js";

import { RdDResolutionTable } from "./rdd-resolution-table.js";
import { RdDDice } from "./rdd-dice.js";
import { RdDRollTables } from "./rdd-rolltables.js";

export class RdDActor extends Actor {

  /* -------------------------------------------- */
  /**
   * Override the create() function to provide additional RdD functionality.
   *
   * This overrided create() function adds initial items 
   * Namely: Basic skills, money, 
   *
   * @param {Object} data        Barebones actor data which this function adds onto.
   * @param {Object} options     (Unused) Additional options which customize the creation workflow.
   *
   */
   
  static async create(data, options) {
    
    // Case of compendium global import
    if (data instanceof Array)
      return super.create(data, options);
    // If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic
    if (data.items) {
      return super.create(data, options);
    }
    
    data.items = [];
    let compendiumName = "";
    if (data.type == "personnage") {
      compendiumName = "foundryvtt-reve-de-dragon.competences";
    } else if (data.type == "humanoide") {
      compendiumName = "foundryvtt-reve-de-dragon.competences-humanoides";
    } else if (data.type == "creature") {
      compendiumName = "foundryvtt-reve-de-dragon.competences-creatures";
    } else if (data.type == "entite") {
      compendiumName = "foundryvtt-reve-de-dragon.competences-entites";
    }
    let competences = [];
    const pack = game.packs.get(compendiumName);
    await pack.getIndex().then(index => competences = index);
    for (let comp of competences)
    {
      let compItem = undefined;
      await pack.getEntity(comp._id).then(skill => compItem = skill);
      data.items.push(compItem);
    }

    return super.create(data, options);
  }
  
  /* -------------------------------------------- */  
  

  /* -------------------------------------------- */  
  prepareData() {
    super.prepareData();

    const actorData = this.data;
    const data = actorData.data;
    const flags = actorData.flags;

    // Dynamic computing fields
    this.encombrementTotal = 0;

    // Make separate methods for each Actor type (character, npc, etc.) to keep
    // things organized.
    if (actorData.type === 'personnage') this._prepareCharacterData(actorData);
    if (actorData.type === 'creature') this.computeEtatGeneral(actorData);
    if (actorData.type === 'humanoide') this.computeEtatGeneral(actorData);
  }

  /* -------------------------------------------- */
  /**
   * Prepare Character type specific data
   */
  _prepareCharacterData(actorData) {
    // Initialize empty items
    RdDUtility.computeCarac(actorData.data);
    this.computeEncombrementTotal();
    this.computeEtatGeneral();
  }
  
  /* -------------------------------------------- */
  getReveActuel() {
    return this.data.data.reve.reve.value;
  }

  /* -------------------------------------------- */
  getBestDraconic() {
    const list = this.getDraconicList().sort((a, b) =>  b.data.niveau - a.data.niveau);
    if (list.length==0)
    {
      return { name: "none", niveau: -11 };
    }
    return duplicate(list[0]);
  }

  /* -------------------------------------------- */
  async deleteSortReserve(coordTMR) {
    let reserve = duplicate(this.data.data.reve.reserve);
    let len = reserve.list.length;
    let i = 0;
    let newTable = [];
    for( i=0; i < len; i++) {
      if (reserve.list[i].coord != coordTMR ) 
        newTable.push(reserve.list[i]);
    }
    if ( newTable.length != len ) {
      reserve.list = newTable;
      await this.update( {"data.reve.reserve": reserve } );    
    }
  }

  /* -------------------------------------------- */
  async performRoll(rollData) {
    let rolled = await RdDResolutionTable.roll(rollData.carac, rollData.finalLevel);
    //rolled.isPart = true; // Pour tester le particulières
    rollData.rolled = rolled; //  garder le résultat
    console.log("performRoll", rollData, rolled)
    if ( !rollData.attackerRoll) // Store in the registry if not a defense roll
      game.system.rdd.rollDataHandler[this.data._id] = rollData; 

    if (rolled.isPart && rollData.arme && !rollData.attackerRoll) { // Réussite particulière avec attaque -> choix !
      let message = "<strong>Réussite particulière en attaque</strong>";
      message = message + "<br><a class='chat-card-button' id='particuliere-attaque' data-mode='force' data-attackerid='" + this.data._id + "'>Attaquer en Force</a>";
      // Finesse et Rapidité seulement en mêlée et si la difficulté libre est de -1 minimum
      if (rollData.selectedCarac.label == "Mêlée" && rollData.diffLibre < 0 ) {
        message = message + "<br><a class='chat-card-button' id='particuliere-attaque' data-mode='rapidite' data-attackerid='"+ this.data._id + "'>Attaquer en Rapidité</a>";
        message = message + "<br><a class='chat-card-button' id='particuliere-attaque' data-mode='finesse' data-attackerid='"+ this.data._id + "'>Attaquer en Finesse</a>";
      }
      ChatMessage.create( {content : message, whisper: ChatMessage.getWhisperRecipients( this.name ) } );
    } else { 
      this.continueRoll(rollData);
    }
  }

  /* -------------------------------------------- */
  async continueRoll(rollData) {

    let rolled = rollData.rolled;
    let result = rolled.roll;
    let quality = rolled.quality

    // Manage weapon categories when parrying (cf. page 115 )
    let need_resist = false; // Do we need to make resistance roll for defender ?
    if (rollData.arme && rollData.attackerRoll) { // Manage parade depeding on weapon type, and change roll results
      let attCategory = RdDUtility.getArmeCategory(rollData.attackerRoll.arme);
      let defCategory = RdDUtility.getArmeCategory(rollData.arme);
      if (defCategory == "bouclier")
        rollData.needSignificative = false;
      else if (attCategory != defCategory)
        rollData.needSignificative = true;
      if (attCategory.match("epee") && (defCategory == "hache" || defCategory == "lance"))
        need_resist = true;
    }
    if (this.data.type != 'entite' && (this.data.data.sante.sonne.value || rollData.particuliereAttaque == "finesse")) {
      rollData.needSignificative = true;
    }

    console.log(">>> ROLL", rollData, rolled);
    let xpmsg = RdDResolutionTable.buildXpMessage(rolled, rollData.finalLevel);

    let resumeCompetence = (rollData.competence) ? rollData.competence.name : (rollData.diffLibre + rollData.diffConditions);
    let explications = "<br>Points de taches : " + rolled.ptTache + ", ajustement qualité: " + rolled.ptQualite;

    // Fight management !
    let defenseMsg;
    let encaisser = false;
    if (rollData.arme || (rollData.competence && rollData.competence.name.toLowerCase() == 'esquive') ) {
      explications = ""
       // In case of fight, replace the message per dommages + localization. it indicates if result is OK or not
      if (rollData.attackerRoll) { // Defense case !
        if (rollData.needSignificative && rolled.isSign ) {
          explications += "<br><strong>Attaque parée/esquivée !</strong>";
        } else if ( !rollData.needSignificative && rolled.isSuccess) {
          explications += "<br><strong>Attaque parée/esquivée !</strong>";
        } else  {
          explications += "<br><strong>Esquive/Parade échouée, encaissement !</strong>";
          encaisser = true;
        }
      } else { // This is the attack roll!
        if (rolled.isSuccess) {
          // Message spécial pour la rapidité, qui reste difficile à gérer automatiquement
          if ( rollData.particuliereAttaque == 'rapidite') {
            ChatMessage.create( { contet: "Vous avez attaqué en Rapidité. Ce cas n'est pas géré autmatiquement, donc suivez les directives de votre MJ pour gérer ce cas.", 
                                  whisper: ChatMessage.getWhisperRecipients( this.name ) } );
          }
          rollData.domArmePlusDom = this._calculBonusDommages(rollData.selectedCarac, rollData.arme, rollData.particuliereAttaque == 'force' );
          rollData.degats = new Roll("2d10").roll().total + rollData.domArmePlusDom;
          rollData.loc = RdDUtility.getLocalisation();
          for (let target of game.user.targets) {
            rollData.mortalite = (rollData.mortalite) ? rollData.mortalite : "mortel";// Force default
            rollData.mortalite = (target.actor.data.type == 'entite') ? "cauchemar" : rollData.mortalite;
            console.log("Mortalité : ", rollData.mortalite, target.actor.data.type);
            defenseMsg = RdDUtility.buildDefenseChatCard(this, target, rollData);
            explications += "<br><strong>Cible</strong> : " + target.actor.data.name;
          }
          explications += "<br>Dégâts : " + rollData.degats + "<br>Localisation : " + rollData.loc.label;
        } else {
          explications = "<br>Echec ! Pas de dégâts";
        }
      }
    }

    // Sort management
    if (rollData.selectedSort) { // Lancement de sort !
      resumeCompetence = rollData.selectedDraconic.name + "/" + sort.name;
      explications = await this._rollLancementDeSort(rollData, rolled);
    }

    // Save it for fight in the flags area
    game.system.rdd.rollDataHandler[this.data._id] = duplicate(rollData);

    // Final chat message
    let chatOptions = {
      content: "<strong>Test : " + rollData.selectedCarac.label + " / " + resumeCompetence + "</strong>"
          + "<br>Difficultés <strong>libre : " + rollData.diffLibre + "</strong> / conditions : " + Misc.toSignedString(rollData.diffConditions) +" / état : " + rollData.etat
          + RdDResolutionTable.explain(rolled)
          + "<br><strong>" + quality + "</strong>"
          + explications + xpmsg,
      user: game.user._id,
      title: "Résultat du test"
    }
    ChatMessage.create(chatOptions);

    // This an attack, generate the defense message
    if (defenseMsg) {
      defenseMsg.rollData = duplicate(rollData);
      if (defenseMsg.toSocket) {
        game.socket.emit("system.foundryvtt-reve-de-dragon", {
          msg: "msg_defense",
          data: defenseMsg
        });
        if ( game.user.isGM ) {  // Always push the message to the MJ
          ChatMessage.create(defenseMsg);
        }
      } else {
        defenseMsg.whisper = [game.user];
        ChatMessage.create(defenseMsg);
      }
    }

    // Get damages!
    if (encaisser) {
      this.encaisserDommages(rollData.attackerRoll);
    }
  }

  /* -------------------------------------------- */
  _calculBonusDommages(carac, arme, isForce=false) {
    const dmgArme = parseInt(arme.data.dommages) + (isForce)? 5 : 0;
    const dmgPerso = parseInt(this.data.data.attributs.plusdom.value);
    if (carac.label == "Tir") {
      return dmgArme;
    }
    if (carac.label == "Lancer") {
      return dmgArme + Math.min(dmgArme, dmgPerso);
    }
    return dmgArme + dmgPerso;
  }

  /* -------------------------------------------- */
  async _rollLancementDeSort(rollData,  rolled) {

    let sort = duplicate(rollData.selectedSort);

    let closeTMR = true;
    let coutReve = sort.data.ptreve_reel || sort.data.ptreve; // cas de sort à ptreve variables

    let explications = "<br>Lancement du sort <strong>" + sort.name + "</strong> : " + Misc.upperFirst(sort.data.draconic)
    + " pour "+coutReve+ " points de Rêve"
      + "<br>Depuis la case " + rollData.coord + " (" + TMRUtility.getTMRDescription(rollData.coord).label + ")";
      
      let myReve = duplicate(this.data.data.reve.reve);
      if (rolled.isSuccess) { // Réussite du sort !
        sort.ptreve_reel = coutReve;
        if (rolled.isPart) {
          coutReve = Math.max(Math.ceil(coutReve / 2), 1);
        }
        if (myReve.value > coutReve){
          explications += "<br>Réussite du sort: " + coutReve + " points de Rêve sont dépensés";

          if (rollData.isSortReserve) {
          // Mise en réserve
          myReve.value--;
          await this.sortMisEnReserve(rollData, sort);
          closeTMR = false;
        }
      }
      else {
         // Todo 0 pts de reve !!!!
         explications += "<br>Pas assez de rêve";
         mergeObject(rollData, RdDResolutionTable.getResultat("echec"));
      } 
    } else {
      if (rolled.isETotal) { // Echec total !
        coutReve *= 2;
        myReve.value = myReve.value - coutReve;
        explications += "<br><strong>Echec TOTAL</strong> du sort : " + coutReve + " Points de Rêve";
      } else {
        coutReve = 0
        explications += "<br>Echec du sort !";
      }
    }

    myReve.value = Math.max(myReve.value - coutReve, 0);
    await this.update({ "data.reve.reve": myReve });
    
    if (myReve.value == 0) { // 0 points de reve
      ChatMessage.create({ title: "Zero Points de Reve !", content: this.name + " est réduit à 0 Points de Rêve, et tombe endormi !" });
      closeTMR = true;
    }
    if (closeTMR) {
      this.currentTMR.close(); // Close TMR !
    } 
    return explications
  }

  /* -------------------------------------------- */
  async dormir(heures=1)  {
    let message = { title : "Récupération", content :"Vous dormez " + heures + " heure" + (heures > 1 ? "s":  "") };
    await this.recupereEndurance(message);
    for (let i=0; i<heures; i++) {
      await this.recupererFatigue(message);
      await this.recuperationReve(message);
    }
    ChatMessage.create( message );
  }

  /* -------------------------------------------- */
  async recupereEndurance(message) {
    const manquant = this._computeEnduranceMax() - this.data.data.sante.endurance.value;
    if (manquant > 0) {
      await this.santeIncDec("endurance", manquant);
      message.content += "<br>Vous récuperez " + manquant + " points d'endurance";
    }
  }

  /* -------------------------------------------- */
  async recupererFatigue(message) {
    let fatigue = duplicate(this.data.data.sante.fatigue)
    const fatigueMin = this._computeFatigueMin();
    if (fatigue.value <= fatigueMin) {
      message.content += "<br>Vous êtes déjà reposé";
      return;
    }
    fatigue.value = Math.max(fatigueMin, this._calculRecuperationSegment(fatigue.value));
    console.log("recupererFatigue", fatigue)
    await this.update( {"data.sante.fatigue": fatigue } );
    if (fatigue.value == 0)
    {
      message.content += "<br>Vous êtes bien reposé";
    }
  }

  /* -------------------------------------------- */
  _calculRecuperationSegment(actuel)
  {
    const segments = RdDUtility.getSegmentsFatigue(this.data.data.sante.endurance.max);
    let cumul = 0;
    let i;
    for (i=0; i <11; i++) {
      cumul += segments[i];
      let diff = cumul - actuel;
      if (diff >= 0)
      {
        const limit2Segments = Math.floor(segments[i] / 2);
        if (diff > limit2Segments && i > 0) {
          cumul -= segments[i-1]; // le segment est à moins de la moitié, il est récupéré 
        }
        cumul -= segments[i];
        break;
      }
    };
    return cumul;
  }
  /* -------------------------------------------- */
  async recuperationReve(message)  {
    const seuil = this.data.data.reve.seuil.value;
    const reve = this.getReveActuel();
    if (reve >= seuil) {
      message.content += "<br>Vous avez suffisament rêvé (seuil " + seuil + ", rêve actuel "+reve+")";
    }
    else {
      let deRecuperation = RdDDice.deDraconique();
      console.log("recuperationReve", deRecuperation);
      if (deRecuperation>=7)
      {
        // Rêve de Dragon !
        message.content += "<br>Vous faites un <strong>Rêve de Dragon</strong> de " + deRecuperation + " Points de rêve";
        message.content += this.combattreReveDeDragon(deRecuperation);
      }
      else{
        message.content += "<br>Vous récupérez " + deRecuperation + " Points de rêve";
        this.updatePointsDeReve(deRecuperation);
      }
    }
  }

  /* -------------------------------------------- */
  async combattreReveDeDragon(force){
    let draconic = this.getBestDraconic();
    let niveau = Math.max(0, draconic.data.niveau);
    let etat = this.data.data.compteurs.etat.value;
    let difficulte = niveau - etat - force;
    let reveActuel = this.getReveActuel();
    let rolled = await RdDResolutionTable.roll(reveActuel, difficulte);
    // TODO: xp particulière
    console.log("combattreReveDeDragon", rolled );
    return this.appliquerReveDeDragon(rolled, force);
  }

  /* -------------------------------------------- */
  appliquerReveDeDragon(roll, force) {
    let message = "";
    if (roll.isSuccess) {
      message += "<br>Vous gagnez " + force + " points de Rêve";
      this.updatePointDeSeuil();
      this.updatePointsDeReve(force);
    }
    if (roll.isPart) {
      // TODO: Dialog pour choix entre HR opu général?
      let tete = "à déterminer";
      message += "<br>Vous gagnez une Tête de dragon: " + tete;
    }
    if (roll.isEchec) {
      message += "<br>Vous subissez une Queue de Dragon";
      this.ajouterQueue();
    }
    if (roll.isETotal) {
      message += "<br>A cause de votre échec total, vous subissez une deuxième Queue de Dragon !"
      this.ajouterQueue();
    }
    return message;
  }

  /* -------------------------------------------- */
  async sortMisEnReserve(rollData, sort) {
    let reserve = duplicate(this.data.data.reve.reserve);
    reserve.list.push({ coord: rollData.coord, sort: sort, draconic: duplicate(rollData.selectedDraconic) });
    await this.update({ "data.reve.reserve": reserve });
    this.currentTMR.updateSortReserve();
  }

  /* -------------------------------------------- */  
  updateCarac( caracName, caracValue )  
  {
    let caracpath = "data.carac." + caracName + ".value"
    if (caracName == "reve") {
      if (caracValue > Misc.toInt(this.data.data.reve.seuil.value)) {
        this.setPointsDeSeuil(caracValue);
      }
    }
    this.update( { caracpath: caracValue } );
  }
  
  /* -------------------------------------------- */  
  async updateCreatureCompetence( compName, fieldName, compValue )
  {
    let comp = RdDUtility.findCompetence( this.data.items, compName);
    console.log( comp );
    if ( comp ) {
      const update = {_id: comp._id }
      if (fieldName == "niveau")
        update['data.niveau'] = compValue;
      else if (fieldName == "dommages")
        update['data.dommages'] = compValue;
      else 
        update['data.carac_value'] = compValue;
      console.log(update);
      const updated = await this.updateEmbeddedEntity("OwnedItem", update); // Updates one EmbeddedEntity
    }
  }

  /* -------------------------------------------- */  
  async updateCompetence( compName, compValue )
  {
    let comp = RdDUtility.findCompetence( this.data.items, compName);
    if ( comp ) {
      let troncList = RdDUtility.isTronc( compName );
      let maxNiveau = compValue;
      if ( troncList  ) {
        let message = "Vous avez modifié une compétence 'tronc'. Vérifiez que les compétences suivantes évoluent ensemble jusqu'au niveau 0 : ";
        for(let troncName of troncList) {
          message += "<br>" + troncName;
        }
        ChatMessage.create( { title : "Compétence Tronc", 
        content: message } );
      }
      const update = {_id: comp._id, 'data.niveau': maxNiveau };
      const updated = await this.updateEmbeddedEntity("OwnedItem", update); // Updates one EmbeddedEntity
    } else {
      console.log("Competence not found", compName);
    }
  }
  
  /* -------------------------------------------- */  
  async updateCompetenceXP( compName, compValue )
  {
    let comp = RdDUtility.findCompetence( this.data.items, compName);
    if ( comp ) {
      const update = {_id: comp._id, 'data.xp': compValue };
      const updated = await this.updateEmbeddedEntity("OwnedItem", update); // Updates one EmbeddedEntity
    } else {
      console.log("Competence not found", compName);
    }
  }
  
  /* -------------------------------------------- */  
  async updateCompteurValue( fieldName, fieldValue )
  { 
    //console.log("Update", fieldName, fieldValue);
    let content;
    let compteurs = duplicate(this.data.data.compteurs);
    compteurs[fieldName].value =  fieldValue;
    await this.update( {"data.compteurs": compteurs } );
  }

  /* -------------------------------------------- */  
  /** Teste si le conteneur de destination a suffisament de capacité
   * pour recevoir le nouvel objet
   */
  testConteneurCapacite( itemId, conteneurId ) {
    if ( !conteneurId ) return true; // pas de conteneur (porté sur soi), donc toujours OK.
    let conteneur = this.items.find( conteneur => conteneurId == conteneur._id);  // recup conteneur
    //console.log("Conteneur trouvé : ", conteneur);
    if ( conteneur && conteneur.type == "conteneur" ) {
      let currentEnc = 0; // Calculer le total actuel des contenus
      for (let id of conteneur.data.data.contenu) {
        let objet = this.items.find( objet => (id == objet._id) );
        currentEnc += (objet) ? objet.data.data.encombrement : 0;
      } 
      // Et gérer le nouvel objet
      let nouvelObjet = this.items.find( objet => (itemId == objet._id) );
      if ( currentEnc + nouvelObjet.data.data.encombrement > Number(conteneur.data.data.capacite) )
        return false;
    }
    return true;
  }

  /* -------------------------------------------- */  
  /** Supprime un item d'un conteneur, sur la base 
   * de leurs ID */
  async enleverDeConteneur( itemId, conteneurId ) {
    if ( !conteneurId ) return; // pas de conteneur (porté sur soi)
    let conteneur = this.items.find( conteneur => conteneurId == conteneur._id);  // recup conteneur
    if ( conteneur ) { // Si présent
      //console.log("Suppression du conteneur1", conteneurId, itemId,  conteneur.data.data.contenu);
      let contenu = conteneur.data.data.contenu;
      contenu.splice(contenu.indexOf('itemId'), 1);
      //let newContenu = conteneur.data.data.contenu.filter( function(value, index, arr) { return value != itemId } );
      //console.log("Suppression du conteneur2", conteneurId, itemId, newContenu);
      //let update = {_id: conteneurId, "data.contenu": newContenu };
      await this.updateEmbeddedEntity("OwnedItem", conteneur.data); 
    }
  }

  /* -------------------------------------------- */  
  /** Ajoute un item dans un conteneur, sur la base 
   * de leurs ID */
  async ajouterAConteneur( itemId, conteneurId ) {
    if ( !conteneurId ) return; // pas de conteneur (porté sur soi)
    let conteneur = this.items.find( conteneur => conteneurId == conteneur._id);
    if ( conteneur && conteneur.type == 'conteneur' ) {
      conteneur.data.data.contenu.push( itemId );
      await this.updateEmbeddedEntity("OwnedItem", conteneur.data );  
    }
  }

  /* -------------------------------------------- */  
  detectSurEncombrement( ) {
    let diffEnc = Number(this.encombrementTotal) - Number(this.data.data.attributs.encombrement.value);
    if ( diffEnc > 0 ) { // Sur-encombrement
      let malus = Math.round( diffEnc);
      malus = (malus == 0) ? 1 : malus; // Always 1 at least
      //console.log("Sur enc malus", malus);
      return malus;
    }
    return 0;
  }

  /* -------------------------------------------- */  
  computeEncombrementTotal(  ) {
    let totalEnc = 0;
    for (const item of this.data.items) {
      if ( item.data && item.data.encombrement ) { // Enc value filtering
        totalEnc += Number(item.data.encombrement) * Number(((item.data.quantite)?item.data.quantite:1));
      }
    }
    this.encombrementTotal = totalEnc;
    this.detectSurEncombrement();
  }

  /* -------------------------------------------- */  
  computeEtatGeneral( ) 
  {
    let data = this.data.data;
    // Pas d'état général pour les entités forçage à 0
    if ( this.data.type == 'entite') {
      data.compteurs.etat.value = 0;
      return;
    }
    // Pour les autres
    let state = 0;
    state = state - (data.sante.vie.max - data.sante.vie.value);
    if (data.sante.fatigue) // Creatures n'ont pas de fatigue
      state = state + RdDUtility.currentFatigueMalus(data.sante.fatigue.value, data.sante.endurance.max);
    state = state - this.detectSurEncombrement();
    data.compteurs.etat.value = state;
  }
  
  /* -------------------------------------------- */  
  async ajouterRefoulement( value=1) {
    let ret = "none";
    
    let refoulement = duplicate(this.data.data.reve.refoulement);
    refoulement.value = refoulement.value + value;

    let total = new Roll("d20").roll().total;
    if ( total <= refoulement.value ) {
      refoulement.value = 0;
      this.ajouterSouffle();
      ret = "souffle";
    }

    await this.update( {"data.reve.refoulement": refoulement } );
    return ret;      
  }

  /* -------------------------------------------- */
  ajouterSouffle() {
    let souffle = RdDRollTables.getSouffle();
    // ChatMessage.create({
    //   title: "Souffle de Dragon",
    //   content: this.name + " subit un Souffle de Dragon  : " + souffle.name
    // });
    // this.actor.createOwnedItem(souffle);
  }

  /* -------------------------------------------- */
  async ajouterQueue() {
    // TODO: Déterminer si Thanatos a été utilisé? => laisser le joueur ne pas choisir Thanatos => choisir sa voie?
    let utiliseThanatos = false;
    let queue;
    if (utiliseThanatos) {
      queue = await RdDRollTables.getOmbre();
      // mettre à jour: plus d'ombre en vue
    }
    else {
      queue = await RdDRollTables.getQueue();
    }
    /*
    // TODO: convertir la queue obtenue en nouvel item ... 
    // ou bien l'ajouter à la liste spécifique => this.data.data.reve.queues
    this.createOwnedItem(queue);
    
    ChatMessage.create({
      title: "Queue de Dragon",
      content: this.name + " subit un Queue de Dragon  : " + queue.name
    });
    
    return queue.name;
    */
  }
  
  /* -------------------------------------------- */  
  async deleteTMRRencontreAtPosition( ) {
    let rencontres = duplicate(this.data.data.reve.rencontre);
    let len = rencontres.list.length;
    let i = 0;
    //console.log("List", rencontres, len);
    let newTable = [];
    for( i=0; i < len; i++) {
      if (rencontres.list[i].coord != this.data.data.reve.tmrpos.coord ) 
        newTable.push(rencontres.list[i]);
    }
    if ( newTable.length != len ) {
      rencontres.list = newTable;
      //console.log("Result: ", rencontres);
      await this.update( {"data.reve.rencontre": rencontres } );    
    }
  }
  
  /* -------------------------------------------- */  
  async addTMRRencontre( currentRencontre ) {    
    let rencontres = duplicate(this.data.data.reve.rencontre);
    let len = rencontres.list.length;
    let i = 0;
    let already = false;
    for( i=0; i < len; i++) {
      if (rencontres.list[i].coord == this.data.data.reve.tmrpos.coord ) 
        already = true;
    }
    if ( !already ) {
      rencontres.list.push( {coord: this.data.data.reve.tmrpos.coord, rencontre: currentRencontre} ); 
      await this.update( {"data.reve.rencontre": rencontres } );
    }
  }

  /* -------------------------------------------- */  
  async updateCoordTMR( coord ) {        
    let tmrPos = duplicate(this.data.data.reve.tmrpos );
    tmrPos.coord = coord;
    await this.update( {"data.reve.tmrpos": tmrPos } );
  }
  
  /* -------------------------------------------- */  
  async updatePointsDeReve( value ) {    
    let reve = duplicate(this.data.data.reve.reve);
    reve.value = Math.max(reve.value + value, 0);
    await this.update( {"data.reve.reve": reve } );
  }
  
  /* -------------------------------------------- */
  async updatePointDeSeuil(value=1) {
    const seuil = Misc.toInt(this.data.data.reve.seuil.value);
    const reve = Misc.toInt(this.data.data.carac.reve.value);
    if (seuil < reve) {
      await this.setPointsDeSeuil(Math.min(seuil+value, reve));
    }
  }
  
  /* -------------------------------------------- */
  async setPointsDeSeuil( value ) {    
    let seuil = duplicate(this.data.data.reve.seuil);
    seuil.value = value;
    await this.update( {"data.reve.seuil": seuil } );
  }

  /* -------------------------------------------- */  
  testSiSonne( sante, endurance ) 
  {
    let result = new Roll("d20").roll().total;
    if ( result <= endurance)
       sante.sonne.value = false;
    if ( result > endurance || result == 20) // 20 is always a failure
       sante.sonne.value = true;
    if (result == 1) {
      sante.sonne.value = false;
      let xp  = Misc.toInt(this.data.data.carac.constitution.xp) + 1;
      this.update( {"data.carac.constitution.xp": xp } ); // +1 XP !
      // TODO : Output to chat
    }
  }

  /* -------------------------------------------- */  
  GetNumberBlessures( blessuresListe ) 
  {
    let nbB = 0;
    for ( let b of blessuresListe) {
      nbB += ( b.active) ? 1 : 0;
    }
    return nbB;
  }

  /* -------------------------------------------- */  
  async santeIncDec(name, inc ) {
    const sante = duplicate(this.data.data.sante);
    let data = sante[name];
    let minValue = name == "vie" ? Number(-this.data.data.attributs.sconst.value) : 0;
    let newValue = Math.max(minValue, Math.min(data.value + inc, data.max));

    if (name == "endurance" && this.data.type != 'entite' ) {
      if ( sante.fatigue && inc < 0 ) { // Each endurance lost -> fatigue lost
        sante.fatigue.value = sante.fatigue.value - inc
      }
      if ( newValue == 0 && inc < 0) { // perte endurance et endurance devient 0 -> -1 vie
        sante.vie.value = sante.vie.value - 1;
      }
      newValue = Math.max(0, newValue);
      if (inc>0) { // le max d'endurance s'applique seulement à la récupération
        newValue = Math.max(newValue, this._computeEnduranceMax())
      }
      if (data.value - newValue > 1) {
        this.testSiSonne(sante, newValue); // Peut-être sonné si 2 points d'endurance perdus d'un coup
      } else if (inc>0) {
        sante.sonne.value = false;
      }
    }
    data.value = newValue;
    //console.log(name, inc, data.value, newValue, minValue, data.max);
    if ( sante.fatigue) { // If endurance lost, then the same amount of fatigue cannot be recovered
      sante.fatigue.value = Math.max(sante.fatigue.value, this._computeFatigueMin());
    } 
    //console.log("SANTE::::", sante);
    
    await this.update( {"data.sante": sante } );
  }

  /* -------------------------------------------- */  
  _computeFatigueMin() {
    return this.data.data.sante.endurance.max - this.data.data.sante.endurance.value;
  }
  
  /* -------------------------------------------- */  
  _computeEnduranceMax() {
    let blessures = this.data.data.blessures;
    let diffVie = this.data.data.sante.vie.max - this.data.data.sante.vie.value;
    let maxEndVie = this.data.data.sante.endurance.max - (diffVie * 2);
    let nbGraves = this.GetNumberBlessures(blessures.graves.liste);
    let nbCritiques = this.GetNumberBlessures(blessures.critiques.liste);
    let maxEndGraves = Math.floor(this.data.data.sante.endurance.max / (2 * nbGraves));
    let maxEndCritiques = nbCritiques > 0 ? 1 : this.data.data.sante.endurance.max;
    return Math.max(0, Math.min(maxEndVie, maxEndGraves, maxEndCritiques));
  }

  /* -------------------------------------------- */  
  async manageBlessureFromSheet( bType, index, active ) {
    let bList    = duplicate(this.data.data.blessures);
    let blessure = bList[bType+"s"].liste[index];
    blessure.active = !blessure.active;
    if ( !blessure.active ) {
      blessure.premiers_soins = 0;
      blessure.soins_complets = 0;
      blessure.jours = 0;
      blessure.localisation = "";  
    }
    //console.log("Blessure update", bType, index, blessure, bList );
    await this.update( { 'data.blessures': bList } );
  }

  /* -------------------------------------------- */  
  async setDataBlessureFromSheet( bType, index, psoins, pcomplets, jours, loc) {
    let bList    = duplicate(this.data.data.blessures);
    let blessure = bList[bType+"s"].liste[index];
    blessure.premiers_soins = psoins;
    blessure.soins_complets = pcomplets;
    blessure.jours = jours;
    blessure.localisation = loc;
    await this.update( { 'data.blessures': bList } );    
  }

  /* -------------------------------------------- */  
  manageBlessures( blessuresData  ) 
  {
    // Fast exit
    if ( this.data.type == 'entite') return; // Une entité n'a pas de blessures
    if ( blessuresData.legeres + blessuresData.graves + blessuresData.critiques == 0 ) return;

    let workData = duplicate(blessuresData);    
    let blessures = duplicate(this.data.data.blessures);
    // Manage blessures
    if ( workData.legeres > 0 ) { 
      for (let k=0; k<blessures.legeres.liste.length; k++) {
        let bless = blessures.legeres.liste[k];
        if ( !bless.active ){
          bless.active = true;
          bless.loc = workData.locName;
          workData.legeres--;
        }
        if (workData.legeres == 0) break;
      }
    }      
    
    if ( workData.legeres > 0 ) {
      workData.graves += 1;
      blessuresData.graves += 1;
    }
      
    if ( workData.graves > 0) {
      for (let k=0; k<blessures.graves.liste.length; k++) {
        let bless = blessures.graves.liste[k];
        if ( !bless.active ) {
          bless.active = true;
          bless.loc = workData.locName;
          workData.graves--;
        }
        if ( workData.graves == 0) break;
      }
    }

    if ( workData.graves > 0 ) { 
      workData.critiques = 1;
      blessuresData.critiques = 1;
    }
      
    if ( workData.critiques > 0 ) {
      workData.endurance = this.data.data.sante.endurance.value; // Patch with real endurance current value (ie end -> 0 when critique)
      blessures.critiques.liste[0].active = true;
      blessures.critiques.liste[0].loc  = workData.locName;
    }
      
    this.update( { "data.blessures": blessures } );
  }

  /* -------------------------------------------- */
  async stressTest() {
    let stressRoll = this._stressRoll();
    let compteurs = duplicate(this.data.data.compteurs);
    let convertion = Math.floor(compteurs.stress.value * stressRoll.factor);
    
    compteurs.experience.value += convertion;
    compteurs.stress.value = Math.max(compteurs.stress.value - convertion - 1, 0);

    ChatMessage.create({
      title: "Jet de Stress", content: "Vous avez transformé " + convertion + " points de Stress en Expérience" + stressRoll.comment,
      whisper: ChatMessage.getWhisperRecipients(game.user.name)
    });
    await this.update({ "data.compteurs": compteurs });
  }

  /* -------------------------------------------- */
  async _stressRoll() {
    let result = await RdDResolutionTable.roll(this.data.data.carac.reve.value, 0);
    console.log("_stressRoll", result);
    switch (result.code) {
      case "sign": return { factor: 0.75, comment: " (75%): " + result.quality + " - " + result.roll + " sur " + result.score + "%" }
      case "norm": return { factor: 0.5, comment: " (50%): " + result.quality + " - " + result.roll + " sur " + result.score + "%" }
      case "echec": return { factor: 0.2, comment: " (20%): " + result.quality + " - " + result.roll + " sur " + result.score + "%" }
      case "epart": return { factor: 0.1, comment: " (10%): " + result.quality + " - " + result.roll + " sur " + result.score + "%" }
      case "etotal": return { factor: 0, comment: " (0%): " + result.quality + " - " + result.roll + " sur " + result.score + "%" }
      case "part":
        {
          let second = await RdDResolutionTable.roll(this.data.data.carac.reve.value, 0);
          console.log("_stressRoll", second);
          switch (second.code) {
            case "part": case "sign":
              return { factor: 1.5, comment: " (150%): Double Particulière - " + result.roll + " puis " + second.roll + " sur " + result.score + "%" }
            default:
              return { factor: 1, comment: " (150%): " + result.quality + " - " + result.roll + " puis " + second.roll + " sur " + result.score + "%" }
          }
        }
    }
  }

  /* -------------------------------------------- */
  async rollUnSort(coord) {
    let draconicList = this.getDraconicList();
    let sortList = duplicate(this.getSortList()); // Duplication car les pts de reve sont modifiés dans le sort
    
    let rollData = { 
      selectedCarac: this.data.data.carac.reve,
      etat: this.data.data.compteurs.etat.value, 
      draconicList: draconicList,
      sortList: sortList,
      selectedDraconic: this.getBestDraconic(),
      selectedSort: sortList[0],
      coord: coord,
      coordLabel: TMRUtility.getTMRDescription( coord).label,
      finalLevel: 0,
      diffConditions: 0,
      diffLibre: sortList[0].data.difficulte, // Per default at startup
      coutreve: Array(20).fill().map((item, index) => 1 + index),
      ajustementsConditions: CONFIG.RDD.ajustementsConditions,
      difficultesLibres: CONFIG.RDD.difficultesLibres
    }
    let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-roll-sort.html', rollData);
    new RdDRollDialog("sort", html, rollData, this ).render(true);
  }
  
  /* -------------------------------------------- */  
  async rollCarac( caracName )
  {
    let carac;  
    if ( caracName == "reveActuel") { // Fake carac for Reve Actuel
      carac = {type: "number",                
               value: this.getReveActuel(), 
               label: "Rêve Actuel"
              }
    } else {
      carac = this.data.data.carac[caracName];// Per default
    }
    let rollData = { 
        selectedCarac: carac,
        ajustementsConditions: CONFIG.RDD.ajustementsConditions,
        difficultesLibres: CONFIG.RDD.difficultesLibres,
        etat: this.data.data.compteurs.etat.value, 
        finalLevel: 0,
        diffConditions: 0,
        diffLibre: 0
      }
      console.log(caracName, rollData);
      let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html', rollData);
      new RdDRollDialog("carac", html, rollData, this ).render(true);
    }
    
    /* -------------------------------------------- */
    getSortList() {
      return this.data.items.filter(item => item.type == "sort");
    }
    
    /* -------------------------------------------- */
    getDraconicList() {
      return this.data.items.filter(item => item.data.categorie == 'draconic')
    }
    
    /* -------------------------------------------- */  
    async displayTMR(mode="normal" ) 
    {
      let isRapide= mode == "rapide" 
      if (mode != "visu")
      {
        let minReveValue = (isRapide) ? 3 : 2;
        if (this.getReveActuel() < minReveValue ) {
          ChatMessage.create( { title: "Montée impossible !", content: "Vous n'avez plus assez de Points de Reve pour monter dans les Terres Médianes", 
          whisper: ChatMessage.getWhisperRecipients(game.user.name) } );
          return;
        } 
      }
      
      let data = { 
        fatigue: {
          malus: RdDUtility.calculMalusFatigue(this.data.data.sante.fatigue.value,  this.data.data.sante.endurance.max),
          html: "<table class='table-fatigue'>" + RdDUtility.makeHTMLfatigueMatrix( this.data.data.sante.fatigue.value,  this.data.data.sante.endurance.max ).html() + "</table>"
        },
        draconic: this.getDraconicList(),
        sort: this.getSortList(),
        caracReve: this.data.data.carac.reve.value,
        pointsReve: this.getReveActuel(),
        isRapide: isRapide
      }    
      let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-tmr.html', data );
      this.currentTMR = new RdDTMRDialog(html, this, data, mode == "visu");
      this.currentTMR.render(true);
    }
    
    /* -------------------------------------------- */  
    rollArme( armeName ) 
    {
      let armeItem = this.data.items.find(item=>item.type==="arme" && (item.name === armeName));
      if ( armeItem && armeItem.data.competence ) 
      this.rollCompetence( armeItem.data.competence, armeItem );
      else 
      this.rollCompetence( armeName ); //Bypass mode!
    }
    
  /* -------------------------------------------- */  
  async rollCompetence( name, armeItem=undefined, attackerRoll=undefined ) {
    let competence = RdDUtility.findCompetence( this.data.items, name);
    console.log("rollCompetence !!!", competence, armeItem, attackerRoll);
    // Common rollData values
    let rollData = { 
      ajustementsConditions: CONFIG.RDD.ajustementsConditions,
      difficultesLibres: CONFIG.RDD.difficultesLibres,
      etat: this.data.data.compteurs.etat.value, 
      diffConditions: 0,
      diffLibre: (attackerRoll) ? attackerRoll.diffLibre : 0,
      attackerRoll: attackerRoll,
      finalLevel: 0,
      coupsNonMortels: false
    }
      
    if ( competence.type == 'competencecreature') { // Specific case for Creatures
      if ( competence.data.iscombat ) {
        armeItem = { name: name, data: { dommages: competence.data.dommages} };
      }
      competence.data.defaut_carac = "carac_creature"; // Fake default competence
      competence.data.categorie = "creature"; // Fake default competence
      rollData.competence = competence;
      rollData.arme  = armeItem;
      rollData.carac = { carac_creature: { label: name, value: competence.data.carac_value } };
    } else { // Usual competence
      rollData.competence = competence;
      rollData.arme  = armeItem;
      rollData.carac = this.data.data.carac;
    }
    
    let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-competence.html', rollData);
    if (rollData.arme)  {
      new RdDRollDialog("arme", html, rollData, this ).render(true);
    } else {
      new RdDRollDialog("competence", html, rollData, this ).render(true);
    }
  }
  
  /* -------------------------------------------- */  
  async equiperObjet( itemID )
  {
    let item = this.getOwnedItem(itemID);
    if ( item && item.data.data ) {
      let update = {_id: item._id, "data.equipe": !item.data.data.equipe };
      await this.updateEmbeddedEntity("OwnedItem", update); 
      this.computeEncombrementTotal(); // Mise à jour encombrement
    }
  }
  
  /* -------------------------------------------- */  
  computeArmure( locData, domArmePlusDom ) 
  {
    let protection = 0;
    for (const item of this.data.items) {
      if (item.type == "armure" && item.data.equipe) {
        let update = duplicate(item);
        protection += new Roll(update.data.protection.toString()).roll().total;
        update.data.deterioration += domArmePlusDom;
        domArmePlusDom = 0; // Reset it
        if ( update.data.deterioration >= 10) {          
          update.data.deterioration = 0;
          if ( update.data.protection.toString().length == 1 ) 
            update.data.protection = "d"+update.data.protection+"-0";
          else {
            let regex = /d\(d+)\-(\d+)/g;
            let res = regex.exec( update.data.protection );
            update.data.protection = "d"+res[1]+"-"+(parseInt(res[2])+1);            
          }
          /* TODO - POST chat message */
        }
        this.updateEmbeddedEntity("OwnedItem", update); 
      }
    }
    console.log("Final protect", protection);
    return protection;
  }

  /* -------------------------------------------- */
  encaisserDommages( attackerRoll ) {
    console.log("encaisserDommages", attackerRoll )
    const armure = this.computeArmure( attackerRoll.loc, attackerRoll.domArmePlusDom);
    let degatsReel = attackerRoll.degats - armure;

    let result = RdDUtility.computeBlessuresSante(degatsReel, attackerRoll.mortalite);
    if ( this.data.type != 'entite') // Pas de PV chez les entités
      this.santeIncDec("vie", result.vie);
    this.santeIncDec("endurance", result.endurance);
    result.locName = (attackerRoll.loc) ?  attackerRoll.loc.label : "Corps";

    this.manageBlessures(result); // Will upate the result table
    const blessureLegere = (result.legeres > 0 ? "une blessure légère" : "");
    const blessureGrave = (result.graves > 0 ? "une blessure grave" : "");
    const blessureCritique = (result.critiques > 0 ? "une blessure critique" : "");
    ChatMessage.create({
      title: "Blessures !", content: this.data.name + " a encaissé : " +
        "<br>Encaissement final : " + degatsReel +
        "<br>" + blessureLegere + blessureGrave + blessureCritique +
        "<br>Et a perdu : " +
        "<br>" + result.endurance + " Endurance et " + result.vie + " Points de Vie"
    });

    this.computeEtatGeneral();
    this.sheet.render(true);
  }

  
  /* -------------------------------------------- */  
  parerAttaque( attackerRoll, armeId )
  {
    let armeItem = this.getOwnedItem(armeId); // Item.data.data !
    console.log("Going to PARY !!!!!!!!!", armeItem, attackerRoll.diffLibre);    
    this.rollCompetence(  armeItem.data.data.competence, armeItem.data, attackerRoll );
  }
  
  /* -------------------------------------------- */  
  esquiverAttaque( attackerRoll )
  {
    this.rollCompetence( "esquive", undefined, attackerRoll );
  }
  
  /* -------------------------------------------- */  
  /** @override */
  getRollData() {
    const data = super.getRollData();

    return data;
  }

}