/****************************************************************************
 * 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 "./prgrule_p.h"

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

#include <ctype.h>


GWEN_LIST_FUNCTIONS(AQHREACT_PRGRULE, AQHREACT_PrgRule)



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

const char *_readRule(AQHREACT_PRGRULE *prgRule, const char *s);

const char *_readField(const char *s, uint64_t *ptrBitField, int minValue, int maxValue);
const char *_readRangeOpenBegin(const char *s, uint64_t *ptrBitField, int minValue, int maxValue);
const char *_readInterval(const char *s, uint64_t *ptrBitField, int minValue, int maxValue);
const char *_readValueAndProbablyRange(const char *s, uint64_t *ptrBitField, int minValue, int maxValue);
const char *_readDouble(const char *s, double *ptrDouble);

int _valueOkay(int v, int minValue, int maxValue);
int _rangeOkay(int startValue, int endValue, int minValue, int maxValue);
void _setBitField(uint64_t *ptrBitField, int startValue, int endValue);


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

AQHREACT_PRGRULE *AQHREACT_PrgRule_new(void)
{
  AQHREACT_PRGRULE *prgRule;

  GWEN_NEW_OBJECT(AQHREACT_PRGRULE, prgRule);
  GWEN_LIST_INIT(AQHREACT_PRGRULE, prgRule);

  return prgRule;
}



void AQHREACT_PrgRule_free(AQHREACT_PRGRULE *prgRule)
{
  if (prgRule) {
    GWEN_LIST_FINI(AQHREACT_PRGRULE, prgRule);
    GWEN_FREE_OBJECT(prgRule);
  }
}



double AQHREACT_PrgRule_GetValue(const AQHREACT_PRGRULE *prgRule)
{
  return prgRule?prgRule->value:0.0;
}



int AQHREACT_PrgRule_Matches(const AQHREACT_PRGRULE *prgRule, int min, int hour, int dayOfMonth, int month, int dayOfWeek)
{
  if ((prgRule->bitfieldMonth & (1L<<month)) &&
      (prgRule->bitfieldDayOfMonth & (1L<<dayOfMonth)) &&
      (prgRule->bitfieldDayOfWeek & (1L<<dayOfWeek)) &&
      (prgRule->bitfieldHour & (1L<<hour)) &&
      (prgRule->bitfieldMinute & (1L<<min)))
    return 1;
  return 0;
}



AQHREACT_PRGRULE *AQHREACT_PrgRule_List_FindMatchingRule(AQHREACT_PRGRULE_LIST *prgRuleList,
							 int min, int hour, int dayOfMonth, int month, int dayOfWeek)
{
  AQHREACT_PRGRULE *prgRule;

  prgRule=AQHREACT_PrgRule_List_First(prgRuleList);
  while(prgRule) {
    if (AQHREACT_PrgRule_Matches(prgRule, min, hour, dayOfMonth, month, dayOfWeek))
      return prgRule;
    prgRule=AQHREACT_PrgRule_List_Next(prgRule);
  }
  return NULL;
}



AQHREACT_PRGRULE_LIST *AQHREACT_PrgRule_ReadRules(const char *s)
{
  AQHREACT_PRGRULE_LIST *prgRuleList;

  prgRuleList=AQHREACT_PrgRule_List_new();

  while(*s) {
    AQHREACT_PRGRULE *prgRule;
    const char *r;

    prgRule=AQHREACT_PrgRule_new();
    r=_readRule(prgRule, s);
    if (r==NULL){
      DBG_INFO(NULL, "Error parsing \"%s\"", s);
      AQHREACT_PrgRule_free(prgRule);
      AQHREACT_PrgRule_List_free(prgRuleList);
      return NULL;
    }
    AQHREACT_PrgRule_List_Add(prgRule, prgRuleList);
    s=r;

    while(*s && isspace(*s))
      s++;
    if (*s==';')
      s++;
  }

  if (AQHREACT_PrgRule_List_GetCount(prgRuleList)<1) {
    AQHREACT_PrgRule_List_free(prgRuleList);
    return NULL;
  }

  return prgRuleList;
}



const char *_readRule(AQHREACT_PRGRULE *prgRule, const char *s)
{
  uint64_t v;
  const char *r;

  v=0;
  r=_readField(s, &v, 0, 59);
  if (r==NULL) {
    DBG_ERROR(NULL, "Error reading MIN field at \"%s\"", s);
    return NULL;
  }
  prgRule->bitfieldMinute=v;

  s=r;
  v=0;
  r=_readField(s, &v, 0, 23);
  if (r==NULL) {
    DBG_ERROR(NULL, "Error reading HOUR field at \"%s\"", s);
    return NULL;
  }
  prgRule->bitfieldHour=(uint32_t)(v & 0xffffffffl);

  s=r;
  v=0;
  r=_readField(s, &v, 1, 31);
  if (r==NULL) {
    DBG_ERROR(NULL, "Error reading DAYOFMONTH field at \"%s\"", s);
    return NULL;
  }
  prgRule->bitfieldDayOfMonth=(uint32_t)(v & 0xffffffffl);

  s=r;
  v=0;
  r=_readField(s, &v, 1, 12);
  if (r==NULL) {
    DBG_ERROR(NULL, "Error reading MONTH field at \"%s\"", s);
    return NULL;
  }
  prgRule->bitfieldMonth=(uint16_t)(v & 0xffffl);

  s=r;
  v=0;
  r=_readField(s, &v, 0, 6);
  if (r==NULL) {
    DBG_ERROR(NULL, "Error reading DAYOFWEEK field at \"%s\"", s);
    return NULL;
  }
  prgRule->bitfieldDayOfWeek=(uint8_t)(v & 0xffl);

  s=r;
  r=_readDouble(s, &(prgRule->value));
  if (r==NULL) {
    DBG_ERROR(NULL, "Error reading DOUBLE field at \"%s\"", s);
    return NULL;
  }

  return r;
}



const char *_readField(const char *s, uint64_t *ptrBitField, int minValue, int maxValue)
{
  while(*s && isspace(*s))
    s++;

  if (*s==0) {
    DBG_INFO(NULL, "Premature field end");
    return NULL;
  }

  while(s && *s) {
    if (*s=='*') {
      _setBitField(ptrBitField, minValue, maxValue);
      s++;
    }
    else if (*s=='-') {
      s=_readRangeOpenBegin(++s, ptrBitField, minValue, maxValue);
      if (s==NULL) {
	DBG_INFO(NULL, "here");
	return NULL;
      }
    }
    else if (*s=='/') {
      s=_readInterval(++s, ptrBitField, minValue, maxValue);
      if (s==NULL) {
	DBG_INFO(NULL, "here");
	return NULL;
      }
    }
    else if (isdigit(*s)) {
      s=_readValueAndProbablyRange(s, ptrBitField, minValue, maxValue);
      if (s==NULL) {
	DBG_INFO(NULL, "here");
	return NULL;
      }
    }
    else {
      DBG_ERROR(NULL, "Unexpected character at \"%s\"", s);
      return NULL;
    }

    if (isspace(*s)){
      /* field done */
      return s;
    }
    else if (*s==',') {
      s++;
    }
    else {
      DBG_ERROR(NULL, "Unexpected character at \"%s\"", s);
      return NULL;
    }
  } /* while */

  return s;
}



const char *_readRangeOpenBegin(const char *s, uint64_t *ptrBitField, int minValue, int maxValue)
{
  int v=0;

  if (!isdigit(*s)) {
    DBG_ERROR(NULL, "Need at least start or end value for range");
    return NULL;
  }
  while(isdigit(*s)) {
    v*=10;
    v+=(*s)-'0';
    s++;
  }
  if (!_rangeOkay(minValue, v, minValue, maxValue)) {
    DBG_INFO(NULL, "here");
    return NULL;
  }
  _setBitField(ptrBitField, minValue, v);
  return s;
}



const char *_readInterval(const char *s, uint64_t *ptrBitField, int minValue, int maxValue)
{
  int v=0;
  int i;

  while(isdigit(*s)) {
    v*=10;
    v+=(*s)-'0';
    s++;
  }
  if (v==0) {
    DBG_ERROR(NULL, "Bad value 0 for interval (/0)");
    return NULL;
  }

  for (i=minValue; i<maxValue; i+=v) {
    _setBitField(ptrBitField, i, i);
  }

  return s;
}



const char *_readValueAndProbablyRange(const char *s, uint64_t *ptrBitField, int minValue, int maxValue)
{
  int v=0;

  while(isdigit(*s)) {
    v*=10;
    v+=(*s)-'0';
    s++;
  }
  if (!_valueOkay(v, minValue, maxValue)) {
    DBG_INFO(NULL, "here");
    return NULL;
  }

  if (*s=='-') {
    s=_readRangeOpenBegin(++s, ptrBitField, v, maxValue);
    if (s==NULL) {
      DBG_INFO(NULL, "here");
      return NULL;
    }
  }
  else {
    if (!_valueOkay(v, minValue, maxValue)) {
      DBG_INFO(NULL, "here");
      return NULL;
    }
    _setBitField(ptrBitField, v, v);
  }
  return s;
}



const char *_readDouble(const char *s, double *ptrDouble)
{
  const char *sStart;
  int rv;

  while(*s && isspace(*s))
    s++;
  sStart=s;
  if (*s=='#') {
    uint32_t v=0;

    s++;
    while(*s) {
      unsigned char c;

      c=tolower(*s);
      if ((c>='0' && c<='9') || (c>='a' && c<='f')) {
	c-='0';
	if (c>9)
	  c-=7;
	v<<=4;
	v+=c;
      }
      else
	break;
      s++;
    }
    *ptrDouble=(double) v;
  }
  else {
    while(*s && (isdigit(*s) || *s=='.' || *s=='+' || *s=='-'))
      s++;
    rv=GWEN_Text_StringToDouble(sStart, ptrDouble);
    if (rv<0) {
      DBG_ERROR(NULL, "Error reading double: %d", rv);
      return NULL;
    }
  }

  return s;
}



int _valueOkay(int v, int minValue, int maxValue)
{
  if (v<minValue) {
    DBG_ERROR(NULL, "Value below minimum value (%d < %d)", v, minValue);
    return 0;
  }
  if (v>maxValue) {
    DBG_ERROR(NULL, "Value above maxmum value (%d > %d)", v, maxValue);
    return 0;
  }

  return 1;
}



int _rangeOkay(int startValue, int endValue, int minValue, int maxValue)
{
  if (startValue>endValue) {
    DBG_ERROR(NULL, "Startvalue (%d) > endvalue (%d)", startValue, endValue);
    return 0;
  }

  if (startValue<minValue || startValue>maxValue) {
    DBG_ERROR(NULL, "Startvalue (%d) out of range (%d-%d)", startValue, minValue, maxValue);
    return 0;
  }

  if (endValue>maxValue) {
    DBG_ERROR(NULL, "Endvalue (%d) out of range (%d-%d)", endValue, minValue, maxValue);
    return 0;
  }

  return 1;
}



void _setBitField(uint64_t *ptrBitField, int startValue, int endValue)
{
  int i;
  uint64_t m;
  uint64_t v=0;

  m=1l<<startValue;
  for (i=startValue; i<=endValue; i++) {
    v|=m;
    m<<=1;
  }
  *ptrBitField|=v;
}





#include "prgrule-t.c"



