import { RdDCalendrierEditeur } from "./rdd-calendrier-editeur.js";
import { RdDAstrologieEditeur } from "./rdd-astrologie-editeur.js";
import { RdDResolutionTable } from "./rdd-resolution-table.js";
import { RdDUtility } from "./rdd-utility.js";
import { RdDDice } from "./rdd-dice.js";
import { Misc } from "./misc.js";
import { HIDE_DICE, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js";
import { DialogChronologie } from "./dialog-chronologie.js";
import { RdDTimestamp, WORLD_TIMESTAMP_SETTING } from "./rdd-timestamp.js";
import { DialogChateauDormant } from "./sommeil/dialog-chateau-dormant.js";
import { ReglesOptionelles } from "./settings/regles-optionelles.js";

const RDD_JOUR_PAR_MOIS = 28;
const RDD_HEURES_PAR_JOUR = 12;
const MAX_NOMBRE_ASTRAL = 12;
const JOURS_DU_MOIS = Array(RDD_JOUR_PAR_MOIS).fill().map((item, index) => 1 + index);

/* -------------------------------------------- */
export class RdDCalendrier extends Application {

  static get defaultOptions() {
    return mergeObject(super.defaultOptions, {
      template: "systems/foundryvtt-reve-de-dragon/templates/calendar-template.html",
      popOut: false,
      resizable: false
    });
  }

  static createCalendrierPos() {
    return { top: 200, left: 200 };
  }

  constructor() {
    super();
    // position
    this.calendrierPos = duplicate(game.settings.get(SYSTEM_RDD, "calendrier-pos"));
    if (this.calendrierPos == undefined || this.calendrierPos.top == undefined) {
      this.calendrierPos = RdDCalendrier.createCalendrierPos();
      game.settings.set(SYSTEM_RDD, "calendrier-pos", this.calendrierPos);
    }
    // Calendrier
    this.timestamp = RdDTimestamp.getWorldTime();

    if (Misc.isUniqueConnectedGM()) { // Uniquement si GM
      RdDTimestamp.setWorldTime(this.timestamp);
      this.listeNombreAstral = this.getListeNombreAstral();
      this.rebuildListeNombreAstral(HIDE_DICE); // Ensure always up-to-date
    }
    console.log('RdDCalendrier.constructor()', this.timestamp, this.timestamp.toCalendrier(), this.calendrierPos, this.listeNombreAstral);
    Hooks.on('updateSetting', async (setting, update, options, id) => this.onUpdateSetting(setting, update, options, id));
  }

  async onUpdateSetting(setting, update, options, id) {
    if (setting.key == SYSTEM_RDD + '.' + WORLD_TIMESTAMP_SETTING) {
      this.timestamp = RdDTimestamp.getWorldTime();
      this.updateDisplay();
    }
  }

  /* -------------------------------------------- */
  /** @override */
  async activateListeners(html) {
    super.activateListeners(html);
    this.html = html;

    this.updateDisplay();

    this.html.find('.ajout-chronologie').click(ev => DialogChronologie.create());

    this.html.find('.calendar-btn').click(ev => this.onCalendarButton(ev));

    this.html.find('.calendar-set-datetime').click(ev => {
      ev.preventDefault();
      this.showCalendarEditor();
    });

    this.html.find('.calendar-astrologie').click(ev => {
      ev.preventDefault();
      this.showAstrologieEditor();
    });

    this.html.find('.calendar-title').mousedown(ev => {
      ev.preventDefault();
      ev = ev || window.event;
      let isRightMB = false;
      if ("which" in ev) { // Gecko (Firefox), WebKit (Safari/Chrome) & Opera
        isRightMB = ev.which == 3;
      } else if ("button" in ev) { // IE, Opera 
        isRightMB = ev.button == 2;
      }

      if (!isRightMB) {
        dragElement(document.getElementById("calendar-time-container"));
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;

        function dragElement(elmnt) {
          elmnt.onmousedown = dragMouseDown;
          function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;

            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
          }

          function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            // calculate the new cursor position:
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            // set the element's new position:
            elmnt.style.bottom = undefined
            elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
            elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
          }

          function closeDragElement() {
            // stop moving when mouse button is released:
            elmnt.onmousedown = undefined;
            document.onmouseup = undefined;
            document.onmousemove = undefined;
            let xPos = (elmnt.offsetLeft - pos1) > window.innerWidth ? window.innerWidth - 200 : (elmnt.offsetLeft - pos1);
            let yPos = (elmnt.offsetTop - pos2) > window.innerHeight - 20 ? window.innerHeight - 100 : (elmnt.offsetTop - pos2)
            xPos = xPos < 0 ? 0 : xPos;
            yPos = yPos < 0 ? 0 : yPos;
            if (xPos != (elmnt.offsetLeft - pos1) || yPos != (elmnt.offsetTop - pos2)) {
              elmnt.style.top = (yPos) + "px";
              elmnt.style.left = (xPos) + "px";
            }
            game.system.rdd.calendrier.calendrierPos.top = yPos;
            game.system.rdd.calendrier.calendrierPos.left = xPos;
            if (game.user.isGM) {
              game.settings.set(SYSTEM_RDD, "calendrier-pos", duplicate(game.system.rdd.calendrier.calendrierPos));
            }
          }
        }
      } else if (isRightMB) {
        game.system.rdd.calendrier.calendrierPos.top = 200;
        game.system.rdd.calendrier.calendrierPos.left = 200;
        if (game.user.isGM) {
          game.settings.set(SYSTEM_RDD, "calendrier-pos", duplicate(game.system.rdd.calendrier.calendrierPos));
        }
        this.setPos(game.system.rdd.calendrier.calendrierPos);
      }
    });
  }
  /* -------------------------------------------- */
  getListeNombreAstral() {
    return game.settings.get(SYSTEM_RDD, "liste-nombre-astral") ?? [];
  }

  /* -------------------------------------------- */
  dateCourante() {
    return this.timestamp.formatDate();
  }

  isAfterIndexDate(indexDate) {
    // TODO: standardize
    return indexDate < this.timestamp.indexDate;
  }

  /* -------------------------------------------- */
  heureCourante() { return RdDTimestamp.definition(this.timestamp.heure); }

  /* -------------------------------------------- */
  getCurrentMinute() { return this.timestamp.indexMinute; }

  getTimestamp() {
    return this.timestamp;
  }
  getTimestampFinChateauDormant(nbJours = 0) {
    return this.timestamp.nouveauJour().addJours(nbJours);
  }

  getTimestampFinHeure(nbHeures = 0) {
    return this.timestamp.nouvelleHeure().addHeures(nbHeures);
  }

  /* -------------------------------------------- */
  getIndexFromDate(jour, mois) {
    const addYear = mois < this.timestamp.mois || (mois == this.timestamp.mois && jour < this.timestamp.jour)
    const time = RdDTimestamp.timestamp(this.timestamp.annee + (addYear ? 1 : 0), mois, jour);
    return time.indexDate;
  }

  /* -------------------------------------------- */
  getJoursSuivants(count) {
    let jours = [];
    let indexDate = this.timestamp.indexDate;
    for (let i = 0; i < count; i++, indexDate++) {
      jours[i] = { label: RdDTimestamp.formatIndexDate(indexDate), index: indexDate };
    }
    return jours;
  }

  /* -------------------------------------------- */
  async ajouterNombreAstral(indexDate, showDice = SHOW_DICE) {
    const nombreAstral = await RdDDice.rollTotal("1dh", { showDice: showDice, rollMode: "selfroll" });
    const dateFuture = RdDTimestamp.formatIndexDate(indexDate);
    if (showDice != HIDE_DICE) {
      ChatMessage.create({
        whisper: ChatMessage.getWhisperRecipients("GM"),
        content: `Le chiffre astrologique du ${dateFuture} sera le ${nombreAstral}`
      });
    }
    return {
      nombreAstral: nombreAstral,
      valeursFausses: [],
      index: indexDate
    }
  }

  /* -------------------------------------------- */
  resetNombreAstral() {
    this.listeNombreAstral = [];
    game.settings.set(SYSTEM_RDD, "liste-nombre-astral", []);

    game.socket.emit(SYSTEM_SOCKET_ID, {
      msg: "msg_reset_nombre_astral",
      data: {}
    });
  }

  /* -------------------------------------------- */
  /**
   * 
   * @param {*} indexDate la date pour laquelle obtenir le nombre astral. Si undefined, on prend la date du jour
   * @returns le nombre astral pour la date, ou pour la date du jour si la date n'est pas fournie.
   *  Si aucun nombre astral n'est trouvé, retourne 0 (cas où l'on demanderait un nombre astral en dehors des 12 jours courant et à venir)
   */
  getNombreAstral(indexDate = undefined) {
    if (indexDate == undefined) {
      indexDate = this.timestamp.indexDate;
    }
    const listNombreAstral = this.getListeNombreAstral();
    let astralData = listNombreAstral.find((nombreAstral, i) => nombreAstral.index == indexDate);
    return astralData?.nombreAstral ?? 0;
  }

  /* -------------------------------------------- */
  async rebuildListeNombreAstral(showDice = HIDE_DICE) {
    if (Misc.isUniqueConnectedGM()) {
      let newList = [];
      for (let i = 0; i < MAX_NOMBRE_ASTRAL; i++) {
        let dayIndex = this.timestamp.indexDate + i;
        let na = this.listeNombreAstral.find(n => n.index == dayIndex);
        if (na) {
          newList[i] = na;
        } else {
          newList[i] = await this.ajouterNombreAstral(dayIndex, showDice);
        }
      }
      this.listeNombreAstral = newList;
      game.settings.set(SYSTEM_RDD, "liste-nombre-astral", newList);
    }
  }

  /* -------------------------------------------- */
  async setNewTimestamp(newTimestamp) {
    const oldTimestamp = this.timestamp;
    await Promise.all(game.actors.map(async actor => await actor.onTimeChanging(oldTimestamp, newTimestamp)));
    RdDTimestamp.setWorldTime(newTimestamp);
    if (oldTimestamp.indexDate + 1 == newTimestamp.indexDate && ReglesOptionelles.isUsing("chateau-dormant-gardien")) {
      await DialogChateauDormant.create();
    }
    this.timestamp = newTimestamp;
    await this.rebuildListeNombreAstral();
    this.updateDisplay();
  }

  /* -------------------------------------------- */
  async onCalendarButton(ev) {
    ev.preventDefault();
    const calendarAvance = ev.currentTarget.attributes['data-calendar-avance'];
    const calendarSet = ev.currentTarget.attributes['data-calendar-set'];
    if (calendarAvance) {
      await this.incrementTime(Number(calendarAvance.value));
    }
    else if (calendarSet) {
      this.positionnerHeure(Number(calendarSet.value));
    }
    this.updateDisplay();
  }

  /* -------------------------------------------- */
  async incrementTime(minutes = 0) {
    await this.setNewTimestamp(this.timestamp.addMinutes(minutes));
  }

  /* -------------------------------------------- */
  async incrementerJour() {
    await this.setNewTimestamp(this.timestamp.nouveauJour());
  }

  /* -------------------------------------------- */
  async positionnerHeure(heure) {
    const indexDate = this.timestamp.indexDate;
    const addDay = this.timestamp.heure < heure ? 0 : 1;
    const newTimestamp = new RdDTimestamp({ indexDate: indexDate + addDay}).addHeures(heure);
    await this.setNewTimestamp(newTimestamp)
  }

  /* -------------------------------------------- */
  fillCalendrierData(formData = {}) {
    mergeObject(formData, this.timestamp.toCalendrier());
    formData.isGM = game.user.isGM;
    return formData;
  }

  /* -------------------------------------------- */
  getLectureAstrologieDifficulte(dateIndex) {
    let indexNow = this.timestamp.indexDate;
    let diffDay = dateIndex - indexNow;
    return - Math.floor(diffDay / 2);
  }

  /* -------------------------------------------- */
  async requestNombreAstral(request) {
    const actor = game.actors.get(request.id);
    if (Misc.isUniqueConnectedGM()) { // Only once
      console.log(request);
      let jourDiff = this.getLectureAstrologieDifficulte(request.date);
      let niveau = Number(request.astrologie.system.niveau) + Number(request.conditions) + Number(jourDiff) + Number(request.etat);
      let rollData = {
        caracValue: request.carac_vue,
        finalLevel: niveau,
        showDice: HIDE_DICE,
        rollMode: "blindroll"
      };
      await RdDResolutionTable.rollData(rollData);
      request.rolled = rollData.rolled;
      request.isValid = request.rolled.isSuccess;
      request.nbAstral = this.getNombreAstral(request.date);

      if (request.rolled.isSuccess) {
        if (request.rolled.isPart) {
          // Gestion expérience (si existante)
          request.competence = actor.getCompetence("astrologie")
          request.selectedCarac = actor.system.carac["vue"];
          actor.appliquerAjoutExperience(request, 'hide');
        }
      }
      else {
        request.nbAstral = await RdDDice.rollTotal("1dhr" + request.nbAstral, {
          rollMode: "selfroll", showDice: HIDE_DICE
        });
        // Mise à jour des nombres astraux du joueur
        this.addNbAstralIncorect(request.id, request.date, request.nbAstral);
      }

      if (Misc.getActiveUser(request.userId)?.isGM) {
        RdDUtility.responseNombreAstral(request);
      } else {
        game.socket.emit(SYSTEM_SOCKET_ID, {
          msg: "msg_response_nombre_astral",
          data: request
        });
      }
    }
  }

  addNbAstralIncorect(actorId, date, nbAstral) {
    let astralData = this.listeNombreAstral.find((nombreAstral, i) => nombreAstral.index == date);
    astralData.valeursFausses.push({ actorId: actorId, nombreAstral: nbAstral });
    game.settings.set(SYSTEM_RDD, "liste-nombre-astral", this.listeNombreAstral);
  }

  static ecartHeureChance(heureNaissance, nombreAstral, heure) {
    return (heureNaissance + nombreAstral - heure) % RDD_HEURES_PAR_JOUR;
  }

  /* -------------------------------------------- */
  getAjustementAstrologique(heureNaissance, name = undefined) {
    let defHeure = RdDTimestamp.findHeure(heureNaissance);
    if (defHeure) {
      return RdDCalendrier.ajustementAstrologiqueHeure(defHeure.heure, this.getNombreAstral(), this.timestamp.heure);
    }
    else if (name) {
      ui.notifications.warn(name + " n'a pas d'heure de naissance, ou elle est incorrecte : " + heureNaissance);
    }
    else {
      ui.notifications.warn(heureNaissance + " ne correspond pas à une heure de naissance");
    }
    return 0;
  }

  static ajustementAstrologiqueHeure(hn, nbAstral, heure) {
    switch (RdDCalendrier.ecartHeureChance(hn, nbAstral, heure)) {
      case 0: return 4;
      case 4: case 8: return 2;
      case 6: return -4;
      case 3: case 9: return -2;
    }
    return 0;
  }

  /* -------------------------------------------- */
  getData() {
    let formData = super.getData();
    this.fillCalendrierData(formData);
    this.setPos(this.calendrierPos);
    return formData;
  }

  /* -------------------------------------------- */
  setPos(pos) {
    return new Promise(resolve => {
      function check() {
        let elmnt = document.getElementById("calendar-time-container");
        if (elmnt) {
          elmnt.style.bottom = undefined;
          let xPos = (pos.left) > window.innerWidth ? window.innerWidth - 200 : pos.left;
          let yPos = (pos.top) > window.innerHeight - 20 ? window.innerHeight - 100 : pos.top;
          elmnt.style.top = (yPos) + "px";
          elmnt.style.left = (xPos) + "px";
          resolve();
        } else {
          setTimeout(check, 30);
        }
      }
      check();
    });
  }

  /* -------------------------------------------- */
  updateDisplay() {
    let calendrier = this.fillCalendrierData();
    // Rebuild text du calendrier
    let dateHTML = `${calendrier.jourDuMois} ${calendrier.mois.label} (${calendrier.mois.saison}) de l'année ${calendrier.annee}`
    if (game.user.isGM) {
      dateHTML = dateHTML + "<br>Nombre Astral: " + (this.getNombreAstral() ?? "?");
    }
    for (let handle of document.getElementsByClassName("calendar-title")) {
      handle.innerHTML = dateHTML;
    }
    for (let heure of document.getElementsByClassName("calendar-heure-texte")) {
      heure.innerHTML = calendrier.heure.label;
    }
    for (const minute of document.getElementsByClassName("calendar-minute-texte")) {
      minute.innerHTML = `${calendrier.minute} minutes`;
    }
    for (const heureImg of document.getElementsByClassName("calendar-heure-img")) {
      heureImg.src = calendrier.heure.icon;
    }
  }

  /* -------------------------------------------- */
  async saveEditeur(calendrierData) {
    const newTimestamp = RdDTimestamp.timestamp(
      Number.parseInt(calendrierData.annee),
      calendrierData.mois.heure,
      Number.parseInt(calendrierData.jourMois),
      calendrierData.heure.heure,
      Number.parseInt(calendrierData.minutes)
    );
    await this.setNewTimestamp(newTimestamp);
  }

  /* -------------------------------------------- */
  async showCalendarEditor() {
    let calendrierData = this.fillCalendrierData();
    if (this.editeur == undefined) {
      let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/calendar-editor-template.html', calendrierData);
      this.editeur = new RdDCalendrierEditeur(html, this, calendrierData)
    }
    this.editeur.updateData(calendrierData);
    this.editeur.render(true);
  }

  static buildJoursMois() { return JOURS_DU_MOIS; }

  /* -------------------------------------------- */
  async showAstrologieEditor() {
    const calendrierData = duplicate(this.fillCalendrierData());
    this.listeNombreAstral = this.listeNombreAstral || [];

    calendrierData.astrologieData = this.listeNombreAstral.map(astro => {
      const timestamp = new RdDTimestamp({ indexDate: astro.index });
      astro.date = { mois: timestamp.mois, jour: timestamp.jour + 1 }
      for (let vf of astro.valeursFausses) {
        let actor = game.actors.get(vf.actorId);
        vf.actorName = (actor) ? actor.name : "Inconnu";
      }
      return astro;
    });

    const nbAstral = this.getNombreAstral()
    calendrierData.heures = Array.from(Array(RDD_HEURES_PAR_JOUR).keys());
    calendrierData.ajustementsActeur = game.actors.filter(it => it.isPersonnage() && it.hasPlayerOwner).map(actor => {
      return {
        actor,
        ajustements: calendrierData.heures.map(heure => {
          const hn = RdDTimestamp.findHeure(actor.getHeureNaissance())?.heure;
          return {
            heure,
            ajustement: RdDCalendrier.ajustementAstrologiqueHeure(hn, nbAstral, heure)
          }
        })
      }
    });

    let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/calendar-astrologie-template.html', calendrierData);
    let astrologieEditeur = new RdDAstrologieEditeur(html, this, calendrierData)
    astrologieEditeur.updateData(calendrierData);
    astrologieEditeur.render(true);
  }
}