import { SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js";
import { Grammar } from "./grammar.js";
import { Misc } from "./misc.js";
import { RdDDice } from "./rdd-dice.js";

export const WORLD_TIMESTAMP_SETTING = "calendrier";

const RDD_JOURS_PAR_AN = 336; //RDD_JOURS_PAR_MOIS * RDD_MOIS_PAR_AN;
const RDD_MOIS_PAR_AN = 12;
export const RDD_JOURS_PAR_MOIS = 28;
export const RDD_HEURES_PAR_JOUR = 12;
export const MAX_NOMBRE_ASTRAL = 12;
export const RDD_MINUTES_PAR_HEURES = 120;
export const RDD_MINUTES_PAR_JOUR = 1440; //RDD_HEURES_PAR_JOUR * RDD_MINUTES_PAR_HEURES;
const ROUNDS_PAR_MINUTE = 10;

const DEFINITION_HEURES = [
  { key: "vaisseau", label: "Vaisseau", lettreFont: 'v', saison: "Printemps" },
  { key: "sirene", label: "Sirène", lettreFont: 'i', saison: "Printemps" },
  { key: "faucon", label: "Faucon", lettreFont: 'f', saison: "Printemps" },
  { key: "couronne", label: "Couronne", lettreFont: '', saison: "Eté" },
  { key: "dragon", label: "Dragon", lettreFont: 'd', saison: "Eté" },
  { key: "epees", label: "Epées", lettreFont: 'e', saison: "Eté" },
  { key: "lyre", label: "Lyre", lettreFont: 'l', saison: "Automne" },
  { key: "serpent", label: "Serpent", lettreFont: 's', saison: "Automne" },
  { key: "poissonacrobate", label: "Poisson Acrobate", lettreFont: 'p', saison: "Automne" },
  { key: "araignee", label: "Araignée", lettreFont: 'a', saison: "Hiver" },
  { key: "roseau", label: "Roseau", lettreFont: 'r', saison: "Hiver" },
  { key: "chateaudormant", label: "Château Dormant", lettreFont: 'c', saison: "Hiver" },
]

const FORMULES_DUREE = [
  { code: "", label: "", calcul: async (t, actor) => t.addJours(100 * RDD_JOURS_PAR_AN) },
  { code: "jour", label: "1 jour", calcul: async (t, actor) => t.nouveauJour().addJours(1) },
  { code: "1d7jours", label: "1d7 jour", calcul: async (t, actor) => t.nouveauJour().addJours(await RdDDice.rollTotal('1d7', { showDice: SHOW_DICE })) },
  { code: "1ddr", label: "Un dé draconique jours", calcul: async (t, actor) => t.nouveauJour().addJours(await RdDDice.rollTotal('1dr+7', { showDice: SHOW_DICE })) },
  { code: "hn", label: "Fin de l'Heure de Naissance", calcul: async (t, actor) => t.finHeure(actor.getHeureNaissance()) },
  // { code: "1h", label: "Une heure", calcul: async (t, actor) => t.nouvelleHeure().addHeures(1) },
  // { code: "12h", label: "12 heures", calcul: async (t, actor) => t.nouvelleHeure().addHeures(12) },
  // { code: "chateaudormant", label: "Fin Chateau dormant", calcul: async (t, actor) => t.nouveauJour() },
  // { code: "special", label: "Spéciale", calcul: async (t, actor) => t.addJours(100 * RDD_JOURS_PAR_AN) },
]
const FORMULES_PERIODE = [
  { code: 'round', label: "Rounds", calcul: async (t, nombre) => t.addMinutes(nombre / 10) },
  { code: 'minute', label: "Minutes", calcul: async (t, nombre) => t.addMinutes(nombre) },
  { code: 'heure', label: "Heures", calcul: async (t, nombre) => t.addHeures(nombre) },
  { code: 'jour', label: "Jours", calcul: async (t, nombre) => t.addJours(nombre) },
]


export class RdDTimestamp {

  static init() {
    game.settings.register(SYSTEM_RDD, WORLD_TIMESTAMP_SETTING, {
      name: WORLD_TIMESTAMP_SETTING,
      scope: "world",
      config: false,
      default: { indexDate: 0, indexMinute: 0 },
      type: Object
    });

    for (let i = 0; i < DEFINITION_HEURES.length; i++) {
      DEFINITION_HEURES[i].heure = i;
      DEFINITION_HEURES[i].hh = RdDTimestamp.hh(i);
      DEFINITION_HEURES[i].icon = RdDTimestamp.iconeHeure(i);
      DEFINITION_HEURES[i].webp = DEFINITION_HEURES[i].icon.replace(".svg", ".webp");
    }
  }


  static hh(heure) {
    return heure < 9 ? `0${heure + 1}` : `${heure + 1}`;
  }

  static iconeHeure(heure) {
    return `systems/foundryvtt-reve-de-dragon/icons/heures/hd${RdDTimestamp.hh(heure)}.svg`;
  }

  static definitions() {
    return DEFINITION_HEURES
  }

  static formulesDuree() {
    return FORMULES_DUREE
  }

  static formulesPeriode() {
    return FORMULES_PERIODE
  }

  static heures() {
    return Misc.intArray(0, RDD_HEURES_PAR_JOUR)
  }

  /**
   * @param signe  
   * @returns L'entrée de DEFINITION_HEURES correspondant au signe
  */
  static definition(signe) {
    if (signe == undefined) {
      signe = 0;
    }
    if (Number.isInteger(signe)) {
      return DEFINITION_HEURES[signe % RDD_HEURES_PAR_JOUR];
    }
    let definition = DEFINITION_HEURES.find(it => it.key == signe);
    if (!definition) {
      definition = Misc.findFirstLike(signe, DEFINITION_HEURES, { mapper: it => it.label, description: 'signe' });
    }
    return definition
  }

  static imgSigneHeure(heure) {
    return RdDTimestamp.imgSigne(RdDTimestamp.definition(heure));
  }

  static imgSigne(signe) {
    return signe == undefined ? '' : `<img class="img-signe-heure" src="${signe.webp}" alt="${signe.label}" title="${signe.label}"/>`
  }

  static ajustementAstrologiqueHeure(hn, nbAstral, heure) {
    let ecart = (hn + nbAstral - heure) % RDD_HEURES_PAR_JOUR;
    if (ecart < 0) {
      ecart = (ecart + RDD_HEURES_PAR_JOUR) % RDD_HEURES_PAR_JOUR;
    }
    switch (ecart) {
      case 0: return 4;
      case 4: case 8: return 2;
      case 6: return -4;
      case 3: case 9: return -2;
    }
    return 0;
  }

  static handleTimestampEditor(html, path, consumeTimestamp = async (path, timestamp) => { }) {
    const fields = {
      annee: html.find(`input[name="${path}.annee"]`),
      mois: html.find(`select[name="${path}.mois"]`),
      jourDuMois: html.find(`input[name="${path}.jourDuMois"]`),
      heure: html.find(`select[name="${path}.heure"]`),
      minute: html.find(`input[name="${path}.minute"]`)
    };

    async function onChangeTimestamp(fields, path) {
      const annee = Number(fields.annee.val());
      const mois = fields.mois.val();
      const jour = Number(fields.jourDuMois.val());
      const heure = fields.heure.val();
      const minute = Number(fields.minute.val());
      await consumeTimestamp(path, RdDTimestamp.timestamp(annee, mois, jour, heure, minute));
    }

    fields.annee.change(async (event) => await onChangeTimestamp(fields, path));
    fields.mois.change(async (event) => await onChangeTimestamp(fields, path));
    fields.jourDuMois.change(async (event) => await onChangeTimestamp(fields, path));
    fields.heure.change(async (event) => await onChangeTimestamp(fields, path));
    fields.minute.change(async (event) => await onChangeTimestamp(fields, path));
  }


  static findHeure(heure) {
    heure = Grammar.toLowerCaseNoAccentNoSpace(heure);
    let parHeureOuLabel = DEFINITION_HEURES.filter(it => (it.heure) == parseInt(heure) % RDD_HEURES_PAR_JOUR || Grammar.toLowerCaseNoAccentNoSpace(it.label) == heure);
    if (parHeureOuLabel.length == 1) {
      return parHeureOuLabel[0];
    }
    let parLabelPartiel = DEFINITION_HEURES.filter(it => Grammar.toLowerCaseNoAccentNoSpace(it.label).includes(heure));
    if (parLabelPartiel.length > 0) {
      parLabelPartiel.sort(Misc.ascending(h => h.label.length));
      return parLabelPartiel[0];
    }
    return undefined;
  }

  /** 
   * @param indexDate: la date (depuis le jour 0)
   * @return la version formattée de la date 
  */
  static formatIndexDate(indexDate) {
    return new RdDTimestamp({ indexDate }).formatDate()
  }

  static splitIndexDate(indexDate) {
    const timestamp = new RdDTimestamp({ indexDate });
    return {
      jour: timestamp.jour + 1,
      mois: RdDTimestamp.definition(timestamp.mois).key
    }
  }

  static getWorldTime() {
    let worldTime = game.settings.get(SYSTEM_RDD, WORLD_TIMESTAMP_SETTING);
    if (worldTime.indexJour != undefined && worldTime.heureRdD != undefined) {
      // Migration
      worldTime = {
        indexDate: worldTime.indexJour,
        indexMinute: worldTime.heureRdD * 120 + worldTime.minutesRelative
      };
      RdDTimestamp.setWorldTime(new RdDTimestamp(worldTime))
    }
    return new RdDTimestamp(worldTime);
  }

  static setWorldTime(timestamp) {
    game.settings.set(SYSTEM_RDD, WORLD_TIMESTAMP_SETTING, duplicate(timestamp));
  }

  /** construit un RdDTimestamp à partir de l'année/mois/jour/heure?/minute? */
  static timestamp(annee, mois, jour, heure = 0, minute = 0) {
    mois = this.definition(mois)?.heure
    heure = this.definition(heure)?.heure
    return new RdDTimestamp({
      indexDate: (jour - 1) + (mois + annee * RDD_MOIS_PAR_AN) * RDD_JOURS_PAR_MOIS,
      indexMinute: heure * RDD_MINUTES_PAR_HEURES + minute
    })
  }

  /**
   * Constructeur d'un timestamp.
   * Selon les paramètres, l'objet construit se base su:
   * - le timestamp
   * - la date numérique + minute (dans la journée)
   * @param indexDate: la date à utiliser pour ce timestamp
   * @param indexMinute: la minute de la journée à utiliser pour ce timestamp
   * 
  */
  constructor({ indexDate, indexMinute = undefined }) {
    this.indexDate = indexDate
    this.indexMinute = indexMinute ?? 0
  }


  /**
   * Convertit le timestamp en une structure avec les informations utiles
   * pour afficher la date et l'heure
  */
  toCalendrier() {
    return {
      timestamp: this,
      annee: this.annee,
      mois: RdDTimestamp.definition(this.mois),
      jour: this.jour,
      jourDuMois: this.jour + 1,
      heure: RdDTimestamp.definition(this.heure),
      minute: this.minute
    };
  }

  get annee() { return Math.floor(this.indexDate / RDD_JOURS_PAR_AN) }
  get mois() { return Math.floor((this.indexDate % RDD_JOURS_PAR_AN) / RDD_JOURS_PAR_MOIS) }
  get jour() { return (this.indexDate % RDD_JOURS_PAR_AN) % RDD_JOURS_PAR_MOIS }
  get heure() { return Math.floor(this.indexMinute / RDD_MINUTES_PAR_HEURES) }
  get minute() { return this.indexMinute % RDD_MINUTES_PAR_HEURES }
  get round() { return ROUNDS_PAR_MINUTE * (this.indexMinute - Math.floor(this.indexMinute)) }
  get angleHeure() { return this.indexMinute / RDD_MINUTES_PAR_JOUR * 360 - 60 }
  get angleMinute() { return this.indexMinute / RDD_MINUTES_PAR_HEURES * 360 + 45}

  formatDate() {
    const jour = this.jour + 1;
    const mois = RdDTimestamp.definition(this.mois).label;
    const annee = this.annee ?? '';
    return `${jour} ${mois}` + (annee ? ' ' + annee : '');
  }

  nouveauJour() { return new RdDTimestamp({ indexDate: this.indexDate + 1, indexMinute: 0 }) }

  nouvelleHeure() {
    return this.heure >= RDD_HEURES_PAR_JOUR ? this.nouveauJour() : new RdDTimestamp({
      indexDate: this.indexDate,
      indexMinute: (this.heure + 1) * RDD_MINUTES_PAR_HEURES
    })
  }

  addJours(jours) {
    return jours == 0 ? this : new RdDTimestamp({
      indexDate: this.indexDate + jours,
      indexMinute: this.indexMinute
    })
  }

  addHeures(heures) {
    if (heures == 0) {
      return this
    }
    const heure = this.heure + heures;
    return new RdDTimestamp({
      indexDate: this.indexDate + Math.floor(heure / RDD_HEURES_PAR_JOUR),
      indexMinute: this.indexMinute + (heure % RDD_HEURES_PAR_JOUR) * RDD_MINUTES_PAR_HEURES
    })
  }

  addMinutes(minutes) {
    if (minutes == 0) {
      return this;
    }
    const indexMinute = this.indexMinute + minutes;
    const jours = Math.floor(indexMinute / RDD_MINUTES_PAR_JOUR)
    return new RdDTimestamp({
      indexDate: this.indexDate + jours,
      indexMinute: indexMinute - (jours * RDD_MINUTES_PAR_JOUR)
    })
  }

  addPeriode(nombre, unite) {
    const formule = FORMULES_PERIODE.find(it => it.code == unite);
    if (formule) {
      return formule.calcul(this, nombre);
    }
    else {
      ui.notifications.info(`Pas de période pour ${unite ?? 'Aucune uinité définie'}`)
    }
    return this;
  }

  finHeure(heure) {
    return this.nouvelleHeure().addHeures((12 + heure - this.heure) % 12);
  }

  async appliquerDuree(duree, actor) {
    const formule = FORMULES_DUREE.find(it => it.code == duree) ?? FORMULES_DUREE.find(it => it.code == "");
    return await formule.calcul(this, actor);
  }

  compare(timestamp) {
    let diff = this.indexDate - timestamp.indexDate
    if (diff == 0) {
      diff = this.indexMinute - timestamp.indexMinute
    }
    return diff < 0 ? -1 : diff > 0 ? 1 : 0;
  }

  difference(timestamp) {
    const jours = this.indexDate - timestamp.indexDate;
    const minutes = this.indexMinute - timestamp.indexMinute;
    return {
      jours: jours,
      heures: Math.floor(minutes / RDD_MINUTES_PAR_HEURES),
      minutes: minutes % RDD_MINUTES_PAR_HEURES
    }
  }
}