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

#include <gwenhywfar/gwenhywfar.h>
#include <gwenhywfar/logger.h>
#include <gwenhywfar/pathmanager.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/i18n.h>
#include <gwenhywfar/stringlist.h>
#include <gwenhywfar/directory.h>

#include <stdlib.h>



#define AQHOME_PM_LIBNAME           "aqhome"
#define AQHOME_PM_SYSCONFDIR        "sysconfdir"
#define AQHOME_PM_DATADIR           "datadir"
#define AQHOME_PM_RTDATADIR         "rtdatadir"
#define AQHOME_PM_LOCALEDIR         "localedir"

#define AQHOME_SYSCONFIG_FILE       "aqhome.conf"



static void _initLogging(void);
static void _finiLogging(void);
static void _initPathManager(void);
static void _finiPathManager(void);
static void _initI18n(void);
static void _definePath(const char *pathName, const char *pathValue);
static GWEN_STRINGLIST *_getListOfMatchingFiles(const char *pathName, const char *subFolder, const char *mask);
static GWEN_BUFFER *_getRuntimeFilePath(const char *pathName, const char *sFilename);
static GWEN_BUFFER *_findFileinPath(const char *pathName, const char *sFilename);
static int _readUint8DataFromDouble(double d, uint16_t *pDataVal, uint16_t *pDataDenom);
static int _readUint16DataFromDouble(double d, uint16_t *pDataVal, uint16_t *pDataDenom);
static int _readUint32DataFromDouble(double d, uint16_t *pDataVal, uint16_t *pDataDenom);

static int _readUint8DataFromString(const char *s, uint16_t *pDataVal, uint16_t *pDataDenom);
static int _readUint16DataFromString(const char *s, uint16_t *pDataVal, uint16_t *pDataDenom);
static int _readUint32DataFromString(const char *s, uint16_t *pDataVal, uint16_t *pDataDenom);



int AQH_Init(void)
{
  int rv;

  rv=GWEN_Init();
  if (rv) {
    DBG_ERROR_ERR(AQH_LOGDOMAIN, rv);
    return rv;
  }

  _initLogging();
  _initPathManager();
  _initI18n();

  return 0;
}



void AQH_Fini(void)
{
  _finiPathManager();
  _finiLogging();

  GWEN_Fini();
}



GWEN_STRINGLIST *AQH_GetGlobalDataDirs(void)
{
  return GWEN_PathManager_GetPaths(AQHOME_PM_LIBNAME, AQHOME_PM_DATADIR);
}



GWEN_STRINGLIST *AQH_GetGlobalSysconfDirs(void)
{
  return GWEN_PathManager_GetPaths(AQHOME_PM_LIBNAME, AQHOME_PM_SYSCONFDIR);
}



GWEN_STRINGLIST *AQH_GetListOfMatchingDataFiles(const char *subFolder, const char *mask)
{
  return _getListOfMatchingFiles(AQHOME_PM_DATADIR, subFolder, mask);
}



GWEN_STRINGLIST *AQH_GetListOfMatchingRuntimeDataFiles(const char *subFolder, const char *mask)
{
  return _getListOfMatchingFiles(AQHOME_PM_RTDATADIR, subFolder, mask);
}



GWEN_STRINGLIST *AQH_GetListOfMatchingSysconfFiles(const char *subFolder, const char *mask)
{
  return _getListOfMatchingFiles(AQHOME_PM_SYSCONFDIR, subFolder, mask);
}



GWEN_BUFFER *AQH_GetRuntimeFilePath(const char *sFilename)
{
  return _getRuntimeFilePath(AQHOME_PM_RTDATADIR, sFilename);
}



GWEN_BUFFER *AQH_FindPathOfRuntimeFile(const char *sFilename)
{
  return _findFileinPath(AQHOME_PM_RTDATADIR, sFilename);
}



GWEN_BUFFER *AQH_FindPathOfSysconfFile(const char *sFilename)
{
  return _findFileinPath(AQHOME_PM_SYSCONFDIR, sFilename);
}



GWEN_BUFFER *AQH_FindPathOfDataFile(const char *sFilename)
{
  return _findFileinPath(AQHOME_PM_DATADIR, sFilename);
}




GWEN_DB_NODE *AQH_LoadConfigFile(void)
{
  GWEN_BUFFER *fbuf;
  int rv;

  fbuf=GWEN_Buffer_new(0, 256, 0, 1);
  rv=GWEN_PathManager_FindFile(AQHOME_PM_LIBNAME, AQHOME_PM_SYSCONFDIR, AQHOME_SYSCONFIG_FILE, fbuf);
  if (rv<0) {
    DBG_INFO(AQH_LOGDOMAIN, "Config file \"%s\" not found (%d)", AQHOME_SYSCONFIG_FILE, rv);
    GWEN_Buffer_free(fbuf);
    return NULL;
  }
  else {
    GWEN_DB_NODE *db;

    db=GWEN_DB_Group_new("aqhome-config");
    rv=GWEN_DB_ReadFile(db, GWEN_Buffer_GetStart(fbuf), GWEN_DB_FLAGS_DEFAULT);
    if (rv<0) {
      DBG_INFO(AQH_LOGDOMAIN, "Error reading config file \"%s\" (%d)", AQHOME_SYSCONFIG_FILE, rv);
      GWEN_Buffer_free(fbuf);
      GWEN_DB_Group_free(db);
      return NULL;
    }
    GWEN_Buffer_free(fbuf);
    return db;
  }
}



void AQH_MergeConfigFileIntoConfig(GWEN_DB_NODE *dbArgs, const char *destDbGroupName)
{
  GWEN_DB_NODE *dbConfig;

  dbConfig=AQH_LoadConfigFile();
  if (dbConfig) {
    GWEN_DB_GroupRename(dbConfig, destDbGroupName);
    GWEN_DB_AddGroup(dbArgs, dbConfig);
  }
}



int AQH_ValueType_fromString(const char *s)
{
  if (s) {
    if (strcasecmp(s, "sensor")==0)
      return AQH_ValueType_Sensor;
    else if (strcasecmp(s, "actor")==0)
      return AQH_ValueType_Actor;
  }
  return AQH_ValueType_Unknown;
}



const char *AQH_ValueType_toString(int i)
{
  switch (i) {
  case AQH_ValueType_Sensor:  return "sensor";
  case AQH_ValueType_Actor:   return "actor";
  case AQH_ValueType_Unknown:
  default:                    return "unknown";
  }
}



int AQH_ValueDataType_fromString(const char *s)
{
  if (s) {
    if (strcasecmp(s, "int")==0)
      return AQH_ValueDataType_Int;
    else if (strcasecmp(s, "dword")==0)
      return AQH_ValueDataType_Uint32;
    else if (strcasecmp(s, "rational")==0)
      return AQH_ValueDataType_Rational;
    else if (strcasecmp(s, "uint8")==0)
      return AQH_ValueDataType_Uint8;
    else if (strcasecmp(s, "uint16")==0)
      return AQH_ValueDataType_Uint16;
    else if (strcasecmp(s, "uint32")==0)
      return AQH_ValueDataType_Uint32;
  }
  return AQH_ValueDataType_Unknown;
}



const char *AQH_ValueDataType_toString(int i)
{
  switch(i) {
  case AQH_ValueDataType_Int:      return "int";
  case AQH_ValueDataType_Rational: return "rational";
  case AQH_ValueDataType_Uint8:    return "uint8";
  case AQH_ValueDataType_Uint16:   return "uint16";
  case AQH_ValueDataType_Uint32:   return "uint32";
  case AQH_ValueDataType_Unknown:
  default:                         return "unknown";
  }
}



int AQH_ValueModality_fromString(const char *s)
{
  if (s) {
    if (strcasecmp(s, "temperature")==0)
      return AQH_ValueModality_Temperature;
    else if (strcasecmp(s, "humidity")==0)
      return AQH_ValueModality_Humidity;
    else if (strcasecmp(s, "door")==0)
      return AQH_ValueModality_Door;
    else if (strcasecmp(s, "rgb")==0)
      return AQH_ValueModality_RGB;
    else if (strcasecmp(s, "rgbw")==0)
      return AQH_ValueModality_RGBW;
    else if (strcasecmp(s, "motion")==0)
      return AQH_ValueModality_Motion;
    else if (strcasecmp(s, "co2")==0)
      return AQH_ValueModality_Co2;
    else if (strcasecmp(s, "tvoc")==0)
      return AQH_ValueModality_TVOC;
    else if (strcasecmp(s, "stats")==0)
      return AQH_ValueModality_Stats;
    else if (strcasecmp(s, "light")==0)
      return AQH_ValueModality_Light;
    else if (strcasecmp(s, "onOff")==0)
      return AQH_ValueModality_OnOff;
    else if (strcasecmp(s, "onOffAuto")==0)
      return AQH_ValueModality_OnOffAuto;
    else if (strcasecmp(s, "rpm")==0)
      return AQH_ValueModality_RPM;
  }
  return AQH_ValueModality_Unknown;
}



const char *AQH_ValueModality_toString(int i)
{
  switch(i) {
  case AQH_ValueModality_Temperature: return "temperature";
  case AQH_ValueModality_Humidity:    return "humidity";
  case AQH_ValueModality_Door:        return "door";
  case AQH_ValueModality_RGB:         return "rgb";
  case AQH_ValueModality_RGBW:        return "rgbw";
  case AQH_ValueModality_Motion:      return "motion";
  case AQH_ValueModality_Co2:         return "co2";
  case AQH_ValueModality_TVOC:        return "tvoc";
  case AQH_ValueModality_Stats:       return "stats";
  case AQH_ValueModality_Light:       return "light";
  case AQH_ValueModality_OnOff:       return "onOff";
  case AQH_ValueModality_OnOffAuto:   return "onOffAuto";
  case AQH_ValueModality_RPM:         return "rpm";
  case AQH_ValueModality_Unknown:
  default:                            return "unknown";
  }
}






void _initLogging(void)
{
  const char *s;

  if (!GWEN_Logger_IsOpen(AQH_LOGDOMAIN))
    GWEN_Logger_Open(AQH_LOGDOMAIN, "aqhome", 0, GWEN_LoggerType_Console, GWEN_LoggerFacility_User);

  s=getenv("AQHOME_LOGLEVEL");
  if (s && *s) {
    GWEN_LOGGER_LEVEL ll;

    ll=GWEN_Logger_Name2Level(s);
    GWEN_Logger_SetLevel(AQH_LOGDOMAIN, ll);
  }
  else
    GWEN_Logger_SetLevel(AQH_LOGDOMAIN, GWEN_LoggerLevel_Notice);
}



void _finiLogging(void)
{
  GWEN_Logger_Close(AQH_LOGDOMAIN);
}



void _initPathManager(void)
{
  _definePath(AQHOME_PM_SYSCONFDIR, AQHOME_SYSCONF_DIR);
  _definePath(AQHOME_PM_LOCALEDIR, AQHOME_SYSCONF_DIR);
  _definePath(AQHOME_PM_DATADIR, AQHOME_DATA_DIR);
  _definePath(AQHOME_PM_RTDATADIR, AQHOME_RTDATA_DIR);
}



void _finiPathManager(void)
{
  GWEN_PathManager_UndefinePath(AQHOME_PM_LIBNAME, AQHOME_PM_LOCALEDIR);
  GWEN_PathManager_UndefinePath(AQHOME_PM_LIBNAME, AQHOME_PM_RTDATADIR);
  GWEN_PathManager_UndefinePath(AQHOME_PM_LIBNAME, AQHOME_PM_DATADIR);
  GWEN_PathManager_UndefinePath(AQHOME_PM_LIBNAME, AQHOME_PM_SYSCONFDIR);
  GWEN_PathManager_RemovePaths(AQHOME_PM_LIBNAME);
}



void _initI18n(void)
{
  GWEN_STRINGLIST *sl;

  sl=GWEN_PathManager_GetPaths(AQHOME_PM_LIBNAME, AQHOME_PM_LOCALEDIR);
  if (sl) {
    const char *localedir;
    int rv;

    localedir=GWEN_StringList_FirstString(sl);

    rv=GWEN_I18N_BindTextDomain_Dir(PACKAGE, localedir);
    if (rv) {
      DBG_ERROR(AQH_LOGDOMAIN, "Could not bind textdomain (%d)", rv);
    }
    else {
      rv=GWEN_I18N_BindTextDomain_Codeset(PACKAGE, "UTF-8");
      if (rv) {
        DBG_ERROR(AQH_LOGDOMAIN, "Could not set codeset (%d)", rv);
      }
    }

    GWEN_StringList_free(sl);
  }
}



void _definePath(const char *pathName, const char *pathValue)
{
  /* define sysconf paths */
  GWEN_PathManager_DefinePath(AQHOME_PM_LIBNAME, pathName);
#if defined(OS_WIN32) || defined(ENABLE_LOCAL_INSTALL)
  /* add folder relative to EXE */
  GWEN_PathManager_AddRelPath(AQHOME_PM_LIBNAME, AQHOME_PM_LIBNAME, pathName, pathValue, GWEN_PathManager_RelModeExe);
#else
  /* add absolute folder */
  GWEN_PathManager_AddPath(AQHOME_PM_LIBNAME, AQHOME_PM_LIBNAME, pathName, pathValue);
#endif
}



GWEN_STRINGLIST *_getListOfMatchingFiles(const char *pathName, const char *subFolder, const char *mask)
{
  int rv;
  GWEN_STRINGLIST *sl;

  sl=GWEN_StringList_new();
  rv=GWEN_PathManager_GetMatchingFilesRecursively(AQHOME_PM_LIBNAME, pathName, subFolder, sl, mask);
  if (rv<0) {
    DBG_INFO(AQH_LOGDOMAIN,
             "Error listing matching data files (folder=%s, mask=%s)",
             subFolder?subFolder:"<empty>", mask?mask:"<empty>");
    GWEN_StringList_free(sl);
    return NULL;
  }
  return sl;
}



GWEN_BUFFER *_getRuntimeFilePath(const char *pathName, const char *sFilename)
{
  GWEN_STRINGLIST *sl;

  sl=GWEN_PathManager_GetPaths(AQHOME_PM_LIBNAME, pathName);
  if (sl) {
    GWEN_STRINGLISTENTRY *se;

    se=GWEN_StringList_FirstEntry(sl);
    if (se) {
      const char *s;

      s=GWEN_StringListEntry_Data(se);
      if (s && *s) {
        GWEN_BUFFER *buf;

        buf=GWEN_Buffer_new(0, 256, 0, 1);
        GWEN_Buffer_AppendString(buf, s);
        if (sFilename && *sFilename) {
          GWEN_Buffer_AppendByte(buf, GWEN_DIR_SEPARATOR);
          GWEN_Buffer_AppendString(buf, sFilename);
        }
        GWEN_StringList_free(sl);
        return buf;
      }
    }
    GWEN_StringList_free(sl);
  }

  return NULL;
}



GWEN_BUFFER *_findFileinPath(const char *pathName, const char *sFilename)
{
  GWEN_STRINGLIST *sl;

  sl=GWEN_PathManager_GetPaths(AQHOME_PM_LIBNAME, pathName);
  if (sl) {
    int rv;
    GWEN_BUFFER *buf;

    buf=GWEN_Buffer_new(0, 256, 0, 1);
    rv=GWEN_Directory_FindFileInPaths(sl, sFilename, buf);
    GWEN_StringList_free(sl);
    if (rv==0)
      return buf;
    GWEN_Buffer_free(buf);
  }
  return NULL;
}



int AQH_ReadDataFromDouble(int dataType, double d, uint16_t *pDataVal, uint16_t *pDataDenom)
{
  switch(dataType) {
  case AQH_ValueDataType_Uint8:    return _readUint8DataFromDouble(d, pDataVal, pDataDenom);
  case AQH_ValueDataType_Int:
  case AQH_ValueDataType_Uint16:   return _readUint16DataFromDouble(d, pDataVal, pDataDenom);
  case AQH_ValueDataType_Uint32:   return _readUint32DataFromDouble(d, pDataVal, pDataDenom);
  case AQH_ValueDataType_Rational: break;
  default:                         break;
  }

  return GWEN_ERROR_INVALID;
}



int _readUint8DataFromDouble(double d, uint16_t *pDataVal, uint16_t *pDataDenom)
{
  uint8_t v;

  v=((uint8_t) (d)) & 0xff;
  *pDataVal=v & 0xff;
  *pDataDenom=1;
  return 0;
}



int _readUint16DataFromDouble(double d, uint16_t *pDataVal, uint16_t *pDataDenom)
{
  uint16_t v;

  v=((uint16_t) d) & 0xffff;
  *pDataVal=v;
  *pDataDenom=1;
  return 0;
}



int _readUint32DataFromDouble(double d, uint16_t *pDataVal, uint16_t *pDataDenom)
{
  uint32_t v;

  v=((uint32_t) d) & 0xffffffff;

  *pDataVal=(v>>16) & 0xffff;
  *pDataDenom=v & 0xffff;
  return 0;
}



int AQH_ReadDataFromString(int dataType, const char *s, uint16_t *pDataVal, uint16_t *pDataDenom)
{
  if (s && *s) {
    //DBG_ERROR(NULL, "Reading \"%s\" as datatype %d (%s)", s?s:"<NULL>", dataType, AQH_ValueDataType_toString(dataType));
    switch(dataType) {
    case AQH_ValueDataType_Uint8:    return _readUint8DataFromString(s, pDataVal, pDataDenom);
    case AQH_ValueDataType_Int:
    case AQH_ValueDataType_Uint16:   return _readUint16DataFromString(s, pDataVal, pDataDenom);
    case AQH_ValueDataType_Uint32:   return _readUint32DataFromString(s, pDataVal, pDataDenom);
    case AQH_ValueDataType_Rational: break;
    default:                         break;
    }
  }

  return GWEN_ERROR_INVALID;
}



int _readUint8DataFromString(const char *s, uint16_t *pDataVal, uint16_t *pDataDenom)
{
  int v=0;

  if (1==sscanf(s, "%i", &v)) {
    *pDataVal=v & 0xff;
    *pDataDenom=1;
    return 0;
  }
  return GWEN_ERROR_INVALID;
}



int _readUint16DataFromString(const char *s, uint16_t *pDataVal, uint16_t *pDataDenom)
{
  int v=0;

  if (1==sscanf(s, "%i", &v)) {
    *pDataVal=v & 0xffff;
    *pDataDenom=1;
    return 0;
  }
  return GWEN_ERROR_INVALID;
}



int _readUint32DataFromString(const char *s, uint16_t *pDataVal, uint16_t *pDataDenom)
{
  long int v=0;

  if (1==sscanf(s, "%li", &v)) {
    *pDataVal=(v>>16) & 0xffff;
    *pDataDenom=v & 0xffff;
    return 0;
  }
  return GWEN_ERROR_INVALID;
}




