/****************************************************************************
 * 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_suntime_p.h"
#include "aqhome-react/suntimes.h"

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



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

#define AQHOMEREACT_UNIT_SUNTIME_OUTSLOT_OUTPUT 0
#define AQHOMEREACT_UNIT_SUNTIME_INSLOT_TIMER   0

GWEN_INHERIT(AQHREACT_UNIT, AQHREACT_UNIT_SUNTIME);


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

static void GWENHYWFAR_CB _freeData(void *bp, void *p);
static int _cbProcess(AQHREACT_UNIT *unit);
static int _checkState(AQHREACT_UNIT *unit);
static int _isInsideSuntime(AQHREACT_UNIT *unit);
static void _updateSuntimes(AQHREACT_UNIT *unit);
static int _hasDateChanged(AQHREACT_UNIT *unit);
static int _gwenTimeToMinutes(const GWEN_TIME *t);
static int _outputResult(AQHREACT_UNIT *unit, int result);
static void _readParams(AQHREACT_UNIT *unit);



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

AQHREACT_UNIT *AqHomeReact_UnitSuntime_new(AQH_OBJECT *aqh)
{
  AQHREACT_UNIT *unit;
  AQHREACT_UNIT_SUNTIME *xunit;
  AQHREACT_PORT *port;
  AQHREACT_PARAM *param;

  unit=AQHREACT_Unit_new(aqh);
  AQHREACT_Unit_SetTypeName(unit, "suntime");
  AQHREACT_Unit_SetDescription(unit, "Make sunset and sunrise times available");
  AQHREACT_Unit_SetGpInt(unit, -1);

  GWEN_NEW_OBJECT(AQHREACT_UNIT_SUNTIME, xunit);
  GWEN_INHERIT_SETDATA(AQHREACT_UNIT, AQHREACT_UNIT_SUNTIME, unit, xunit, _freeData);

  AQHREACT_Unit_SetProcessFn(unit, _cbProcess);

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

  port=AQHREACT_Port_new();
  AQHREACT_Port_SetName(port, "timer");
  AQHREACT_Port_SetIdForUnit(port, AQHOMEREACT_UNIT_SUNTIME_INSLOT_TIMER);
  AQHREACT_Port_SetDataType(port, AQHREACT_DATAOBJECTTYPE_DOUBLE);
  AQHREACT_Unit_AddInputPort(unit, port);

  param=AQHREACT_Param_new();
  AQHREACT_Param_SetName(param, AQHOMEREACT_UNIT_SUNTIME_PARAM_LAT);
  AQHREACT_Param_SetDataType(param, AQHREACT_DATAOBJECTTYPE_DOUBLE);
  AQHREACT_Unit_AddParam(unit, param);

  param=AQHREACT_Param_new();
  AQHREACT_Param_SetName(param, AQHOMEREACT_UNIT_SUNTIME_PARAM_LONG);
  AQHREACT_Param_SetDataType(param, AQHREACT_DATAOBJECTTYPE_DOUBLE);
  AQHREACT_Unit_AddParam(unit, param);

  param=AQHREACT_Param_new();
  AQHREACT_Param_SetName(param, AQHOMEREACT_UNIT_SUNTIME_PARAM_OFFS_RISE);
  AQHREACT_Param_SetDataType(param, AQHREACT_DATAOBJECTTYPE_DOUBLE);
  AQHREACT_Unit_AddParam(unit, param);

  param=AQHREACT_Param_new();
  AQHREACT_Param_SetName(param, AQHOMEREACT_UNIT_SUNTIME_PARAM_OFFS_SET);
  AQHREACT_Param_SetDataType(param, AQHREACT_DATAOBJECTTYPE_DOUBLE);
  AQHREACT_Unit_AddParam(unit, param);

  return unit;

}



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

  xunit=(AQHREACT_UNIT_SUNTIME*) p;

  GWEN_Date_free(xunit->date);
  GWEN_FREE_OBJECT(xunit);
}



int _cbProcess(AQHREACT_UNIT *unit)
{
  AQHREACT_UNIT_SUNTIME *xunit;

  xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_SUNTIME, unit);
  if (xunit) {
    if (unit /* && AQHREACT_Unit_InputHasChanged(unit)*/) {
      int rv;

      if (AQHREACT_Unit_GetGpInt(unit)==-1)
        _readParams(unit);

      rv=_checkState(unit);
      AQHREACT_Unit_ClearChangeFlagsInUnitAndInputPorts(unit);
      return rv;
    }
    return 0;
  }

  return 0;
}



int _checkState(AQHREACT_UNIT *unit)
{
  AQHREACT_UNIT_SUNTIME *xunit;

  xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_SUNTIME, unit);
  if (xunit) {
    time_t now;

    now=time(NULL);
    if (60.0<difftime(now, xunit->lastCheckTime)) {
      if (_hasDateChanged(unit))
	_updateSuntimes(unit);
      xunit->lastCheckTime=now;
      return _outputResult(unit, _isInsideSuntime(unit));
    }
  }
  return 0;
}



int _isInsideSuntime(AQHREACT_UNIT *unit)
{
  AQHREACT_UNIT_SUNTIME *xunit;

  xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_SUNTIME, unit);
  if (xunit) {
    GWEN_TIME *now;
    int startTimeInMinutes;
    int endTimeInMinutes;
    int nowInMinutes;
    int result;

    now=GWEN_CurrentTime();
    nowInMinutes=_gwenTimeToMinutes(now);
    GWEN_Time_free(now);

    startTimeInMinutes=xunit->sunRiseTimeInMinutes+xunit->offsetMinsForSunrise;
    endTimeInMinutes=xunit->sunSetTimeInMinutes+xunit->offsetMinsForSunset;

    result=(nowInMinutes>=startTimeInMinutes && nowInMinutes<=endTimeInMinutes)?1:0;
    DBG_INFO(NULL, "Is inside suntime: %d", result);
    return result;
  }
  return 0;
}



void _updateSuntimes(AQHREACT_UNIT *unit)
{
  AQHREACT_UNIT_SUNTIME *xunit;

  xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_SUNTIME, unit);
  if (xunit) {
    GWEN_TIME *t;

    GWEN_Date_free(xunit->date);
    xunit->date=GWEN_Date_CurrentDate();

    t=AQHomeReact_GetSunriseTimeForDateAndLoc(xunit->date, xunit->latitude, xunit->longitude);
    xunit->sunRiseTimeInMinutes=_gwenTimeToMinutes(t);
    DBG_INFO(NULL, "%s: Sunrise today at %02d:%02d UTC",
              GWEN_Date_GetString(xunit->date), xunit->sunRiseTimeInMinutes/60, xunit->sunRiseTimeInMinutes%60);
    GWEN_Time_free(t);

    t=AQHomeReact_GetSunsetTimeForDateAndLoc(xunit->date, xunit->latitude, xunit->longitude);
    xunit->sunSetTimeInMinutes=_gwenTimeToMinutes(t);
    DBG_INFO(NULL, "%s: Sunset today at %02d:%02d UTC",
              GWEN_Date_GetString(xunit->date), xunit->sunSetTimeInMinutes/60, xunit->sunSetTimeInMinutes%60);
    GWEN_Time_free(t);

  }
}



int _gwenTimeToMinutes(const GWEN_TIME *t)
{
  if (t) {
    int hours;
    int mins;
    int secs;

    GWEN_Time_GetBrokenDownUtcTime(t, &hours, &mins, &secs);
    return (hours*60)+mins;
  }
  return 0;
}


int _hasDateChanged(AQHREACT_UNIT *unit)
{
  AQHREACT_UNIT_SUNTIME *xunit;

  xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_SUNTIME, unit);
  if (xunit) {
    if (xunit->date==NULL)
      return 1;
    else {
      GWEN_DATE *today;

      today=GWEN_Date_CurrentDate();

      if (GWEN_Date_Compare(today, xunit->date)!=0) {
        GWEN_Date_free(today);
        return 1;
      }
      GWEN_Date_free(today);
    }
  }

  return 0;
}


void _readParams(AQHREACT_UNIT *unit)
{
  AQHREACT_UNIT_SUNTIME *xunit;

  xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_SUNTIME, unit);
  if (xunit) {
    DBG_INFO(NULL, "Reading parameters");
    /* default to "Neues Rathaus" Celle ;-) */
    xunit->latitude=AQHREACT_Unit_GetParamValueDouble(unit, AQHOMEREACT_UNIT_SUNTIME_PARAM_LAT, 52.619425);
    xunit->longitude=AQHREACT_Unit_GetParamValueDouble(unit, AQHOMEREACT_UNIT_SUNTIME_PARAM_LONG, 10.087891);
    xunit->offsetMinsForSunrise=AQHREACT_Unit_GetParamValueDouble(unit, AQHOMEREACT_UNIT_SUNTIME_PARAM_OFFS_RISE, 0.0);
    xunit->offsetMinsForSunset=AQHREACT_Unit_GetParamValueDouble(unit, AQHOMEREACT_UNIT_SUNTIME_PARAM_OFFS_SET, 0.0);
  }
}



int _outputResult(AQHREACT_UNIT *unit, int result)
{
  AQHREACT_PORT *port;

  port=AQHREACT_Unit_GetOutputPortByIdForUnit(unit, AQHOMEREACT_UNIT_SUNTIME_OUTSLOT_OUTPUT);
  if (port) {
    int currentState;

    currentState=AQHREACT_Unit_GetGpInt(unit);
    if (AQHREACT_Unit_GetFlags(unit) & AQHREACT_UNIT_FLAGS_INVERT)
      result^=1;
    if (currentState!=result) {
      DBG_INFO(NULL, "%s: Changing result to %d", AQHREACT_Unit_GetId(unit), result);
      AQHREACT_Unit_OutputDoubleData(unit, port, result?1.0:0.0);
      AQHREACT_Unit_SetGpInt(unit, result);
      return 1; /* we changed something */
    }
  }
  return 0; /* nothing changed */
}












