/****************************************************************************
 * 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 "./server.h"
#include "./net_read.h"
#include "aqhome-react/units/u_timer.h"
#include "aqhome-react/types/prgrule.h"
#include "aqhome-react/types/prgrule-t.h"

#include "aqhome/aqhome.h"
#include "aqhome/data/path-t.h"
#include "aqhome/data/vars-t.h"
#include "aqhome/data/vars_dbread-t.h"
#include "aqhome/data/vars_dbwrite-t.h"

#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/args.h>
#include <gwenhywfar/logger.h>
#include <gwenhywfar/db.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/cgui.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/testframework.h>

#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <time.h>



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

#define I18N(msg) msg
#define I18S(msg) msg


#define FULL_DEBUG

#define AQHOME_REACT_READNET_INTERVAL 300
#define AQHOME_REACT_SAVE_INTERVAL    300

#define CONNCLEAN_INTERVAL_IN_SECS  2
#define CONNCHECK_INTERVAL_IN_SECS  10


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

static void _runService(AQH_OBJECT *aqh, AQH_EVENT_LOOP *eventLoop);
static int _diffInSeconds(time_t t1, time_t t0);
static int _testModules(int argc, char **argv);
static int _test(int argc, char **argv);

#ifdef HAVE_SIGNAL_H
static int _setSignalHandlers(void);
static int _setupSigAction(struct sigaction *sa, int sig);
static void _signalHandler(int s);
static struct sigaction saINT,saTERM, saHUP, saTSTP, saCONT, saPIPE;
#endif



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

static int stopService=0;



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

int main(int argc, char **argv)
{
  int rv;
  AQH_EVENT_LOOP *eventLoop;
  AQH_OBJECT *aqh;
  GWEN_GUI *gui;

  rv=GWEN_Init();
  if (rv) {
    fprintf(stderr, "ERROR: Unable to init Gwen.\n");
    return 2;
  }

  GWEN_Logger_Open(0, "aqhome-react", 0, GWEN_LoggerType_Console, GWEN_LoggerFacility_User);
  GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Warning);
  //GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Info);

  rv=_setSignalHandlers();
  if (rv<0) {
    DBG_INFO(NULL, "here (%d)", rv);
    return rv;
  }

  rv=AQH_Init();
  if (rv<0) {
    DBG_INFO(NULL, "here (%d)", rv);
    return 2;
  }

  gui=GWEN_Gui_CGui_new();
  GWEN_Gui_SetGui(gui);

  if (argc>1) {
    const char *cmd;

    cmd=argv[1];
    if (cmd && *cmd) {
      if (strcasecmp(cmd, "unittest")==0) {
	rv=_testModules(argc, argv);
	GWEN_Gui_SetGui(NULL);
	GWEN_Gui_free(gui);
	return rv;
      }
      else if (strcasecmp(cmd, "test")==0) {
	rv=_test(argc, argv);
	GWEN_Gui_SetGui(NULL);
	GWEN_Gui_free(gui);
	return rv;
      }
    }
  }

  eventLoop=AQH_EventLoop_new();
  aqh=AQH_ReactServer_new(eventLoop);
  rv=AQH_ReactServer_Init(aqh, argc, argv);
  if (rv<0) {
    if (rv==GWEN_ERROR_CLOSE)
      return 1;
    DBG_INFO(NULL, "here (%d)", rv);
    return 2;
  }

  _runService(aqh, eventLoop);

  AQH_ReactServer_Fini(aqh);
  AQH_Object_free(aqh);
  AQH_EventLoop_free(eventLoop);

  GWEN_Gui_SetGui(NULL);
  GWEN_Gui_free(gui);

  return 0;
}



void _runService(AQH_OBJECT *aqh, AQH_EVENT_LOOP *eventLoop)
{
  time_t timeStart;
  int timeout;
  time_t timeLastConnCheck;
  time_t lastFileScanTime;
  time_t lastSaveTime;

  timeout=AQH_ReactServer_GetTimeout(aqh);
  timeStart=time(NULL);
  timeLastConnCheck=time(NULL);
  lastFileScanTime=timeStart;
  lastSaveTime=timeStart;

  AQH_ReactServer_SetLatestNetworkFileTime(aqh, AQH_ReactServer_GetNewestUnitNetFiletime());

  while(!stopService) {
    time_t now;
    int rv;

    AQH_EventLoop_Run(eventLoop, 2000);
    AQH_ReactServer_HandleBrokerMsgs(aqh);

    now=time(NULL);

    if (_diffInSeconds(now, timeLastConnCheck)>CONNCHECK_INTERVAL_IN_SECS) {
      DBG_INFO(NULL, "Check connections");
      AQH_ReactServer_CheckBrokerConnection(aqh);
      timeLastConnCheck=now;
    }

    AQH_ReactServer_ProcessAllUnits(aqh);

    if (_diffInSeconds(now, lastFileScanTime)>AQHOME_REACT_READNET_INTERVAL) {
      time_t tNew;
      time_t t;

      DBG_INFO(NULL, "Checking network files");
      tNew=AQH_ReactServer_GetNewestUnitNetFiletime();
      t=AQH_ReactServer_GetLatestNetworkFileTime(aqh);
      if (tNew && tNew>t) {
        DBG_INFO(NULL, "Reloading network files");
        AQH_ReactServer_ReloadUnitNets(aqh);
        AQH_ReactServer_SetLatestNetworkFileTime(aqh, tNew);
      }
      lastFileScanTime=now;
    }

    if (_diffInSeconds(now, lastSaveTime)>AQHOME_REACT_SAVE_INTERVAL) {
      DBG_INFO(NULL, "Writing var file");
      rv=AQH_ReactServer_WriteVarsFile(aqh);
      if (rv<0) {
        DBG_INFO(NULL, "Error writing runtime data");
      }
      lastSaveTime=time(NULL);
    }

    if (timeout && (_diffInSeconds(now, timeStart)>timeout)) {
      DBG_INFO(NULL, "Timeout");
      break;
    }
  } /* while */
}



int _setSignalHandlers(void)
{
#ifdef HAVE_SIGNAL_H
  int rv;

  rv=_setupSigAction(&saINT, SIGINT);
  if (rv)
    return rv;

  rv=_setupSigAction(&saTERM, SIGTERM);
  if (rv)
    return rv;

  rv=_setupSigAction(&saHUP, SIGHUP);
  if (rv)
    return rv;

  rv=_setupSigAction(&saPIPE, SIGPIPE);
  if (rv)
    return rv;

#  ifdef SIGTSTP
  rv=_setupSigAction(&saTSTP, SIGTSTP);
  if (rv)
    return rv;
#  endif

#  ifdef SIGCONT
  rv=_setupSigAction(&saCONT, SIGCONT);
  if (rv)
    return rv;
#  endif
#endif
  return 0;
}



int _setupSigAction(struct sigaction *sa, int sig)
{
  sa->sa_handler=_signalHandler;
  sigemptyset(&sa->sa_mask);
  sa->sa_flags=0;
  if (sigaction(sig, sa, 0)) {
    DBG_ERROR(NULL, "Could not setup signal handler for signal %d", sig);
    return GWEN_ERROR_IO;
  }

  return 0;
}



void _signalHandler(int s)
{
  switch(s) {
  case SIGINT:
  case SIGTERM:
  case SIGHUP:
    DBG_WARN(0, "Received signal %d, stopping service in next loop.",s);
    stopService=1;
    break;
  case SIGPIPE:
    DBG_WARN(0, "Received PIPE signal");
    break;
  default:
    DBG_WARN(0, "Unknown signal %d",s);
    break;
  }
}



int _testModules(int argc, char **argv)
{
  GWEN_GUI *gui;
  GWEN_TEST_FRAMEWORK *tf;
  int rv;

  gui=GWEN_Gui_CGui_new();
  GWEN_Gui_SetGui(gui);

  tf=TestFramework_new();

  rv=AQHREACT_PrgRule_AddTests(TestFramework_GetModulesRoot(tf));
  if (rv<0) {
    fprintf(stderr, "Adding module \"PrgRule\" failed.\n");
    return 2;
  }

  rv=AQH_Path_AddTests(TestFramework_GetModulesRoot(tf));
  if (rv<0) {
    fprintf(stderr, "Adding module \"path\" failed.\n");
    return 2;
  }

  rv=AQH_Vars_AddTests(TestFramework_GetModulesRoot(tf));
  if (rv<0) {
    fprintf(stderr, "Adding module \"vars\" failed.\n");
    return 2;
  }

  rv=AQH_Vars_DbRead_AddTests(TestFramework_GetModulesRoot(tf));
  if (rv<0) {
    fprintf(stderr, "Adding module \"vars_dbread\" failed.\n");
    return 2;
  }

  rv=AQH_Vars_DbWrite_AddTests(TestFramework_GetModulesRoot(tf));
  if (rv<0) {
    fprintf(stderr, "Adding module \"vars_dbwrite\" failed.\n");
    return 2;
  }

  argc--;
  argv++;
  rv=TestFramework_Run(tf, argc, argv);
  if (rv) {
    fprintf(stderr, "SomeError in tests failed.\n");
    GWEN_Gui_SetGui(NULL);
    GWEN_Gui_free(gui);
    return 2;
  }
  TestFramework_free(tf);

  GWEN_Gui_SetGui(NULL);
  GWEN_Gui_free(gui);
  return 0;
}



int _test(int argc, char **argv)
{
  if (argc>2) {
    AQHREACT_PRGRULE_LIST *ruleList;

    GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Info);
    ruleList=AQHREACT_PrgRule_ReadRules(argv[2]);
    if (ruleList==NULL) {
      DBG_ERROR(NULL, "Error in rules");
      return 2;
    }
    DBG_ERROR(NULL, "Rules ok (%d)", AQHREACT_PrgRule_List_GetCount(ruleList));
    AQHREACT_PrgRule_List_free(ruleList);
    return 0;
  }
  else {
    DBG_ERROR(NULL, "No rules");
    return 2;
  }
}




int _diffInSeconds(time_t t1, time_t t0)
{
  return t1-t0;
}

