package com.diampark.test.Class;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;

//les objets de la classe "suivi_concentration" sont uniques pour un utilisateurs : ils ne doivent pas être reconstruits et sont voués à évoluer avec le temps

public class SuiviConcentration {

    public final int demi_vie = 140 * 60; //en secondes, le temps de demi-vie de la disparition du levodopa dans l'organisme
    public final int temps_absorption = 30 * 60 * 1000; //en millisecondes, temps que dure l'absorption du levodopa dans l'organisme après ingération
    public final int temps_entre_deux_mesures = 5 * 60 * 1000; //en millisecondes, c'est le pas associé à la discrétisation du temps
    public final int taille_max = 2 * 24 * 12; //nombre d'états que l'on garde en mémoire

    //ci-dessous, des valeurs temprelles correspondant à un modèle beaucoup plus rapide, pour le débuggage
	
	/*
	public final int demi_vie=3;
	public final int temps_absorption=1000;
	public final int temps_entre_deux_mesures = 200;
	public final int taille_max = 2*24*12;
	*/

    public double efficacite_metabolisation; //double entre 0 et 1 indiquant à quel point la metabolisation de levodopa par l'organisme est réalisée efficacement (1 si tout va bien)

    public HashMap<Long, Etat> etats; //on range les etats dans une HashMap (les clés sont des instants)
    public HashSet<Long> prises; //ensemble des temps auxquels l'utilisateur réalise une prise de levodopa

    //constructeur standard : on met tout à 0
    public SuiviConcentration() {
        long time = (System.currentTimeMillis() / this.temps_entre_deux_mesures) * temps_entre_deux_mesures;

        etats = new HashMap<Long, Etat>();
        prises = new HashSet<Long>();

        //des l'initialisation, on remplit la hashmap a capacité maximale
        for (long i = time - (taille_max - 1) * temps_entre_deux_mesures; i <= time; i += temps_entre_deux_mesures) {
            etats.put(i, new Etat());
        }
        efficacite_metabolisation = 1;


    }

    //nombre d'etats successifs nécessaires pour que l'absorption soit réalisée par l'organisme
    public int nb_etapes_absorbtion() {
        return temps_absorption / temps_entre_deux_mesures;
    }

    //méthode visant à réaliser la simulation de l'évolution de la concentration en levodopa jusqu'au temps actuel, étant données les informations données par l'utilisateur jusque là
    public void mise_a_jour() {
        long time = (System.currentTimeMillis() / this.temps_entre_deux_mesures) * temps_entre_deux_mesures;

        long tps_max_avec_mesure_enregistree = Collections.max(etats.keySet()); //dernier temps auquel on a fait une estimation de la concentration
        boolean etat_sommeil = etats.get(tps_max_avec_mesure_enregistree).est_couche; //on évalue si l'utilisateur était couché ou non à ce moment là

        //on calcule tous les etats de proche en proche
        for (long i = tps_max_avec_mesure_enregistree; i < time; i += temps_entre_deux_mesures) {
            Etat current_etat = etats.get(i);

            double concentration = (etat_sommeil) ? current_etat.concentration * Math.exp(-(temps_entre_deux_mesures) / (1000 * demi_vie * 1.3 * efficacite_metabolisation)) + etats.get(i + temps_entre_deux_mesures - temps_absorption).absorbe : current_etat.concentration * Math.exp(-(temps_entre_deux_mesures * efficacite_metabolisation) / (1000 * demi_vie)) + etats.get(i + temps_entre_deux_mesures - temps_absorption).absorbe;

            etats.put(i + temps_entre_deux_mesures, new Etat(concentration, etat_sommeil, 0));
        }

        for (long i = tps_max_avec_mesure_enregistree; i < time; i += temps_entre_deux_mesures) {
            etats.remove(Collections.min(etats.keySet()));
        }
    }

    //méthode appelée lorsque l'utilisateur indique qu'il se lève ou se couche
    public void coucher_ou_lever() {
        long time = (System.currentTimeMillis() / this.temps_entre_deux_mesures) * temps_entre_deux_mesures;

        long tps_max_avec_mesure_enregistree = Collections.max(etats.keySet()); //le temps auquel on a effectué la dernière mesure
        boolean etat_sommeil = etats.get(tps_max_avec_mesure_enregistree).est_couche; //on évalue si l'utilisateur était couché ou non à ce moment là

        this.mise_a_jour(); //on met a jour le suivi jusqu'à l'instant actuel

        etats.get(time).est_couche = (etat_sommeil) ? false : true; //on change la valeur de "est_couche"

    }

    //methode appelee lorsque l'utilisateur indique qu'il prend une certaine quantité de levodopa
    public void prise(double taille_prise) {
        long time = (System.currentTimeMillis() / this.temps_entre_deux_mesures) * temps_entre_deux_mesures;

        this.mise_a_jour(); //on met a jour le suivi jusqu'à l'instant actuel

        double abs = taille_prise / this.nb_etapes_absorbtion();

        //on indique le fait que les prochains etats vont absorber
        for (long i = time; i > time - temps_absorption; i -= temps_entre_deux_mesures) {
            etats.get(i).absorbe += abs;
        }

        prises.add(time);

    }

    //methode appelee pour connaitre la concentration en levodopa dans le corps à l'instant présent
    public double concentration() {
        long timeexact = System.currentTimeMillis();
        long time = (timeexact / this.temps_entre_deux_mesures) * temps_entre_deux_mesures;

        this.mise_a_jour();
        boolean etat_sommeil = etats.get(time).est_couche;

        double concentration = (etat_sommeil) ? etats.get(time).concentration * Math.exp(-(timeexact - time) / (1000 * demi_vie * 1.3 * efficacite_metabolisation)) + etats.get(time + temps_entre_deux_mesures - temps_absorption).absorbe * ((float) (timeexact - time) / temps_entre_deux_mesures) : etats.get(time).concentration * Math.exp(-((timeexact - time) * efficacite_metabolisation) / (1000 * demi_vie)) + etats.get(time + temps_entre_deux_mesures - temps_absorption).absorbe * ((float) (timeexact - time) / temps_entre_deux_mesures);

        return concentration;
    }

    //methode appelee pour connaitre l'état de sommeil (couché ou levé) de l'utilisateur
    //renvoie true si l'utilisateur est couché, false sinon
    public boolean get_etat_sommeil() {
        long time = (System.currentTimeMillis() / this.temps_entre_deux_mesures) * temps_entre_deux_mesures;

        this.mise_a_jour();

        return etats.get(time).est_couche;
    }

    public boolean getCurrentEtat() {
        long tps_max_avec_mesure_enregistree = Collections.max(etats.keySet()); //le temps auquel on a effectué la dernière mesure
        return Objects.requireNonNull(etats.get(tps_max_avec_mesure_enregistree)).est_couche;
    }
}
