/****************************************************************************
 * This file is part of the project AqHome.
 * AqHome (c) by 2025 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 "./mdevices.h"

#include "aqhome-cgi/modules/devices/mdevices_index.h"
#include "aqhome-cgi/modules/devices/mdevices_valuestable.h"
#include "aqhome-cgi/modules/devices/mdevices_valuesgraph.h"
#include "aqhome-cgi/modules/devices/mdevices_value.h"
#include "aqhome-cgi/modules/devices/mdevices_setdata.h"
#include "aqhome-cgi/modules/devices/mdevices_vgraph.h"
#include "aqhome-cgi/modules/devices/mdevices_device.h"
#include "aqhome-cgi/modules/devices/mdevices_setdevice.h"
#include "aqhome-cgi/modules/devices/mdevices_page.h"
#include "aqhome-cgi/modules/mdataclient.h"

#include <aqcgi/service/module.h>

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



/* ------------------------------------------------------------------------------------------------
 * defs and enums
 * ------------------------------------------------------------------------------------------------
 */

#define GBAS GWEN_Buffer_AppendString
#define GBAA GWEN_Buffer_AppendArgs

#define P_DEVICEREAD AQH_MODDEVICES_PERMS_DEVICEREAD
#define P_DEVICEWRITE AQH_MODDEVICES_PERMS_DEVICEWRITE
#define P_VALUEREAD  AQH_MODDEVICES_PERMS_VALUEREAD
#define P_VALUEWRITE AQH_MODDEVICES_PERMS_VALUEWRITE



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

static AQCGI_MODULE *_loadSubModule(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sModuleName);
static int _handleRequest(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sLastPathElem);

static void _handleRqIndexGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqValuesTableGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqValuesGraphGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqValueGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqSetDataPost(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqGraphGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqDeviceGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqDevicePost(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqPageGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqPagePost(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf);
static void _handleRqPageGraphGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf);

static AQDG_GRAPH_DATAPAIR_LIST *_createDataPairListFromDataPoints(const uint64_t *dataPoints, uint64_t numValues);

static void _addValueActionToForm(const AQH_VALUE *value, GWEN_BUFFER *dbuf);
static void _addLastValueToForm(AQH_DATACLIENT *dc, const AQH_VALUE *value, GWEN_BUFFER *dbuf);



/* ------------------------------------------------------------------------------------------------
 * vars
 * ------------------------------------------------------------------------------------------------
 */

static AQH_MODSERVICE_HANDLER_ENTRY _requestTable[]={
  {"index.html",   AQCGI_REQUEST_METHOD_GET,   P_DEVICEREAD,                _handleRqIndexGet},
  {"device.html",  AQCGI_REQUEST_METHOD_GET,   P_DEVICEREAD | P_VALUEREAD,  _handleRqDeviceGet},
  {"device.html",  AQCGI_REQUEST_METHOD_POST,  P_DEVICEWRITE,               _handleRqDevicePost},
  {"vtable.html",  AQCGI_REQUEST_METHOD_GET,   P_DEVICEREAD | P_VALUEREAD,  _handleRqValuesTableGet},
  {"vgraph.html",  AQCGI_REQUEST_METHOD_GET,   P_DEVICEREAD | P_VALUEREAD,  _handleRqValuesGraphGet},
  {"value.html",   AQCGI_REQUEST_METHOD_GET,   P_DEVICEREAD | P_VALUEREAD,  _handleRqValueGet},
  {"setdata.html", AQCGI_REQUEST_METHOD_POST,  P_VALUEWRITE,                _handleRqSetDataPost},
  {"graph.html",   AQCGI_REQUEST_METHOD_GET,   P_DEVICEREAD | P_VALUEREAD,  _handleRqGraphGet},
  {"page.html",    AQCGI_REQUEST_METHOD_GET,   P_DEVICEREAD | P_VALUEREAD,  _handleRqPageGet},
  {"page.html",    AQCGI_REQUEST_METHOD_POST,  P_VALUEWRITE,                _handleRqPagePost},
  {"pgraph.html",  AQCGI_REQUEST_METHOD_GET,   P_DEVICEREAD | P_VALUEREAD,  _handleRqPageGraphGet},
  {NULL, 0, 0, NULL}
};



/* ------------------------------------------------------------------------------------------------
 * code
 * ------------------------------------------------------------------------------------------------
 */

void AQH_ModDevices_Extend(AQCGI_MODULE *m, AQCGI_SERVICE *sv, const char *baseFolder)
{
  AQH_ModService_Extend(m, sv, baseFolder);
  AQH_ModService_SetHandleRequestFn(m, _handleRequest);
  AQH_ModService_SetLoadSubModuleFn(m, _loadSubModule);
}



AQCGI_MODULE *_loadSubModule(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sModuleName)
{
  return NULL;
}



int _handleRequest(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sLastPathElem)
{
  AQH_ModService_HandleRequestWithTable(m, rq, session, sLastPathElem, _requestTable);
  return AQCGI_SendResponse(rq);
}



void _handleRqIndexGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
  AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunIndex, dbuf);
}



void _handleRqValuesTableGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
  AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunValuesAsTable, dbuf);
}



void _handleRqValuesGraphGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
  AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunValuesAsGraph, dbuf);
}



void _handleRqValueGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
  AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunValue, dbuf);
}



void _handleRqSetDataPost(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
  AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunSetData, dbuf);
}



void _handleRqGraphGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
  AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunGraphValue, dbuf);
}



void _handleRqDeviceGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
  AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunDevice, dbuf);
}



void _handleRqDevicePost(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
  AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunSetDevice, dbuf);
}



void _handleRqPageGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
  AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPageGet, dbuf);
}



void _handleRqPagePost(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
  AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPagePost, dbuf);
}



void _handleRqPageGraphGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
  AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPageGraph, dbuf);
}









uint32_t AQH_ModDevices_ColorFromHexString(const char *s)
{
  uint32_t colorIn=0;

  while(*s && *s<33)
    s++;
  if (*s=='#')
    s++;
  while(*s) {
    uint c;

    c=(*s)-'0';
    if (c>9)
      c-=7;
    colorIn<<=4;
    colorIn|=c & 0xf;
    s++;
  }

  /* hex 00RRGGBB -> GGRRWWBB  */
  //return _htmlColorToValueRGBW(colorIn);
  return colorIn;
}



uint32_t AQH_ModDevices_HtmlColorToValueRGBW(uint32_t colorIn)
{
  uint32_t colorOut;
  /* hex 00RRGGBB -> GGRRWWBB  */
  /*   RGBW             GGRRWWBB                       GGRRWWBB                 GGRRWWBB */
  /*   html             00RRGGBB                       00RRGGBB                 00RRGGBB*/
  colorOut=(colorIn & 0x00ff0000) | ((colorIn<<16) & 0xff000000) | (colorIn & 0x000000ff);
  return colorOut;
}



uint32_t AQH_ModDevices_RgbwToHtmlColor(uint32_t colorIn)
{
  uint32_t colorOut;
  /*   RGBW             GGRRWWBB                        GGRRWWBB                      GGRRWWBB */
  /*   hex              00RRGGBB                        00RRGGBB                      00RRGGBB*/
  colorOut=(colorIn & 0x00ff0000) | ( (colorIn>>16) & 0x0000ff00) | (colorIn & 0x000000ff);
  return colorOut;
}



uint32_t AQH_ModDevices_RgbwFromComponents(int r, int g, int b, int w)
{
  uint32_t colorOut;

  colorOut=((r & 0xff)<<16) | ((g & 0xff)<<24) | (b & 0xff) | ((w & 0xff)<<8);
  return colorOut;
}



int AQH_ModDevices_RgbwGetR(uint32_t color)
{                /* GGRRWWBB */
  return (color & 0x00ff0000)>>16;
}



int AQH_ModDevices_RgbwGetG(uint32_t color)
{                /* GGRRWWBB */
  return (color & 0xff000000)>>24;
}



int AQH_ModDevices_RgbwGetB(uint32_t color)
{                /* GGRRWWBB */
  return (color & 0x000000ff);
}



int AQH_ModDevices_RgbwGetW(uint32_t color)
{                /* GGRRWWBB */
  return (color & 0x0000ff00)>>8;
}



void _addValueActionToForm(const AQH_VALUE *value, GWEN_BUFFER *dbuf)
{
  const char *sValueName;

  sValueName=AQH_Value_GetName(value);
  switch(AQH_Value_GetModality(value)) {
  case AQH_ValueModality_RGBW:
    GBAA(dbuf, "<input type=\"color\" name=\"%s\" value=\"refresh\"/>", sValueName);
    break;
  case AQH_ValueModality_OnOff:
    GBAA(dbuf,
         "<select name=\"%s\">"
         "<option value=\"unchanged\">unchanged</option>"
         "<option value=\"off\">off</option>"
         "<option value=\"on\">on</option>"
         "</select>",
         sValueName);
    break;
  case AQH_ValueModality_OnOffAuto:
    GBAA(dbuf,
         "<select name=\"%s\">"
         "<option value=\"unchanged\">unchanged</option>"
         "<option value=\"off\">off</option>"
         "<option value=\"on\">on</option>"
         "<option value=\"auto\">auto</option>"
         "</select>",
         sValueName);
    break;
  default:
    break;
  }
}



void _addLastValueToForm(AQH_DATACLIENT *dc, const AQH_VALUE *value, GWEN_BUFFER *dbuf)
{
  const char *sValueSystemName;
  const char *sValueName;
  uint64_t dataPoints[2];
  uint64_t recvdNum;
//  uint64_t timestamp;
  union {double f; uint64_t i;} u;
  int intVal;

  sValueSystemName=AQH_Value_GetNameForSystem(value);
  sValueName=AQH_Value_GetName(value);
  recvdNum=AQH_DataClient_GetLastData(dc, sValueSystemName, &dataPoints[0], 1);
  if (recvdNum>0) {
//    timestamp=dataPoints[0];
    u.i=dataPoints[1];
    intVal=(int) u.f;
  }
  else {
    u.i=0;
    intVal=-1;
  }

  if (AQH_Value_GetValueType(value)==AQH_ValueType_Actor) {
    DBG_ERROR(NULL, "Adding actor");
    switch(AQH_Value_GetModality(value)) {
    case AQH_ValueModality_RGBW:
      DBG_ERROR(NULL, "Color: %.f RGBW=%08x HTML=%08x, RGBW2=%08x",
                u.f,
                (uint32_t) (u.f),
                AQH_ModDevices_RgbwToHtmlColor(u.f),
                AQH_ModDevices_HtmlColorToValueRGBW(AQH_ModDevices_RgbwToHtmlColor(u.f)));
#if 1
      GBAA(dbuf, "<input type=\"text\" name=\"%s\" value=\"#%08x\"/>", sValueName, (uint32_t) (u.f));
#else
      GBAA(dbuf, "<input type=\"color\" name=\"%s\" value=\"#%08x\"/>#%08x (#%08x)",
           sValueName,
           _rgbwToHtmlColor((unsigned int) (u.f)),
           _rgbwToHtmlColor((unsigned int) (u.f)),
           (uint32_t) (u.f));
#endif
      break;
    case AQH_ValueModality_OnOff:
      GBAA(dbuf, "<select name=\"%s\">" "<option value=\"unchanged\" >unchanged</option>", sValueName);
      GBAA(dbuf, "<option value=\"off\" %s>off</option>", (intVal==0)?"selected":"");
      GBAA(dbuf, "<option value=\"on\" %s>on</option>", (intVal==1)?"selected":"");
      GBAS(dbuf, "</select>");
      break;
    case AQH_ValueModality_OnOffAuto:
      GBAA(dbuf, "<select name=\"%s\">" "<option value=\"unchanged\" >unchanged</option>", sValueName);
      GBAA(dbuf, "<option value=\"off\" %s>off</option>", (intVal==0)?"selected":"");
      GBAA(dbuf, "<option value=\"on\" %s>on</option>", (intVal==1)?"selected":"");
      GBAA(dbuf, "<option value=\"auto\" %s>auto</option>", (intVal==2)?"selected":"");
      GBAS(dbuf, "</select>");
      break;
    default:
      //      GBAA(dbuf, "<input type=\"text\" name=\"%s\" value=\"%.2f\"/>", sValueName, u.f);
      GBAA(dbuf, "%.2f", u.f);
      break;
    } /* switch */
  } /* if actor */
  else {
    DBG_ERROR(NULL, "Adding sensor (%s=%.2f)", sValueName, u.f);
    GBAA(dbuf, "%.2f", u.f);
  }
}



AQH_VALUE *AQH_ModDevices_GetValueForDevice(AQH_DATACLIENT *dc, const char *sDeviceName, const char *sValueName)
{
  AQH_VALUE_LIST *valueList;

  valueList=AQH_DataClient_GetValues(dc, sDeviceName, 0);
  if (valueList) {
    AQH_VALUE *value;

    value=AQH_Value_List_First(valueList);
    while(value) {
      const char *s;

      s=AQH_Value_GetName(value);
      if (s && *s && strcasecmp(s, sValueName)==0) {
        break;
      }
      value=AQH_Value_List_Next(value);
    }

    if (value) {
      AQH_Value_List_Del(value);
      AQH_Value_List_free(valueList);
      return value;
    }

    AQH_Value_List_free(valueList);
  }

  return NULL;
}



AQH_DEVICE *AQH_ModDevices_GetDevice(AQH_DATACLIENT *dc, const char *sDeviceName)
{
  AQH_DEVICE_LIST *deviceList;

  deviceList=AQH_DataClient_GetDevices(dc, sDeviceName);
  if (deviceList) {
    AQH_DEVICE *device;

    device=AQH_Device_List_First(deviceList);
    while(device) {
      const char *s;

      s=AQH_Device_GetNameForSystem(device);
      if (s && *s && 0==strcasecmp(s, sDeviceName)) {
        AQH_Device_List_Del(device);
        AQH_Device_List_free(deviceList);
        return device;
      }
      device=AQH_Device_List_Next(device);
    }
    AQH_Device_List_free(deviceList);
  }

  return NULL;
}



int AQH_ModDevices_ValueGetLastDataAsInt(AQH_DATACLIENT *dc, const AQH_VALUE *value, int defaultValue)
{
  const char *sValueSystemName;
  uint64_t dataPoints[2];
  uint64_t recvdNum;
  //  uint64_t timestamp;
  union {double f; uint64_t i;} u;
  int intVal;

  sValueSystemName=AQH_Value_GetNameForSystem(value);
  recvdNum=AQH_DataClient_GetLastData(dc, sValueSystemName, &dataPoints[0], 1);
  if (recvdNum>0) {
    //    timestamp=dataPoints[0];
    u.i=dataPoints[1];
    intVal=(int) u.f;
  }
  else {
    DBG_INFO(NULL, "No last value for \"%s\"", sValueSystemName);
    intVal=defaultValue;
  }

  return intVal;
}



uint32_t AQH_ModDevices_ValueGetLastDataAsUint32(AQH_DATACLIENT *dc, const AQH_VALUE *value, uint32_t defaultValue)
{
  const char *sValueSystemName;
  uint64_t dataPoints[2];
  uint64_t recvdNum;
  //  uint64_t timestamp;
  union {double f; uint64_t i;} u;
  uint32_t uintVal;

  sValueSystemName=AQH_Value_GetNameForSystem(value);
  recvdNum=AQH_DataClient_GetLastData(dc, sValueSystemName, &dataPoints[0], 1);
  if (recvdNum>0) {
    //    timestamp=dataPoints[0];
    u.i=dataPoints[1];
    uintVal=(uint32_t) u.f;
    DBG_ERROR(NULL, "Transformed value %.2f -> %08x", u.f, uintVal);
  }
  else {
    DBG_INFO(NULL, "No last value for \"%s\"", sValueSystemName);
    uintVal=defaultValue;
  }

  return uintVal;
}




AQDG_GRAPH_DATAPAIR_LIST *AQH_ModDevices_RequestDataPairList(AQH_DATACLIENT *dc, const char *systemValueName,
                                                             uint64_t tsBegin, uint64_t tsEnd, uint64_t num)
{
  uint64_t *dataPoints;
  uint64_t recvdNum;

  dataPoints=malloc(num*sizeof(uint64_t)*2);

  recvdNum=AQH_DataClient_GetPeriodData(dc, systemValueName, dataPoints, num, tsBegin, tsEnd);
  if (recvdNum>0) {
    AQDG_GRAPH_DATAPAIR_LIST *dpList;

    dpList=_createDataPairListFromDataPoints(dataPoints, recvdNum);
    free(dataPoints);
    return dpList;
  }
  else {
    DBG_ERROR(NULL, "No data received for %s", systemValueName);
    free(dataPoints);
    return NULL;
  }
}



void AQH_ModDevices_RedirectToDevice(AQCGI_REQUEST *rq, const char *destLocation, const AQH_DEVICE *device)
{
  const char *sDeviceName;
  GWEN_BUFFER *pbuf;

  sDeviceName=AQH_Device_GetName(device);
  pbuf=GWEN_Buffer_new(0, 256, 0, 1);
  GWEN_Buffer_AppendString(pbuf, "Location: ");
  GWEN_Buffer_AppendString(pbuf, (destLocation && *destLocation)?destLocation:"index.html");
  if (sDeviceName && *sDeviceName) {
    GWEN_Buffer_AppendString(pbuf, "?device=");
    GWEN_Text_EscapeToBuffer(sDeviceName, pbuf);
  }
  AQH_ModService_Redirect(rq, GWEN_Buffer_GetStart(pbuf));
  GWEN_Buffer_free(pbuf);
}



AQDG_GRAPH_DATAPAIR_LIST *_createDataPairListFromDataPoints(const uint64_t *dataPoints, uint64_t numValues)
{
  AQDG_GRAPH_DATAPAIR_LIST *dpList;
  uint64_t i;

  DBG_DEBUG(NULL, "Got %d datapoints", (int) numValues);
  dpList=AQDG_Graph_DataPair_List_new();
  for(i=0; i<numValues; i++) {
    AQDG_GRAPH_DATAPAIR *dp;
    double timestamp;
    union {double f; uint64_t i;} u;
  
    timestamp=(double)(*(dataPoints++));
    u.i=*(dataPoints++);
    dp=AQDG_Graph_DataPair_new();
    AQDG_Graph_DataPair_SetValueX(dp, timestamp);
    AQDG_Graph_DataPair_SetValueY(dp, u.f);
    AQDG_Graph_DataPair_List_Add(dp, dpList);
  }
  AQDG_Graph_DataPair_List_SortByValueX(dpList, 1);
  return dpList;
}



