/*
 * Stairs
 * Copyright 2017 (c) Mark van Renswoude
 *
 * https://git.x2software.net/pub/Stairs
*/
#include "./time.h"
#include <string.h>
#include <ArduinoJson.h>
#include "../../debug.h"
#include "../../global.h"


timeDayOfWeek_t toTimeDayOfWeek(DayOfWeek day)
{
  switch (day)
  {
    case Monday:    return dowMonday;
    case Tuesday:   return dowTuesday;
    case Wednesday: return dowWednesday;
    case Thursday:  return dowThursday;
    case Friday:    return dowFriday;
    case Saturday:  return dowSaturday;
    case Sunday:    return dowSunday;
  }

  return dowInvalid;
}


DayOfWeek toDayOfWeek(timeDayOfWeek_t timeDay)
{
  switch (timeDay)
  {
    case dowSunday:     return Sunday;
    case dowMonday:     return Monday;
    case dowTuesday:    return Tuesday;
    case dowWednesday:  return Wednesday;
    case dowThursday:   return Thursday;
    case dowFriday:     return Friday;
    case dowSaturday:   return Saturday;
    default:            return Monday;
  }
}




void TimeTriggerSettings::toJson(Print &print)
{
  DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(10) + JSON_OBJECT_SIZE(3) + 10*JSON_OBJECT_SIZE(5));

  JsonObject& root = jsonBuffer.createObject();
  root["enabled"] = enabled();
  root["transitionTime"] = transitionTime();

  JsonArray& jsonTriggers = root.createNestedArray("triggers");

  for (uint8_t i = 0; i < triggerCount(); i++)
  {
    TimeTrigger* triggerItem = trigger(i);

    JsonObject& jsonTrigger = jsonTriggers.createNestedObject();
    jsonTrigger["time"] = triggerItem->time;
    jsonTrigger["daysOfWeek"] = triggerItem->daysOfWeek;
    jsonTrigger["brightness"] = triggerItem->brightness;
    jsonTrigger["triggerType"] = (uint8_t)triggerItem->triggerType;
    jsonTrigger["enabled"] = triggerItem->enabled;
  }

  root.printTo(print);
}


bool TimeTriggerSettings::fromJson(char* data, bool* changed)
{
  if (changed != nullptr)
    *changed = false;

  DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(10) + JSON_OBJECT_SIZE(3) + 10*JSON_OBJECT_SIZE(5) + 270);
  JsonObject& root = jsonBuffer.parseObject(data);

  if (!root.success())
    return false;


  enabled(root["enabled"]);
  transitionTime(root["transitionTime"]);

  if (root.containsKey("triggers"))
  {
    JsonArray& jsonTriggers = root["triggers"];
    if (mTriggers != nullptr)
      delete [] mTriggers;

    mTriggerCount = jsonTriggers.size();
    mTriggers = new TimeTrigger[mTriggerCount];


    for (uint8_t i = 0; i < mTriggerCount; i++)
    {
      JsonObject& jsonTrigger = jsonTriggers[i];
      TimeTrigger* trigger = &mTriggers[i];

      trigger->time = jsonTrigger["time"];
      trigger->daysOfWeek = jsonTrigger["daysOfWeek"];
      trigger->brightness = jsonTrigger["brightness"];
      trigger->triggerType = (TimeTriggerType)(uint8_t)jsonTrigger["triggerType"];
      trigger->enabled = jsonTrigger["enabled"];
    }
  }

  if (changed != nullptr)
    *changed = true;

  return true;
}



TimeTrigger* TimeTriggerSettings::getActiveTrigger(tmElements_t &time)
{
  if (mTriggerCount == 0)
    return nullptr;


  DayOfWeek dayOfWeek = toDayOfWeek((timeDayOfWeek_t)time.Wday);
  DayOfWeek startDayOfWeek = dayOfWeek;
  TimeTrigger* activeTrigger = nullptr;
  int16_t activeTriggerTime = 0;


  Dusk2Dawn location(systemSettings->latitude(), systemSettings->longitude(), timezoneOffset / 3600.0f);

  // Praise the sun \o/
  // DST is always hardcoded as false, since it is already included in timezoneOffset
  int16_t sunriseMinutes = location.sunrise(time.Year, time.Month, time.Day, false);
  int16_t sunsetMinutes = location.sunset(time.Year, time.Month, time.Day, false);

  int16_t dayTime = (time.Hour * 60) + time.Minute;

  _d("TimeTrigger :: sunrise: "); _dln(sunriseMinutes);
  _d("TimeTrigger :: sunset: "); _dln(sunsetMinutes);
  _d("TimeTrigger :: current time: "); _dln(dayTime);

  do
  {
    for (uint16_t i = 0; i < mTriggerCount; i++)
    {
      TimeTrigger* trigger = &mTriggers[i];

      if (trigger->enabled && (trigger->daysOfWeek & dayOfWeek))
      {
        int16_t triggerTime = trigger->time;

        switch (trigger->triggerType)
        {
          case RelativeToSunrise:
            triggerTime += sunriseMinutes;
            break;

          case RelativeToSunset:
            triggerTime += sunsetMinutes;
            break;

          case FixedTime:
            // No changes (but eliminates warning)
            break;
        }

        // Check if the current time is after the time set in the trigger, and
        // if this trigger is later than any previously found trigger, so that
        // we'll always get the most recent match
        if (triggerTime <= dayTime && (activeTrigger == nullptr || triggerTime > activeTriggerTime))
        {
          activeTrigger = trigger;
          activeTriggerTime = triggerTime;
        }
      }
    }

    if (activeTrigger != nullptr)
      return activeTrigger;


    // If there are no active triggers on this day, go back
    // one weekday and try again until we've come around completely
    if (dayOfWeek == Monday)
      dayOfWeek = Sunday;
    else
      dayOfWeek = (DayOfWeek)((uint8_t)dayOfWeek / 2);

    // Set the comparison time to the end of the day, so the last
    // trigger for that day will match
    dayTime = 24 * 60;
  } while (dayOfWeek != startDayOfWeek);

  return nullptr;
}
