#include "ordonnanceur.h"
#include "avr/wdt.h"

/***************************************************************************************
//Déclaration d'une liste circulaire des tâches
****************************************************************************************/

struct liste_tache
{
  tache       *_tache;
  liste_tache *suiv;
};
typedef struct liste_tache liste_tache;

liste_tache *tache_courante = NULL;


/***************************************************************************************
//Méthodes liées aux tâches
****************************************************************************************/

tache::tache(fonctionVoid fonction, uint32_t periode_ms)
{
  //Initialisation des variables privées
  this->_fonction = fonction;
  this->_periode = periode_ms;
  this->_millis_prochaine_exec = 0;

  //Ajout de la tâche dans la liste circulaire
  liste_tache *nouvelle_entree;
  nouvelle_entree = new liste_tache;
  nouvelle_entree->_tache = this;
  if (tache_courante == NULL)
  {
    nouvelle_entree->suiv = nouvelle_entree;
  }
  else
  {
    nouvelle_entree->suiv = tache_courante->suiv;
    tache_courante->suiv = nouvelle_entree;
  }
  tache_courante = nouvelle_entree;
}

void tache::changerFonction(fonctionVoid nouvelle_fonction)
{
  this->_fonction = nouvelle_fonction;
}

void tache::changerPeriode(uint32_t nouvelle_periode_ms)
{
  this->_periode = nouvelle_periode_ms;
}

void tache::recaler()
{
  this->_millis_prochaine_exec = millis() + this->_periode;
}

void tache::executer()
{
  //Détection d'exécution anticipée (forcée depuis une autre tâche)
  if (this->_millis_prochaine_exec - millis() < 0x7fffffff)
  {
    this->_millis_prochaine_exec = millis();
  }

  //Exécution
  if (this->_fonction != NULL)
  {
    this->_fonction();
  }

  //Calcul de la prochaine échéance
  this->_millis_prochaine_exec += this->_periode;

  //Sécurité anti saturation
  if (this->_millis_prochaine_exec - millis() > 0x7fffffff)
  {
    this->recaler();
  }
}


/***************************************************************************************
//Méthodes liées à l'ordonnanceur
****************************************************************************************/

ordonnanceur_c::ordonnanceur_c()
{
  this->stopper();
  this->actualiserRamDisponible();
  this->_chargeCPU1s = 0;
  this->_chargeCPU5s = 0;
  this->_tempo1s[0] = 0;
  this->_tempo1s[1] = 0;
  this->_tempo5s[0] = 0;
  this->_tempo5s[1] = 0;
}

void ordonnanceur_c::modifierWatchdog(const uint8_t watchdog)
{
  switch (watchdog)
  {
    case WATCHDOG_INACTIF    : wdt_disable(); break;
    case WATCHDOG_1_SECONDE  : wdt_enable(WDTO_1S); break;
    case WATCHDOG_2_SECONDES : wdt_enable(WDTO_2S); break;
    case WATCHDOG_4_SECONDES : wdt_enable(WDTO_4S); break;
    case WATCHDOG_8_SECONDES : wdt_enable(WDTO_8S); break;
    default : return;
  }
  this->_statut &= 128;
  this->_statut |= watchdog;
}

void ordonnanceur_c::gererWatchdog()
{
  if (this->watchdogActif())
  {
    wdt_reset();
  }
}

void ordonnanceur_c::reboot()
{
  //Activation du watchdog
  this->modifierWatchdog(WATCHDOG_1_SECONDE);

  //Boucle infinie => Reboot au bout d'une seconde
  while (1);
}

void ordonnanceur_c::stopper()
{
  this->desactiverWatchdog();
  this->_statut = 0;
}

void ordonnanceur_c::actualiserRamDisponible()
{
  extern int __heap_start, *__brkval;
  int v;
  this->_ramDisponible = (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

void ordonnanceur_c::actualiserChargeCPU(uint32_t duree_pause)
{
  uint32_t maintenant = millis();
  uint32_t duree;

  this->_tempo1s[1] += duree_pause;
  duree = maintenant - this->_tempo1s[0];
  if (duree >= 1000)
  {
    this->_chargeCPU1s = 100UL - (this->_tempo1s[1] * 100 + (duree >> 1)) / duree;
    this->_tempo1s[0] += 1000;
    this->_tempo1s[1] = 0;
  }

  this->_tempo5s[1] += duree_pause;
  duree = maintenant - this->_tempo5s[0];
  if (duree >= 5000)
  {
    this->_chargeCPU5s = 100UL - (this->_tempo5s[1] * 100 + (duree >> 1)) / duree;
    this->_tempo5s[0] += 5000;
    this->_tempo5s[1] = 0;
  }
}

void ordonnanceur_c::lancer(uint8_t watchdog)
{
  //Il faut avoir défini au moins une tâche
  if (tache_courante == NULL) return;

  //Mise à jour du statut
  this->_statut |= 128;

  //Activation du watchdog
  this->modifierWatchdog(watchdog);

  //Boucle infinie sauf si arrêt demandé par une tâche
  while (this->estActif())
  {
    //Gestion du watchdog
    this->gererWatchdog();

    //Mesure de la ram disponible
    this->actualiserRamDisponible();

    //Sélection de la prochaine tâche à exécuter
    //tache_courante pointe vers la dernière tâche exécutée
    liste_tache *curseur, *selection;
    curseur         = tache_courante->suiv;
    selection       = curseur;
    uint32_t base   = 0x7fffffff - millis(); //on recale le zéro en milieu de plage de valeur
    uint32_t refmin = 0xffffffff;
    bool continuer  = true;
    while (continuer)
    {
      uint32_t ref = base + curseur->_tache->millis_prochaine_exec();
      if (ref < refmin)
      {
        refmin = ref;
        selection = curseur;
      }
      continuer = (curseur != tache_courante);
      curseur = curseur->suiv;
    }

    //Exécuter tout de suite si l'échéance est atteinte
    uint32_t duree_pause;
    if (refmin <= 0x7fffffff)
    {
      duree_pause = 0;
      tache_courante = selection;
      tache_courante->_tache->executer();
    }
    else
    {
      duree_pause = refmin - 0x7fffffff;
      if (duree_pause > 100) duree_pause = 100;
      delay(duree_pause);
    }

    //Evaluation de la charge CPU
    this->actualiserChargeCPU(duree_pause);
  }
}

ordonnanceur_c ordonnanceur;
