#include "TaskSchedulerManager.h"

//Schedule part

TaskSchedulerManagerHelper::Schedule::Schedule() :
  _days(0x7F),
  _months(0xFFF),
  _triggered(false),
  _triggerRightAway(true),
  _hasBeenTriggeredRightAway(false),
  _triggerType(TRIGGER_REPEAT),
  _mode(EVERY),
  _enabled(true),
  _millis(0),
  _everyMillis(1000),
  _dateTime(2000,0,0,0,0,0)
{

}

TaskSchedulerManagerHelper::Schedule::Schedule(const TaskSchedulerManagerHelper::Schedule &Object) : 
  _days(Object._days),
  _months(Object._months),
  _triggered(Object._triggered),
  _triggerRightAway(Object._triggerRightAway),
  _hasBeenTriggeredRightAway(Object._hasBeenTriggeredRightAway),
  _triggerType(Object._triggerType),
  _mode(Object._mode),
  _enabled(Object._enabled),
  _millis(Object._millis),
  _everyMillis(Object._everyMillis),
  _dateTime(Object._dateTime)
{
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManagerHelper::Schedule::scheduleBuilder()
{
  return new TaskSchedulerManagerHelper::Schedule();
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManagerHelper::Schedule::setMillis(uint32_t millis)
{
  _everyMillis = millis;
  return this;
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManagerHelper::Schedule::setSeconds(uint32_t seconds)
{
  if(seconds < 60)
    _dateTime = DateTime(_dateTime.year(), _dateTime.month(), _dateTime.day(), _dateTime.hour(), _dateTime.minute(), seconds);
    
  return TaskSchedulerManagerHelper::Schedule::setMillis(seconds*1000);
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManagerHelper::Schedule::setMinutes(uint32_t minutes)
{
  if(minutes < 60)
    _dateTime = DateTime(_dateTime.year(), _dateTime.month(), _dateTime.day(), _dateTime.hour(), minutes, _dateTime.second());
    
  return TaskSchedulerManagerHelper::Schedule::setSeconds(minutes*60);
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManagerHelper::Schedule::setHours(uint16_t hours)
{
  if(hours < 24)
    _dateTime = DateTime(_dateTime.year(), _dateTime.month(), _dateTime.day(), hours, _dateTime.minute(), _dateTime.second());
    
  return TaskSchedulerManagerHelper::Schedule::setMinutes(hours*60);
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManagerHelper::Schedule::setDateTime(DateTime &dateTime)
{
  _dateTime = dateTime;
  return this;
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManagerHelper::Schedule::setDays(uint8_t days)
{
  _days = days;
  return this;
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManagerHelper::Schedule::setMonths(uint16_t months)
{
  _months = months;
  return this;
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManagerHelper::Schedule::setTriggerRightAway(boolean enabled)
{
  _triggerRightAway = enabled;
  _hasBeenTriggeredRightAway = false;
  return this;
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManagerHelper::Schedule::setTriggerType(TriggerType triggerType)
{
  _triggerType = triggerType;
  return this;
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManagerHelper::Schedule::setMode(Mode mode)
{
  _mode = mode;
  return this;
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManagerHelper::Schedule::setEnabled(boolean enabled)
{
  _enabled = enabled;
  return this;
}

void TaskSchedulerManagerHelper::Schedule::printDebug()
{
  Serial.printf("###Schedule DEBUG###\nMillis -> %u\nTrigger right away -> %u\nTrigger type -> %u\nMode -> %u\nEnabled -> %u\nDays -> %u\nMonth -> >%u\n h : %u, m : %u, s : %u\n######\n",_everyMillis, _triggerRightAway, _triggerType, _mode, _enabled, _days, _months, _dateTime.hour(), _dateTime.minute(), _dateTime.second());
}

// TaskScheduleManager part
TaskSchedulerManager::TaskSchedulerManager() : _nextTaskIndexToBeRun(0), _rtcManager(NULL)
{
  
}

TaskSchedulerManager::TaskSchedulerManager(RtcManager &rtcManager) : _nextTaskIndexToBeRun(0), _rtcManager(&rtcManager)
{
  
}

boolean TaskSchedulerManager::addTask(const char *name, TaskSchedulerManagerHelper::Schedule *schedule, boolean (*taskRoutine)(void*), void *pData)
{
  if(schedule == NULL)return false;

  schedule->_millis = millis();
  boolean result = _taskDataDictio.add(name, new TaskData({*schedule, taskRoutine, pData}));
  delete schedule;schedule = NULL;
  
  return result;
}

boolean TaskSchedulerManager::addTask(uint16_t id, TaskSchedulerManagerHelper::Schedule *schedule, boolean (*taskRoutine)(void*), void *pData)
{
  if(schedule == NULL)return false;

  schedule->_millis = millis();
  boolean result = _taskDataDictio.add(id, new TaskData({*schedule, taskRoutine, pData}));
  delete schedule;schedule = NULL;
  
  return result;
}

boolean TaskSchedulerManager::removeTask(const char *name)
{
  return _taskDataDictio.remove(name);
}

boolean TaskSchedulerManager::removeTask(uint16_t id)
{
  return _taskDataDictio.remove(id);
}

void TaskSchedulerManager::enableTask(const char *name)
{
  getTask(name)->setEnabled(true);
}

void TaskSchedulerManager::enableTask(uint16_t id)
{
  getTask(id)->setEnabled(true);
}

void TaskSchedulerManager::disableTask(const char *name)
{
  getTask(name)->setEnabled(false);
}

void TaskSchedulerManager::disableTask(uint16_t id)
{
  getTask(id)->setEnabled(false);
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManager::getTask(const char *name)
{
  return &(_taskDataDictio(name)->schedule);
}

TaskSchedulerManagerHelper::Schedule *TaskSchedulerManager::getTask(uint16_t id)
{
  char indiceToStr[10];
  sprintf(indiceToStr,"%d", id);
  return &(_taskDataDictio(indiceToStr)->schedule);
}

void TaskSchedulerManager::clearTask()
{
  _taskDataDictio.clear();
}

void TaskSchedulerManager::run()
{
  if(_taskDataDictio.count() == 0)return;
  
  if(_nextTaskIndexToBeRun >= _taskDataDictio.count())_nextTaskIndexToBeRun = 0;
  
  TaskData *p = _taskDataDictio.getAt(_nextTaskIndexToBeRun);
  
  if(p == NULL)//Shouldn't happen
    return;
    
  if(p->schedule._enabled)
  {
    if(p->schedule._mode == TaskSchedulerManagerHelper::Schedule::Mode::EVERY)
    {
        if(p->schedule._triggerRightAway && !p->schedule._hasBeenTriggeredRightAway)//Should it be executed right away ?
        {
          p->schedule._millis = millis();
          p->schedule._hasBeenTriggeredRightAway = true;
          p->schedule._triggered = true;
          (*(p->taskRoutine))(p->pData);
        }
        else if(millis() - p->schedule._millis >= p->schedule._everyMillis) //Or should it be executed
        {
            p->schedule._millis = millis();
            p->schedule._triggered = true;
            (*(p->taskRoutine))(p->pData);
        }
    }
    else //AT mode task
    {
      
    }
  }

  if(p->schedule._triggerType != TaskSchedulerManagerHelper::Schedule::TRIGGER_ONCE_AND_DELETE)_nextTaskIndexToBeRun++;
  else if(p->schedule._triggerType == TaskSchedulerManagerHelper::Schedule::TRIGGER_ONCE_AND_DELETE && !(p->schedule._triggered))_nextTaskIndexToBeRun++;
  else if(p->schedule._triggerType == TaskSchedulerManagerHelper::Schedule::TRIGGER_ONCE_AND_DELETE && p->schedule._triggered) //If the task has to be deleted after it has been executed and if it has, we delete it
  {
    _taskDataDictio.removeAt(_nextTaskIndexToBeRun);
  }
}

unsigned int TaskSchedulerManager::taskCount() const
{
  return _taskDataDictio.count();
}

uint16_t TaskSchedulerManager::findFreeTaskId()
{
  uint16_t idToTry(1);
  while(_taskDataDictio(idToTry) != NULL)
  {
    idToTry++;
  }
  return idToTry;
}
