#ifndef TASKSCHEDULERMANAGER_H
#define TASKSCHEDULERMANAGER_H
#include <Arduino.h>
#include "RtcManager.h"
#include "Dictionary.h"

//Forward declaration
class TaskSchedulerManager;

namespace TaskSchedulerManagerHelper
{
  class Schedule
  {
    friend class ::TaskSchedulerManager;
    
    public:
      enum DaysOfWeek {MONDAY = 1, TUESDAY = 2, WEDNESDAY = 4, THURSDAY = 8, FRIDAY = 16, SATURDAY = 32, SUNDAY = 64, EVERY_DAY = 127};
      enum MonthsOfYear {JANUARY = 1, FEBRUARY = 2, MARCH = 4, APRIL = 8, MAY = 16, JUNE = 32, JULY = 64, AUGUST = 128, SEPTEMBER = 256, OCTOBER = 512, NOVEMBER = 1024, DECEMBER = 2048, EVERY_MONTH = 4095};
      enum TriggerType {TRIGGER_REPEAT/*Default*/, TRIGGER_ONCE, TRIGGER_ONCE_AND_DELETE};
      enum Mode {EVERY, AT}; //Indicates if it is every 10:30:00 or if it is AT 10:30:00
      
      ~Schedule()
      {
        delete _dateTime;
      }
      
      static Schedule *scheduleBuilder();
      Schedule *setMillis(uint32_t millis);
      Schedule *setSeconds(uint32_t seconds);
      Schedule *setDateTime(DateTime &dateTime);
      Schedule *setDays(uint8_t days);
      Schedule *setMonths(uint16_t months);
      Schedule *setTriggerRightAway(boolean enabled);
      Schedule *setTriggerType(TriggerType triggerType);
      Schedule *setMode(Mode mode);
      Schedule *setEnabled(boolean enabled);
    protected:
      Schedule();//By default, the schedule is the following : every second, every day, every month, trigger is repeated...
      Schedule(const Schedule &Object);
    private:
      uint8_t _days;
      uint16_t _months;
      boolean _triggered; //To know internally if it has been triggered
      boolean _triggerRightAway; //Should it be triggered the first time ? only for the "every" mode
      boolean _hasBeenTriggeredRightAway;
      TriggerType _triggerType;
      Mode _mode;
      boolean _enabled;
      uint32_t _millis; //Used to internally keep track of the time elapsed for this task
      uint32_t _everyMillis; //We can use millis or DateTime, but not both e.g every 00h 10m 00s 000ms
      DateTime *_dateTime;
  };
}

class TaskSchedulerManager
{
  friend class SAB;
  public:
    ~TaskSchedulerManager()
    {
      _taskDataDictio.dispose();
    }
    boolean addTask(const char *name, TaskSchedulerManagerHelper::Schedule *schedule, boolean (*taskRoutine)(void*), void *pData = NULL);
    boolean addTask(uint16_t id, TaskSchedulerManagerHelper::Schedule *schedule, boolean (*taskRoutine)(void*), void *pData = NULL);
    boolean removeTask(const char *name);
    boolean removeTask(uint16_t id);
    boolean enableTask(const char *name);
    boolean enableTask(uint16_t id);
    boolean disableTask(const char *name);
    boolean disableTask(uint16_t id);
    boolean clearTask();
    void runTaskScheduler();
    const unsigned int taskCount();
    uint16_t findFreeTaskId();
  protected:
    TaskSchedulerManager();
    TaskSchedulerManager(RtcManager &rtcManager);
  private:
    struct TaskData
    {
      TaskSchedulerManagerHelper::Schedule schedule;
      boolean (*taskRoutine)(void*);
      void *pData;
    };

    uint16_t _nextTaskIndexToBeRun;
    RtcManager *_rtcManager;
    Dictionary<TaskData> _taskDataDictio;
};

#endif //TASKSCHEDULERMANAGER_H
