<template>
  <div class="header">
    <div class="logo"></div>
    <h1 class="appname">BDLG planner</h1>
    <nav class="tabs floating">
      <router-link class="tab" active-class="selected" to="/"> Accueil </router-link>
      <router-link class="tab" active-class="selected" to="/evenement">Planning</router-link>
      <router-link class="tab" active-class="selected" to="/competences">
        Gestion des compétences
      </router-link>
      <router-link class="tab" active-class="selected" to="/benevoles">
        Gestion des bénévoles
      </router-link>
      <router-link class="tab" active-class="selected" to="/planningIndividuel">
        Planning Individuel
      </router-link>
    </nav>
  </div>
  <div class="container">
    <router-view
      @export="exportStateToJson"
      @import="(e) => importJsonState(e, false)"
      @localSave="localSave"
      @solve="solve"
    />
    <div v-if="optimisationInProgress" class="modal">
      <div class="modal-content">
        <h3>Optimisation en cours</h3>
        <p>L'ordinateur calcul un meilleur planning</p>
        <div class="spinner"><span class="material-icons"> sync </span></div>
      </div>
    </div>
    <div v-if="showExplanation" class="modal" @click="showExplanation = false">
      <div class="modal-content" v-html="explanation" @click="(e) => e.stopPropagation()"></div>
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import toast, { Toast } from "./utils/Toast";
import "@/assets/css/tabs.css";
import ConstraintTranslation from "@/assets/ConstraintTranslation";
import Evenement from "./models/Evenement";
import Benevole from "./models/Benevole";
import Creneau from "./models/Creneau";
import Competence from "./models/Competence";
import {
  AssignementPair,
  SolverInput,
  SolverOutput,
  JustificationObject,
  ScoreExplanation,
  HardConstraint,
} from "./models/SolverInput";
import Ressource, { IRessource, RessourceJSON } from "jc-timeline/lib/Ressource";
import { MutationTypes } from "./store/Mutations";
import dayjs from "dayjs";
import { StateJSON } from "./store/State";
const keyofEvent: Array<keyof Evenement> = ["name", "uuid", "start", "end"];
export default defineComponent({
  data() {
    return {
      optimisationInProgress: false,
      explanation: "",
      showExplanation: false,
      lastToast: null as null | Toast,
    };
  },
  mounted() {
    const previousState = window.localStorage.getItem("activeState");
    toast({ html: "coucou" });
    if (previousState) {
      this.importJsonState(JSON.parse(previousState), false);
    }
    window.onbeforeunload = () => {
      this.localSave();
    };
  },
  methods: {
    localSave() {
      window.localStorage.setItem("activeState", JSON.stringify(this.$store.getters.getJSONState));
    },
    clearToast() {
      if (this.lastToast) {
        this.lastToast.dismiss();
        this.lastToast = null;
      }
    },
    exportStateToJson(): void {
      const obj: StateJSON = this.$store.getters.getJSONState;
      const mimeType = "data:text/json;charset=utf-8";
      const dummy = document.createElement("a");
      dummy.href = mimeType + ", " + encodeURI(JSON.stringify(obj));
      dummy.download =
        "Planning-" + this.$store.state.evenement.name + dayjs().format("-YYYY-MM-DD[.json]");
      document.body.appendChild(dummy);
      dummy.click();
      setTimeout(() => document.body.removeChild(dummy), 10000);
    },
    solve() {
      const meals = this.$store.state.creneauList.filter((o) => o.isMeal);
      const slots = this.$store.state.creneauList.filter((o) => !o.isMeal);
      const timeslots = slots.map((o) => o.toTimeslotRaw());
      const mealSlots = meals.map((o) => o.toMealSlot());
      const volonteerList = this.$store.state.benevoleList.map((b) => b.toVolonteeRaw());
      const constraints = this.$store.state.competenceList.map((c) => c.toSkill());
      const getAssignementPair = (c: Creneau) =>
        c.benevoleIdList.map((id) => {
          const obj: AssignementPair = { volonteerId: id, slotId: c.id };
          if (c.fixedAttendee) obj.isFixed = true;
          return obj;
        });
      const assignements = slots.flatMap(getAssignementPair);
      const mealAssignements = meals.flatMap(getAssignementPair);

      const body: SolverInput = {
        constraints,
        timeslots,
        mealSlots,
        volonteerList,
        assignements,
        mealAssignements,
      };
      const options = {
        method: "POST",
        body: JSON.stringify(body),
        headers: { "Content-type": "application/json; charset=UTF-8" },
      };
      this.optimisationInProgress = true;
      fetch(`http://localhost:8080/planning/solve`, options)
        .then((response) => response.json())
        .then(this.updatePlanningWithNewPairing)
        .catch((error) => {
          toast({ html: "Pas de réponse du serveur<br>" + error.toString(), classes: "error" });
          this.optimisationInProgress = false;
        });
    },
    updatePlanningWithNewPairing(data: SolverOutput): void {
      this.optimisationInProgress = false;
      // Display message from the solver
      toast({
        html: "Le planning a été mis à jour <br>Score : " + data.score,
        classes: "success",
        displayLength: 10000,
      });
      if (data.message) toast({ html: data.message, classes: "warning", displayLength: 10000 });
      this.displayExplanation(data.explanation);

      // Remove previous timeslot assignement
      this.$store.commit(MutationTypes.clearBenevole2Creneau, undefined);
      // Add new timeslot assignement
      for (const pair of data.assignements) {
        this.$store.commit(MutationTypes.addBenevole2Creneau, {
          creneauId: pair.slotId,
          benevoleId: pair.volonteerId,
        });
      }
    },
    displayExplanation(explanation: string) {
      // Display score explanation if any
      const scoreExplanation: ScoreExplanation = JSON.parse(explanation);
      let scoreExplanationHTML = "";
      let k: HardConstraint;
      for (k in scoreExplanation) {
        // Title of the section
        scoreExplanationHTML += `<h4>${ConstraintTranslation[k]}</h4>`;
        // Content of the section splitted by group
        scoreExplanationHTML += scoreExplanation[k].map((list) => {
          const pairs = list
            .map((o) => {
              if (typeof o === "string" || o instanceof String) {
                return o;
              } else {
                const parseObj = o as JustificationObject;
                const slot = this.$store.getters.getCreneauById(parseObj.slotId);
                const benevole = this.$store.getters.getBenevoleById(parseObj.volonteerId);
                return `Bénevole :\t${benevole?.shortame}<br>Créneau:\t${slot?.title} ${slot?.horaire}`;
              }
            })
            .join("<br>Et<br>");
          if (pairs != "") {
            return `<p>${pairs}</p>`;
          }
          return "";
        });
        // End of the section
        scoreExplanationHTML += "<hr>";
      }
      this.explanation = scoreExplanationHTML;
      this.clearToast();
      this.lastToast = toast({
        html: `<span>Ouvrir l'explication du score</span><i class="material-icons">close<i>`,
        displayLength: Infinity,
        classes: "action",
      });
      this.lastToast.element.firstElementChild?.addEventListener(
        "click",
        () => (this.showExplanation = true)
      );
      this.lastToast.element.lastElementChild?.addEventListener("click", this.clearToast);
    },
    importJsonState(obj: StateJSON, preserve = false) {
      // Remove previous content and load the main event title
      if (preserve == false) {
        this.$store.commit(MutationTypes.resetState, undefined);
        const e = Evenement.fromJSON(obj.evenement);
        for (const k of keyofEvent) {
          this.$store.commit(MutationTypes.editEvenement, {
            field: k,
            value: e[k],
          });
        }
      }
      // Import constraint
      obj.competences.forEach((c) => {
        this.$store.commit(MutationTypes.addConstraint, Competence.fromJSON(c));
      });
      // Import Benevoles
      obj.benevoles.forEach((b) => {
        this.$store.commit(MutationTypes.addBenevole, Benevole.fromJSON(b));
      });
      // Import creneau group
      const dict: { [k: string]: RessourceJSON } = {};
      obj.creneauGroups.forEach((element) => {
        dict[element.id] = element;
      });
      // map parent to children
      const creneauGroups = obj.creneauGroups.map((ressource) => {
        const iRessource: IRessource = { ...ressource };
        if (iRessource.parentId) {
          if (iRessource.parentId in dict) {
            iRessource.parent = dict[iRessource.parentId];
          } else {
            throw new Error("Missing parent of creneau group : " + ressource.id);
          }
        }
        return iRessource;
      });
      // Push the items to this application
      creneauGroups.forEach((r) => {
        this.$store.commit(MutationTypes.addCreneauGroup, new Ressource(r));
      });
      // Import Creneau
      for (let c of obj.creneaux) {
        const creneau = Creneau.fromJSON(c);
        this.$store.commit(MutationTypes.addCreneau, creneau);
        // add the creneau to the corresponding benevole
        for (const id of creneau.benevoleIdList) {
          const b = this.$store.getters.getBenevoleById(id);
          if (b) {
            this.$store.commit(MutationTypes.editBenevole, {
              id: id,
              field: "creneauIdList",
              value: Array.from(new Set([...b.creneauIdList, creneau.id])),
            });
          } else {
            throw new Error(`The benevole ${id} was not find `);
          }
        }
      }
    },
  },
});
</script>

<style>
.toast.action > span {
  cursor: pointer;
  color: var(--color-primary-600);
}
.toast.action > i.material-icons {
  cursor: pointer;
  margin-left: 16px;
  margin-right: -8px;
  font-size: 18px;
}
</style>
