/* -------------------------------------------- */  
import { PegasusCommands } from "./pegasus-commands.js";
import { PegasusActorCreate } from "./pegasus-create-char.js";

/* -------------------------------------------- */  
const __level2Dice = [ "d0", "d4", "d6", "d8", "d10", "d12" ];
const __name2DiceValue = { "d0": 0, "d4": 4, "d6": 6, "d8": 8, "d10" : 10, "d12": 12 }

/* -------------------------------------------- */  
export class PegasusUtility  {
  

  /* -------------------------------------------- */
  static async init() {
    Hooks.on('renderChatLog', (log, html, data) => PegasusUtility.chatListeners(html));
    this.rollDataStore = {}
    this.defenderStore = {}
    this.diceList = [];
    this.diceFoundryList = [];
    this.optionsDiceList = "";
    this.buildDiceLists();
    PegasusCommands.init();

    Handlebars.registerHelper('upper', function (text) {
      return text.toUpperCase();
    });
    Handlebars.registerHelper('upperFirst', function (text) {
      if (typeof text !== 'string') return text
      return text.charAt(0).toUpperCase() + text.slice(1)
    });

  }  

  /* -------------------------------------------- */
  static getSpecs( ) {
    return this.specs;
  }

  /* -------------------------------------------- */
  static async ready() {
    const specs = await PegasusUtility.loadCompendium("fvtt-pegasus-rpg.specialisations");
    this.specs = specs.map(i => i.toObject());
  }

  /* -------------------------------------------- */
  static async loadCompendiumData(compendium) {
    const pack = game.packs.get(compendium);
    return await pack?.getDocuments() ?? [];
  }

  /* -------------------------------------------- */
  static async loadCompendium(compendium, filter = item => true) {
    let compendiumData = await PegasusUtility.loadCompendiumData(compendium);
    return compendiumData.filter(filter);
  }

  /* -------------------------------------------- */
  static buildDiceLists() {
    let maxLevel = game.settings.get("fvtt-pegasus-rpg", "dice-max-level");
    let diceList = [ "0" ];
    let diceValues = [0];
    let diceFoundryList = [ "d0" ];
    let diceLevel = 1;
    let concat = "";
    let concatFoundry = "";
    let optionsDiceList = '<option value="0">0</option>';
    let optionsLevel = '<option value="0">0</option>';
    for(let i=1; i<=maxLevel;i++) {
      let currentDices = concat + __level2Dice[diceLevel];
      diceList.push( currentDices );
      diceFoundryList.push( concatFoundry + __level2Dice[diceLevel] + "x" );
      if ( __level2Dice[diceLevel] == "d12") {
        concat = concat +  "d12 ";
        concatFoundry = concatFoundry +  "d12x, ";
        diceLevel = 1;
      } else {
        diceLevel++;
      }
      optionsDiceList += `<option value="${i}">${currentDices}</option>`;
      optionsLevel += `<option value="${i}">${i}</option>`;
    }
    this.diceList = diceList;
    this.diceFoundryList = diceFoundryList;
    this.optionsDiceList = optionsDiceList;
    this.optionsLevel = optionsLevel;

    this.optionsStatusList = '<option value="notapplicable">Not applicable</option><option value="health">Health</option><option value="nrg">NRG</option><option value="delirium">Delirium</option>';

    console.log("Defautl dice List", diceList, diceFoundryList);
  }
  
  /* -------------------------------------------- */
  static getOptionsStatusList() {
    return this.optionsStatusList;
  }
  /* -------------------------------------------- */
  static getOptionsDiceList() {
    return this.optionsDiceList;
  }
  /* -------------------------------------------- */
  static getOptionsLevel() {
    return this.optionsLevel;
  }

  /* -------------------------------------------- */
  static computeAttackDefense(defenseRollId) {
    let defenseRollData = this.getRollData(defenseRollId );
    let attackRollData = this.getRollData(defenseRollData.linkedRollId);
    let defender = game.actors.get( defenseRollData.actorId);
    defender.processDefenseResult(defenseRollData, attackRollData);
  }

  /* -------------------------------------------- */
  static applyDamage( defenseRollId) {
    let defenseRollData = this.getRollData(defenseRollId );
    let defender = game.actors.get( defenseRollData.actorId);
    defender.applyDamageLoss( defenseRollData.finalDamage) ;
  }

  /* -------------------------------------------- */
  static applyNoDefense( actorId, attackRollId ) {
    let attackRollData = this.getRollData(attackRollId );
    let defender = game.actors.get( actorId );
    defender.processNoDefense( attackRollData ) ;
  }

  /* -------------------------------------------- */
  static async chatListeners(html) {

    html.on("click", '.chat-create-actor', event => {
      console.log("Event !")
      game.system.pegasus.creator.processChatEvent(event);      
    } );

  }
  
  /* -------------------------------------------- */  
  static async preloadHandlebarsTemplates() {
    
    const templatePaths = [
      'systems/fvtt-pegasus-rpg/templates/editor-notes-gm.html',
      'systems/fvtt-pegasus-rpg/templates/partial-roll-common-dices.html',
      'systems/fvtt-pegasus-rpg/templates/partial-options-statistics.html',
      'systems/fvtt-pegasus-rpg/templates/partial-options-level.html',
      'systems/fvtt-pegasus-rpg/templates/partial-options-range.html',
      'systems/fvtt-pegasus-rpg/templates/partial-options-equipment-types.html'
    ]
    return loadTemplates(templatePaths);    
  }

    /* -------------------------------------------- */
    static removeChatMessageId(messageId) {
      if (messageId){
        game.messages.get(messageId)?.delete();
      }
    }
    
    static findChatMessageId(current) {
      return PegasusUtility.getChatMessageId(PegasusUtility.findChatMessage(current));
    }
  
    static getChatMessageId(node) {
      return node?.attributes.getNamedItem('data-message-id')?.value;
    }
  
    static findChatMessage(current) {
      return PegasusUtility.findNodeMatching(current, it => it.classList.contains('chat-message') && it.attributes.getNamedItem('data-message-id'));
    }
  
    static findNodeMatching(current, predicate) {
      if (current) {
        if (predicate(current)) {
          return current;
        }
        return PegasusUtility.findNodeMatching(current.parentElement, predicate);
      }
      return undefined;
    }
  
  /* -------------------------------------------- */
  static templateData(it) {
    return PegasusUtility.data(it)?.data ?? {}
  }

  /* -------------------------------------------- */
  static data(it) {
    if (it instanceof Actor || it instanceof Item || it instanceof Combatant) {
      return it.data;
    }
    return it;
  }
  
  /* -------------------------------------------- */
  static getDiceValue( level = 0) {
    let diceString = this.diceList[level]
    let diceTab = diceString.split(" ")
    let diceValue = 0
    for (let dice of diceTab) {
      diceValue += __name2DiceValue[dice]
    }
    return diceValue
  }

  /* -------------------------------------------- */
  static getDiceFromLevel(level = 0) {
    level = Number(level)
    return this.diceList[level];
  }
  /* -------------------------------------------- */
  static getFoundryDiceFromLevel(level = 0) {
    level = Number(level)
    console.log(this.diceFoundryList);
    return this.diceFoundryList[level];
  }
  
  /* -------------------------------------------- */
  static createDirectOptionList( min, max) {
    let options = {};
    for(let i=min; i<=max; i++) {
      options[`${i}`] = `${i}`;
    }
    return options;
  }

  /* -------------------------------------------- */
  static buildListOptions(min, max) {
    let options = ""
    for (let i = min; i <= max; i++) {
      options += `<option value="${i}">${i}</option>`
    }
    return options;
  }

  /* -------------------------------------------- */
  static getTarget() {
    if (game.user.targets && game.user.targets.size == 1) {
      for (let target of game.user.targets) {
        return target;
      }
    }
    return undefined;
  }
  
  /* -------------------------------------------- */
  static getDefenseState( actorId) {
    return this.defenderStore[actorId];
  }
  
  /* -------------------------------------------- */
  static async updateDefenseState( defenderId, rollId) {
    this.defenderStore[defenderId] = rollId;
    if ( game.user.character && game.user.character.id == defenderId ) {
      let defender = game.actors.get( defenderId);
      let chatData = {
        user: game.user.id,
        alias : defender.name,
        rollMode: game.settings.get("core", "rollMode"),
        whisper: [game.user.id].concat( ChatMessage.getWhisperRecipients('GM') ),
        content: `<div>${defender.name} is under attack. He must roll a skill/weapon/technique to defend himself or suffer damages (button below).
        <button class="chat-card-button apply-nodefense" data-actor-id="${defenderId}" data-roll-id="${rollId}" >No defense</button></div`
      };  
      //console.log("Apply damage chat", chatData );
      await ChatMessage.create( chatData );
    }
  }

  /* -------------------------------------------- */
  static clearDefenseState( defenderId) {
    this.defenderStore[defenderId] = undefined;
  }
  /* -------------------------------------------- */
  static storeDefenseState( rollData ) {
    game.socket.emit("system.fvtt-weapons-of-the-gods", {
            name: "msg_update_defense_state", data: { defenderId: rollData.defenderId, rollId: rollData.rollId } } );
    this.updateDefenseState(rollData.defenderId, rollData.rollId );
  }
  /* -------------------------------------------- */
  static updateRollData( rollData) { 
    
    let id = rollData.rollId;
    let oldRollData = this.rollDataStore[id] || {};
    let newRollData = mergeObject( oldRollData, rollData);
    this.rollDataStore[id] = newRollData;
  }
  /* -------------------------------------------- */
  static saveRollData( rollData )  {
    game.socket.emit("system.pegasus-rpg", {
      name: "msg_update_roll", data: rollData } ); // Notify all other clients of the roll    
    this.updateRollData( rollData);
  }

  /* -------------------------------------------- */
  static getRollData( id ) {
    return this.rollDataStore[id];
  }

  /* -------------------------------------------- */
  static onSocketMesssage( msg ) {
    //console.log("SOCKET MESSAGE", msg.name, game.user.character.id, msg.data.defenderId);
    if (msg.name == "msg_update_defense_state" ) {
      this.updateDefenseState( msg.data.defenderId, msg.data.rollId );
    }
    if (msg.name == "msg_update_roll" ) {
      this.updateRollData( msg.data );
    }  
  }

  /* -------------------------------------------- */
  static chatDataSetup(content, modeOverride, isRoll = false, forceWhisper) {
    let chatData = {
      user: game.user.id,
      rollMode: modeOverride || game.settings.get("core", "rollMode"),
      content: content
    };

    if (["gmroll", "blindroll"].includes(chatData.rollMode)) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM").map(u => u.id);
    if (chatData.rollMode === "blindroll") chatData["blind"] = true;
    else if (chatData.rollMode === "selfroll") chatData["whisper"] = [game.user];

    if (forceWhisper) { // Final force !
      chatData["speaker"] = ChatMessage.getSpeaker();
      chatData["whisper"] = ChatMessage.getWhisperRecipients(forceWhisper);
    }

    return chatData;
  }
  
  /* -------------------------------------------- */
  static async loadCompendiumData(compendium) {
    const pack = game.packs.get(compendium);
    return await pack?.getDocuments() ?? [];
  }

  /* -------------------------------------------- */
  static async loadCompendium(compendium, filter = item => true) {
    let compendiumData = await this.loadCompendiumData(compendium);
    //console.log("Compendium", compendiumData);
    return compendiumData.filter(filter);
  }
  
  /* -------------------------------------------- */
  static async showDiceSoNice(roll, rollMode) {
    if (game.modules.get("dice-so-nice")?.active) {
      if (game.dice3d) {
        let whisper = null;
        let blind = false;
        rollMode = rollMode ?? game.settings.get("core", "rollMode");
        switch (rollMode) {
          case "blindroll": //GM only
            blind = true;
          case "gmroll": //GM + rolling player
            whisper = this.getUsers(user => user.isGM);
            break;
          case "roll": //everybody
            whisper = this.getUsers(user => user.active);
            break;
          case "selfroll":
            whisper = [game.user.id];
            break;
        }
        await game.dice3d.showForRoll(roll, game.user, true, whisper, blind);
      }
    }
  }


  /* -------------------------------------------- */
   static async rollPegasus( rollData ) {

    let actor = game.actors.get(rollData.actorId);  

    let dicePool = [ {name:"stat", level: 0, statmod: 0}, {name: "spec", level: 0}, {name:"bonus", level: 0}, {name:"hindrance", level: 0}, {name:"other", level:0} ];
    if (rollData.stat) {
      dicePool[0].level += Number(rollData.stat.value);
      dicePool[0].statmod = Number(rollData.stat.mod);
    }
    if ( rollData.statDicesLevel) {
      dicePool[0].level = rollData.statDicesLevel;
    }
    if (rollData.selectedSpec && rollData.selectedSpec != "0") {
      rollData.spec = rollData.specList.find( item => item._id == rollData.selectedSpec);
      rollData.spec.data.dice = PegasusUtility.getDiceFromLevel(rollData.spec.data.level);
    }
    if (rollData.spec) {
      dicePool[1].level += Number(rollData.spec.data.level);
    }
    if ( rollData.specDicesLevel) {
      dicePool[0].level = rollData.specDicesLevel;
    }
    if (rollData.bonusDicesLevel) {
      dicePool[2].level += Number(rollData.bonusDicesLevel);
    }
    if (rollData.hindranceDicesLevel) {
      dicePool[3].level += Number(rollData.hindranceDicesLevel);
    }
    if (rollData.otherDicesLevel) {
      dicePool[4].level += Number(rollData.otherDicesLevel);
    }

    let diceFormulaTab = [];
    for (let diceGroup of dicePool) {
      diceFormulaTab.push( this.getFoundryDiceFromLevel( diceGroup.level) )
    }
    let diceFormula = '{' + diceFormulaTab.join(', ') + '}kh';
    console.log(diceFormula);

    // Performs roll
    let myRoll = rollData.roll;
    if ( !myRoll ) { // New rolls only of no rerolls
      myRoll = new Roll(diceFormula).roll( { async: false} );
      console.log("ROLL : ", diceFormula);
      await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode") );
      rollData.roll = myRoll
    }

    // Final score and keep data
    rollData.finalScore = myRoll.total + dicePool[0].statmod;
    console.log("ROLLLL!!!!", rollData);

    if (rollData.damages) {
      let dmgFormula = this.getFoundryDiceFromLevel( rollData.damages.value )
      let dmgRoll = new Roll(dmgFormula).roll( { async: false} );
      await this.showDiceSoNice(dmgRoll, game.settings.get("core", "rollMode") );
      rollData.dmgResult =  dmgRoll.total;
    }

    this.createChatWithRollMode( rollData.alias, {
      content: await renderTemplate(`systems/fvtt-pegasus-rpg/templates/chat-generic-result.html`, rollData)
    });

    if ( rollData.defender ) {
      this.storeDefenseState( rollData );
    }
    this.saveRollData( rollData );
  }

  /* -------------------------------------------- */
  static getDamageDice( result ) {
    if ( result < 0) return 0;
    return Math.floor(result/5) + 1;
  }

  /* -------------------------  ------------------- */
  static async updateRoll( rollData) {

    let diceResults = rollData.diceResults;
    let sortedRoll = [];
    for (let i=0; i<10; i++) {
      sortedRoll[i] = 0;
    }
    for (let dice of diceResults) {
      sortedRoll[dice.result]++;
    }
    let index = 0;
    let bestRoll  = 0;
    for (let i=0; i<10; i++) {
      if ( sortedRoll[i] > bestRoll) {
        bestRoll = sortedRoll[i];
        index = i;
      }
    }
    let bestScore = (bestRoll * 10) + index;
    rollData.bestScore = bestScore;
    rollData.finalScore = bestScore + rollData.negativeModifier + rollData.positiveModifier;

    this.saveRollData(rollData );
  
    this.createChatWithRollMode( rollData.alias, {
      content: await renderTemplate(`systems/fvtt-weapons-of-the-gods/templates/chat-generic-result.html`, rollData)
    });
  }

  /* -------------------------  ------------------- */
  static async rerollDice( actorId, diceIndex = -1 ) {
    let actor = game.actors.get(actorId);
    let rollData = actor.getRollData(  );

    if ( diceIndex == -1 ) {
      rollData.hasWillpower = actor.decrementWillpower();
      rollData.roll = undefined;
    } else { 
      let myRoll = new Roll("1d6").roll( { async: false} );
      await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode") );
      console.log("Result: ", myRoll);

      rollData.roll.dice[0].results[diceIndex].result = myRoll.total; // Patch
      rollData.nbStrongHitUsed++;
    }
    this.rollFraggedKingdom( rollData );
  }

  /* -------------------------------------------- */
  static getUsers(filter) {
    return game.users.filter(filter).map(user => user.data._id);
  }
  /* -------------------------------------------- */
  static getWhisperRecipients(rollMode, name) {
    switch (rollMode) {
      case "blindroll": return this.getUsers(user => user.isGM);
      case "gmroll": return this.getWhisperRecipientsAndGMs(name);
      case "selfroll": return [game.user.id];
    }
    return undefined;
  }
  /* -------------------------------------------- */
  static getWhisperRecipientsAndGMs(name) {
    let recep1 = ChatMessage.getWhisperRecipients(name) || [];
    return recep1.concat(ChatMessage.getWhisperRecipients('GM'));
  }

  /* -------------------------------------------- */
  static blindMessageToGM(chatOptions) {
    let chatGM = duplicate(chatOptions);
    chatGM.whisper = this.getUsers(user => user.isGM);
    chatGM.content = "Blinde message of " + game.user.name + "<br>" + chatOptions.content;
    console.log("blindMessageToGM", chatGM);
    game.socket.emit("system.fvtt-weapons-of-the-gods", { msg: "msg_gm_chat_message", data: chatGM });
  }

  /* -------------------------------------------- */
  static split3Columns(data) {
    
    let array = [ [], [], [] ];
    if (data== undefined) return array;

    let col = 0;
    for (let key in data) {
      let keyword = data[key];
      keyword.key = key; // Self-reference
      array[col].push( keyword);
      col++;
      if (col == 3) col = 0;
    } 
    return array;
  }

  /* -------------------------------------------- */
  static createChatMessage(name, rollMode, chatOptions) {
    switch (rollMode) {
      case "blindroll": // GM only
        if (!game.user.isGM) {
          this.blindMessageToGM(chatOptions);

          chatOptions.whisper = [game.user.id];
          chatOptions.content = "Message only to the GM";
        }
        else {
          chatOptions.whisper = this.getUsers(user => user.isGM);
        }
        break;
      default:
        chatOptions.whisper = this.getWhisperRecipients(rollMode, name);
        break;
    }
    chatOptions.alias = chatOptions.alias || name;
    ChatMessage.create(chatOptions);
  }

  /* -------------------------------------------- */
  static createChatWithRollMode(name, chatOptions) {
    this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions);
  }

  /* -------------------------------------------- */
  static async confirmDelete(actorSheet, li) {
    let itemId = li.data("item-id");
    let msgTxt = "<p>Are you sure to remove this Item ?";
    let buttons = {
      delete: {
          icon: '<i class="fas fa-check"></i>',
          label: "Yes, remove it",
          callback: () => {
            actorSheet.actor.deleteEmbeddedDocuments( "Item", [itemId] );
            li.slideUp(200, () => actorSheet.render(false));
          }
        },
        cancel: {
          icon: '<i class="fas fa-times"></i>',
          label: "Cancel"
        }
      }
      msgTxt += "</p>";
      let d = new Dialog({
        title: "Confirm removal",
        content: msgTxt,
        buttons: buttons,
        default: "cancel"
      });
      d.render(true);
  }

}