/****************************************************************************
 * This file is part of the project AqHome.
 * AqHome (c) by 2024 Martin Preuss, all rights reserved.
 *
 * The license for this file can be found in the file COPYING which you
 * should have received along with this file.
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "./u_timeprogram_p.h"

#include "aqhome-react/types/prgrule.h"

#include <gwenhywfar/debug.h>
#include <gwenhywfar/xml.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/gwendate.h>
#include <gwenhywfar/gwentime.h>



/* ------------------------------------------------------------------------------------------------
 * defines
 * ------------------------------------------------------------------------------------------------
 */

#define AQHOMEREACT_UNIT_TIMEPROGRAM_OUTSLOT_OUTPUT 0

#define AQHOMEREACT_UNIT_TIMEPROGRAM_CREATEFORNEXT_MINS 60        /* 1h */
#define AQHOMEREACT_UNIT_TIMEPROGRAM_CREATEEVERY_SECS (15*60)   /* 15m */

#define AQHOMEREACT_UNIT_TIMEPROGRAM_CHECKINTERVAL_SEC 60         /* 1m */

GWEN_INHERIT(AQHREACT_UNIT, AQHREACT_UNIT_TIMEPROGRAM);



/* ------------------------------------------------------------------------------------------------
 * forward declarations
 * ------------------------------------------------------------------------------------------------
 */

static MODULE_TIMER_ACTION *ModuleTimerAction_new(int64_t triggerTime, double value);
static void ModuleTimerAction_free(MODULE_TIMER_ACTION *act);

static void GWENHYWFAR_CB _freeData(void *bp, void *p);
static int _cbProcess(AQHREACT_UNIT *unit);
static int _readRules(AQHREACT_UNIT *unit);

static int _checkAndAddActions(AQHREACT_UNIT *unit, AQHREACT_UNIT_TIMEPROGRAM *xunit);
static int _addActionsForNextMinutes(AQHREACT_UNIT_TIMEPROGRAM *xunit, int minutes,
                                     const AQHREACT_PRGRULE_LIST *ruleList, MODULE_TIMER_ACTION_LIST *actionList);
static int _handlePendingActions(AQHREACT_UNIT *unit, AQHREACT_UNIT_TIMEPROGRAM *xunit);



/* ------------------------------------------------------------------------------------------------
 * implementations
 * ------------------------------------------------------------------------------------------------
 */

GWEN_LIST_FUNCTIONS(MODULE_TIMER_ACTION, ModuleTimerAction);


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * ModuleTimerAction
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */

MODULE_TIMER_ACTION *ModuleTimerAction_new(int64_t triggerTime, double value)
{
  MODULE_TIMER_ACTION *act;

  GWEN_NEW_OBJECT(MODULE_TIMER_ACTION, act);
  GWEN_LIST_INIT(MODULE_TIMER_ACTION, act);

  act->triggerTime=triggerTime;
  act->value=value;

  return act;
}



void ModuleTimerAction_free(MODULE_TIMER_ACTION *act)
{
  if (act) {
    GWEN_LIST_FINI(MODULE_TIMER_ACTION, act);
    GWEN_FREE_OBJECT(act);
  }
}



/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * AqHomeReact_UnitTimeProgram
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */

AQHREACT_UNIT *AqHomeReact_UnitTimeProgram_new(AQH_OBJECT *aqh)
{
  AQHREACT_UNIT *unit;
  AQHREACT_UNIT_TIMEPROGRAM *xunit;
  AQHREACT_PORT *port;
  AQHREACT_PARAM *param;

  unit=AQHREACT_Unit_new(aqh);
  AQHREACT_Unit_SetTypeName(unit, "timeraction");
  AQHREACT_Unit_SetDescription(unit, "Programmable timer");
  GWEN_NEW_OBJECT(AQHREACT_UNIT_TIMEPROGRAM, xunit);
  GWEN_INHERIT_SETDATA(AQHREACT_UNIT, AQHREACT_UNIT_TIMEPROGRAM, unit, xunit, _freeData);

  xunit->actionList=ModuleTimerAction_List_new();
  xunit->lastActionHour=-1;
  AQHREACT_Unit_SetProcessFn(unit, _cbProcess);

  port=AQHREACT_Port_new();
  AQHREACT_Port_SetName(port, "output");
  AQHREACT_Port_SetIdForUnit(port, AQHOMEREACT_UNIT_TIMEPROGRAM_OUTSLOT_OUTPUT);
  AQHREACT_Port_SetDataType(port, AQHREACT_DATAOBJECTTYPE_DOUBLE);
  AQHREACT_Unit_AddOutputPort(unit, port);

  param=AQHREACT_Param_new();
  AQHREACT_Param_SetName(param, AQHOMEREACT_UNIT_TIMEPROGRAM_PARAM_RULES);
  AQHREACT_Param_SetDataType(param, AQHREACT_DATAOBJECTTYPE_STRING);
  AQHREACT_Unit_AddParam(unit, param);

  xunit->nextAddTime=GWEN_Timestamp_NowInLocalTime();

  return unit;
}



void GWENHYWFAR_CB _freeData(GWEN_UNUSED void *bp, void *p)
{
  AQHREACT_UNIT_TIMEPROGRAM *xunit;

  xunit=(AQHREACT_UNIT_TIMEPROGRAM*) p;

  ModuleTimerAction_List_free(xunit->actionList);
  AQHREACT_PrgRule_List_free(xunit->ruleList);
  GWEN_Timestamp_free(xunit->nextAddTime);
  GWEN_FREE_OBJECT(xunit);
}



int _cbProcess(AQHREACT_UNIT *unit)
{
  int didSomething=0;

  if (unit) {
    AQHREACT_UNIT_TIMEPROGRAM *xunit;

    xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_TIMEPROGRAM, unit);
    if (xunit) {
      int rv;

      if (xunit->ruleList==NULL) {
        DBG_INFO(NULL, "Reading rules");
        rv=_readRules(unit);
        if (rv<0) {
          DBG_INFO(NULL, "here (%d)", rv);
          return rv;
        }
        didSomething=1;
      }

      rv=_checkAndAddActions(unit, xunit);
      if (rv<0) {
        DBG_INFO(NULL, "here (%d)", rv);
        return rv;
      }
      if (rv>0)
        didSomething=1;
    }
  }
  return didSomething?1:0;
}



int _checkAndAddActions(AQHREACT_UNIT *unit, AQHREACT_UNIT_TIMEPROGRAM *xunit)
{
  int didSomething=0;

  xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_TIMEPROGRAM, unit);
  if (xunit) {
    time_t now;
    int rv;

    now=time(NULL);
    if ((now-xunit->lastCreateTime)>AQHOMEREACT_UNIT_TIMEPROGRAM_CREATEEVERY_SECS) {
      rv=_addActionsForNextMinutes(xunit,
                                   AQHOMEREACT_UNIT_TIMEPROGRAM_CREATEFORNEXT_MINS,
                                   xunit->ruleList,
                                   xunit->actionList);
      if (rv<0) {
        DBG_INFO(NULL, "here (%d)", rv);
        return rv;
      }
      if (rv>0)
        didSomething=1;
      xunit->lastCreateTime=now;
    }

    if ((now-xunit->lastCheckTime)>AQHOMEREACT_UNIT_TIMEPROGRAM_CHECKINTERVAL_SEC) {
      rv=_handlePendingActions(unit, xunit);
      if (rv<0) {
        DBG_INFO(NULL, "here (%d)", rv);
        return rv;
      }
      if (rv>0)
        didSomething=1;
      xunit->lastCheckTime=now;
    }
  }

  return didSomething?1:0;
}



int _handlePendingActions(AQHREACT_UNIT *unit, AQHREACT_UNIT_TIMEPROGRAM *xunit)
{
  int valueSent=0;

  if (unit && xunit) {
    AQHREACT_PORT *outputPort;

    outputPort=AQHREACT_Unit_GetOutputPortByIdForUnit(unit, AQHOMEREACT_UNIT_TIMEPROGRAM_OUTSLOT_OUTPUT);
    if (outputPort) {
      GWEN_TIMESTAMP *ts;
      int64_t nowInt64;
      MODULE_TIMER_ACTION *action;

      ts=GWEN_Timestamp_NowInLocalTime();
      nowInt64=ts?GWEN_Timestamp_toInt64(ts):0;
      GWEN_Timestamp_free(ts);

      xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_TIMEPROGRAM, unit);
      action=ModuleTimerAction_List_First(xunit->actionList);
      while(action) {
        MODULE_TIMER_ACTION *nextAction;

        nextAction=ModuleTimerAction_List_Next(action);
        if (action->triggerTime<nowInt64) {
          ModuleTimerAction_List_Del(action);
          DBG_ERROR(NULL, "Sending output value %.2f", action->value);
          AQHREACT_Unit_OutputDoubleData(unit, outputPort, action->value);
          ModuleTimerAction_free(action);
          valueSent=1;
        }

        action=nextAction;
      }
    }
  }
  return valueSent?1:0;
}



int _addActionsForNextMinutes(AQHREACT_UNIT_TIMEPROGRAM *xunit,
                              int minutes,
                              const AQHREACT_PRGRULE_LIST *ruleList,
                              MODULE_TIMER_ACTION_LIST *actionList)
{
  int actionsAdded=0;
  int i;
  GWEN_TIMESTAMP *tsWork;

  tsWork=GWEN_Timestamp_NowInLocalTime();
  for (i=0; i<minutes; i++) {
    if (GWEN_Timestamp_Compare(tsWork, xunit->nextAddTime)==1) {
      int64_t triggerTime;
      const AQHREACT_PRGRULE *rule;

      triggerTime=GWEN_Timestamp_toInt64(tsWork);

      rule=AQHREACT_PrgRule_List_First(ruleList);
      while(rule) {
	if (AQHREACT_PrgRule_Matches(rule,
				     GWEN_Timestamp_GetMinute(tsWork),
				     GWEN_Timestamp_GetHour(tsWork),
				     GWEN_Timestamp_GetDay(tsWork),
				     GWEN_Timestamp_GetMonth(tsWork),
				     GWEN_Timestamp_GetWeekDay(tsWork))>0) {
	  MODULE_TIMER_ACTION *act;

	  DBG_ERROR(NULL, "- rule matches for %s, creating action", GWEN_Timestamp_GetString(tsWork));
	  act=ModuleTimerAction_new(triggerTime, AQHREACT_PrgRule_GetValue(rule));
	  ModuleTimerAction_List_Add(act, actionList);
	  actionsAdded=1;
	}
	rule=AQHREACT_PrgRule_List_Next(rule);
      } /* while */
    }
    GWEN_Timestamp_AddSeconds(tsWork, 60); /* next min */
  } /* for */
  GWEN_Timestamp_free(xunit->nextAddTime);
  xunit->nextAddTime=tsWork;

  return actionsAdded?1:0;
}



int _readRules(AQHREACT_UNIT *unit)
{
  AQHREACT_UNIT_TIMEPROGRAM *xunit;
  const char *s;

  xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_TIMEPROGRAM, unit);
  AQHREACT_PrgRule_List_free(xunit->ruleList);
  xunit->ruleList=NULL;

  s=AQHREACT_Unit_GetParamValueString(unit, AQHOMEREACT_UNIT_TIMEPROGRAM_PARAM_RULES, NULL);
  if (s) {
    xunit->ruleList=AQHREACT_PrgRule_ReadRules(s);
    if (xunit->ruleList==NULL) {
      DBG_INFO(NULL, "Error reading rules from [%s]", s);
      return GWEN_ERROR_BAD_DATA;
    }
    return 0;
  }
  else {
    DBG_INFO(NULL, "No rules");
    return GWEN_ERROR_NO_DATA;
  }
}





