/****************************************************************************
 * 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 "./eventloop_p.h"
#include "./fdobject.h"

#include <gwenhywfar/debug.h>

#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <errno.h>
#include <string.h>



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

static int _sampleFdsIntoSet(AQH_OBJECT_LIST2 *ol, fd_set *fds, int mode);
static void _markReadyFdObjects(AQH_OBJECT_LIST2 *ol, fd_set *fds, int mode);
static void _signalReadyFdObjects(AQH_OBJECT_LIST2 *ol);



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

void AQH_EventLoop_Run(AQH_EVENT_LOOP *eventLoop, int timeoutInMillisecs)
{
  static int selectErrorCount=0;
  fd_set fdRead;
  fd_set fdWrite;
  int highestFd=-1;
  int rv;

  FD_ZERO(&fdRead);
  FD_ZERO(&fdWrite);

  highestFd=_sampleFdsIntoSet(eventLoop->fdObjectList, &fdRead, AQH_FDOBJECT_FDMODE_READ);
  rv=_sampleFdsIntoSet(eventLoop->fdObjectList, &fdWrite, AQH_FDOBJECT_FDMODE_WRITE);
  highestFd=(rv>highestFd)?rv:highestFd;

  if (highestFd>-1) {
    struct timeval tv;

    tv.tv_sec=timeoutInMillisecs/1000;
    tv.tv_usec=(timeoutInMillisecs%1000)*1000;
    rv=select(highestFd+1, &fdRead, &fdWrite, NULL, &tv);
    if (rv>0) {
      /* some fds became active */
      if (selectErrorCount)
	selectErrorCount--;
      _markReadyFdObjects(eventLoop->fdObjectList, &fdRead, AQH_FDOBJECT_FDMODE_READ);
      _markReadyFdObjects(eventLoop->fdObjectList, &fdWrite, AQH_FDOBJECT_FDMODE_WRITE);
      _signalReadyFdObjects(eventLoop->fdObjectList);
    }
    else if (rv<0) {
      if (errno!=EINTR) {
        /* error */
	DBG_ERROR(AQH_LOGDOMAIN, "Error on SELECT: %d (%s)", errno, strerror(errno));
	selectErrorCount++;
	/* TODO: walk through all sockets to find the bad one */
	if (selectErrorCount==90) {
	  /* increase log level */
	  DBG_ERROR(AQH_LOGDOMAIN, "Increasing log level to INFO");
	  GWEN_Logger_SetLevel(AQH_LOGDOMAIN, GWEN_LoggerLevel_Info);
	}
	else if (selectErrorCount==100) {
	  DBG_ERROR(AQH_LOGDOMAIN, "Aborting due to too many SELECT errors, will be restarted by systemd");
	  abort();
	}
      }
    }
    else {
      /* no fd became active (TODO: maybe signal deep idle objects?) */
      if (selectErrorCount)
	selectErrorCount--;
    }
  }
}



int _sampleFdsIntoSet(AQH_OBJECT_LIST2 *ol, fd_set *fds, int mode)
{
  AQH_OBJECT_LIST2_ITERATOR *it;
  int highestFd=-1;

  it=AQH_Object_List2_First(ol);
  if (it) {
    AQH_OBJECT *o;

    o=AQH_Object_List2Iterator_Data(it);
    while(o) {
      int m;

      m=AQH_FdObject_GetFdMode(o);
      if (m==mode) {
        int fd;

        fd=AQH_FdObject_GetFd(o);
        if (fd>-1) {
          highestFd=(fd>highestFd)?fd:highestFd;
          FD_SET(fd, fds);
        }
      }
      o=AQH_Object_List2Iterator_Next(it);
    }
    AQH_Object_List2Iterator_free(it);
  }

  return highestFd;
}



void _markReadyFdObjects(AQH_OBJECT_LIST2 *ol, fd_set *fds, int mode)
{
  AQH_OBJECT_LIST2_ITERATOR *it;

  it=AQH_Object_List2_First(ol);
  if (it) {
    AQH_OBJECT *o;

    o=AQH_Object_List2Iterator_Data(it);
    while(o) {
      int m;

      m=AQH_FdObject_GetFdMode(o);
      if (m==mode) {
        int fd;

        fd=AQH_FdObject_GetFd(o);
        if (FD_ISSET(fd, fds))
          AQH_Object_AddFlags(o, AQH_OBJECT_FLAGS_FIRE);
      }
      o=AQH_Object_List2Iterator_Next(it);
    }

    AQH_Object_List2Iterator_free(it);
  }
}



void _signalReadyFdObjects(AQH_OBJECT_LIST2 *ol)
{
  AQH_OBJECT_LIST2_ITERATOR *it;

  it=AQH_Object_List2_First(ol);
  if (it) {
    AQH_OBJECT *o;

    o=AQH_Object_List2Iterator_Data(it);
    while(o) {
      if (AQH_Object_GetFlags(o) & AQH_OBJECT_FLAGS_FIRE) {
        AQH_Object_SubFlags(o, AQH_OBJECT_FLAGS_FIRE);
        AQH_Object_EmitSignal(o, AQH_FDOBJECT_SIGNAL_ISREADY, 0, NULL);
      }
      o=AQH_Object_List2Iterator_Next(it);
    }

    AQH_Object_List2Iterator_free(it);
  }
}










