/****************************************************************************
 * 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 "./vars_p.h"
#include "aqhome/data/path.h"

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



GWEN_TREE2_FUNCTIONS(AQH_VARS, AQH_Vars);


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

static AQH_VARS *_newData(void);
static void _releaseData(AQH_VARS *vt);
static AQH_VARS *_newStringElement(char *s, AQH_VARS_DATATYPE dt);
static AQH_VARS *_getValueNodeByIdx(const AQH_VARS *vt, int idx);
static AQH_VARS *_getPath(AQH_VARS *vt, const char *s, uint32_t flags);
static void *_pathHandlerFn(const char *entry, void *data, int idx, uint32_t flags);
static AQH_VARS *_getNodeByTypeAndName(const AQH_VARS *vt, AQH_VARS_DATATYPE dt, const char *name, int idx);
static void _dumpElement(const AQH_VARS *vt, int indent, GWEN_BUFFER *dbuf);
static int GWENHYWFAR_CB _replaceVarsCb(void *cbPtr, const char *name, int index, int maxLen, GWEN_BUFFER *dstBuf);



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

AQH_VARS *_newData(void)
{
  AQH_VARS *vt;

  GWEN_NEW_OBJECT(AQH_VARS, vt);
  GWEN_TREE2_INIT(AQH_VARS, vt, AQH_Vars);
  return vt;
}



AQH_VARS *_newStringElement(char *s, AQH_VARS_DATATYPE dt)
{
  AQH_VARS *vt;

  vt=_newData();
  vt->data.dataString=s;
  vt->dataType=dt;

  return vt;
}




void _releaseData(AQH_VARS *vt)
{
  if (vt) {
    switch(vt->dataType) {
    case AQH_Vars_DataType_Group:
    case AQH_Vars_DataType_Variable:
    case AQH_Vars_DataType_ValueString:
      free(vt->data.dataString);
      vt->data.dataString=NULL;
      break;
    default:
      break;
    }
    vt->dataType=AQH_Vars_DataType_Unknown;
  }
}



AQH_VARS *_dupData(const AQH_VARS *vt)
{
  if (vt) {
    AQH_VARS *vtCopy;

    vtCopy=_newData();
    vtCopy->dataType=vt->dataType;
    switch(vt->dataType) {
      case AQH_Vars_DataType_Unknown:
        break;
      case AQH_Vars_DataType_Group:
      case AQH_Vars_DataType_Variable:
      case AQH_Vars_DataType_ValueString:
        vtCopy->data.dataString=(vt->data.dataString)?strdup(vt->data.dataString):NULL;
        break;
      case AQH_Vars_DataType_ValueInt:
        vtCopy->data.dataInt=vt->data.dataInt;
        break;
      case AQH_Vars_DataType_ValueDouble:
        vtCopy->data.dataDouble=vt->data.dataDouble;
        break;
    }

    return vtCopy;
  }
  return NULL;
}



void AQH_Vars_free(AQH_VARS *vt)
{
  if (vt) {
    GWEN_TREE2_FINI(AQH_VARS, vt, AQH_Vars);
    _releaseData(vt);
    GWEN_FREE_OBJECT(vt);
  }
}



AQH_VARS_DATATYPE AQH_Vars_DataTypeFromString(const char *s)
{
  if (s && *s) {
    if (strcasecmp(s, "group")==0)
      return AQH_Vars_DataType_Group;
    else if (strcasecmp(s, "variable")==0)
      return AQH_Vars_DataType_Variable;
    else if (strcasecmp(s, "char")==0)
      return AQH_Vars_DataType_ValueString;
    else if (strcasecmp(s, "int")==0)
      return AQH_Vars_DataType_ValueInt;
    else if (strcasecmp(s, "double")==0)
      return AQH_Vars_DataType_ValueDouble;
  }
  return AQH_Vars_DataType_Unknown;
}



const char *AQH_Vars_DataTypeToString(AQH_VARS_DATATYPE dt)
{
  switch(dt) {
    case AQH_Vars_DataType_Unknown:     break;
    case AQH_Vars_DataType_Group:       return "group";
    case AQH_Vars_DataType_Variable:    return "variable";
    case AQH_Vars_DataType_ValueString: return "char";
    case AQH_Vars_DataType_ValueInt:    return "int";
    case AQH_Vars_DataType_ValueDouble: return "double";
  }

  return "unknown";
}



AQH_VARS *AQH_Vars_dup(const AQH_VARS *vt)
{
  AQH_VARS *vtCopy;
  AQH_VARS *vtChild;

  vtCopy=_dupData(vt);
  vtChild=AQH_Vars_Tree2_GetFirstChild(vt);
  while(vtChild) {
    AQH_Vars_Tree2_AddChild(vtCopy, _dupData(vtChild));
    vtChild=AQH_Vars_Tree2_GetNext(vtChild);
  }

  return vtCopy;
}




AQH_VARS *AQH_Vars_CreateGroup(const char *s)
{
  return _newStringElement(s?strdup(s):NULL, AQH_Vars_DataType_Group);
}



AQH_VARS *AQH_Vars_CreateVariable(const char *s)
{
  return _newStringElement(s?strdup(s):NULL, AQH_Vars_DataType_Variable);
}



AQH_VARS *AQH_Vars_CreateStringValue(char *s)
{
  return _newStringElement(s, AQH_Vars_DataType_ValueString);
}



AQH_VARS *AQH_Vars_CreateIntValue(int d)
{
  AQH_VARS *vt;

  vt=_newData();
  vt->data.dataInt=d;
  vt->dataType=AQH_Vars_DataType_ValueInt;

  return vt;
}



AQH_VARS *AQH_Vars_CreateDoubleValue(double d)
{
  AQH_VARS *vt;

  vt=_newData();
  vt->data.dataDouble=d;
  vt->dataType=AQH_Vars_DataType_ValueDouble;

  return vt;
}



AQH_VARS_DATATYPE AQH_Vars_GetDataType(const AQH_VARS *vt)
{
  return vt?vt->dataType:AQH_Vars_DataType_Unknown;
}



uint32_t AQH_Vars_GetFlags(const AQH_VARS *vt)
{
  return vt?vt->flags:0;
}



const char *AQH_Vars_GetStringData(const AQH_VARS *vt, const char *defValue)
{
  if (vt) {
    switch(vt->dataType) {
    case AQH_Vars_DataType_Group:
    case AQH_Vars_DataType_Variable:
    case AQH_Vars_DataType_ValueString:
      return vt->data.dataString;
    case AQH_Vars_DataType_ValueInt:
      DBG_INFO(AQH_LOGDOMAIN, "Unexpected type INT: %d", vt->data.dataInt);
      break;
    case AQH_Vars_DataType_ValueDouble:
      DBG_INFO(AQH_LOGDOMAIN, "Unexpected type DOUBLE: %f", vt->data.dataDouble);
      break;
    default:
      DBG_INFO(AQH_LOGDOMAIN, "Unexpected type %d", vt->dataType);
      break;
    }
  }

  return defValue;
}



void AQH_Vars_SetStringData(AQH_VARS *vt, char *s)
{
  if (vt) {
    _releaseData(vt);
    vt->data.dataString=s;
    vt->dataType=AQH_Vars_DataType_ValueString;
  }
}



int AQH_Vars_GetIntData(const AQH_VARS *vt, int defValue)
{
  if (vt && vt->dataType==AQH_Vars_DataType_ValueInt)
    return vt->data.dataInt;

  return defValue;
}



void AQH_Vars_SetIntData(AQH_VARS *vt, int d)
{
  if (vt) {
    _releaseData(vt);
    vt->data.dataInt=d;
    vt->dataType=AQH_Vars_DataType_ValueInt;
  }
}



double AQH_Vars_GetDoubleData(const AQH_VARS *vt, double defValue)
{
  if (vt && vt->dataType==AQH_Vars_DataType_ValueDouble)
    return vt->data.dataDouble;

  return defValue;
}



void AQH_Vars_SetDoubleData(AQH_VARS *vt, double d)
{
  if (vt) {
    _releaseData(vt);
    vt->data.dataDouble=d;
    vt->dataType=AQH_Vars_DataType_ValueDouble;
  }
}



AQH_VARS *AQH_Vars_GetFirstChildByType(const AQH_VARS *vt, AQH_VARS_DATATYPE dt)
{
  if (vt) {
    AQH_VARS *vtChild;

    vtChild=AQH_Vars_Tree2_GetFirstChild(vt);
    while(vtChild) {
      if (vtChild->dataType==dt)
        return vtChild;
      vtChild=AQH_Vars_Tree2_GetNext(vtChild);
    }
  }

  return NULL;
}



AQH_VARS *AQH_Vars_GetNextByType(const AQH_VARS *vt, AQH_VARS_DATATYPE dt)
{
  if (vt) {
    AQH_VARS *vtNext;

    vtNext=AQH_Vars_Tree2_GetNext(vt);
    while(vtNext) {
      if (vtNext->dataType==dt)
        return vtNext;
      vtNext=AQH_Vars_Tree2_GetNext(vtNext);
    }
  }

  return NULL;
}



AQH_VARS *AQH_Vars_GetGroup(AQH_VARS *vt, const char *path, uint32_t flags)
{
  return vt?_getPath(vt, path, (flags & ~AQH_PATH_FLAGS_VARIABLE)):NULL;
}



int AQH_Vars_SetCharValue(AQH_VARS *vt, uint32_t flags, const char *path, const char *value)
{
  if (vt) {
    AQH_VARS *vtChild;
    AQH_VARS *vtValue;

    vtChild=_getPath(vt, path, flags | AQH_PATH_FLAGS_VARIABLE);
    if (vtChild==NULL) {
      DBG_ERROR(AQH_LOGDOMAIN, "Error getting path for var \"%s\"", path);
      return GWEN_ERROR_GENERIC;
    }
    vtValue=AQH_Vars_CreateStringValue(value?strdup(value):NULL);
    AQH_Vars_Tree2_AddChild(vtChild, vtValue);
    return 0;
  }
  return GWEN_ERROR_INVALID;
}



const char *AQH_Vars_GetCharValue(AQH_VARS *vt, const char *path, int idx, const char *defaultValue)
{
  if (vt && idx>=0) {
    AQH_VARS *vtChild;
    AQH_VARS *vtValue;

    vtChild=_getPath(vt, path, AQH_PATH_FLAGS_PATHMUSTEXIST | AQH_PATH_FLAGS_VARIABLE);
    if (vtChild==NULL)
      return defaultValue;
    vtValue=_getValueNodeByIdx(vtChild, idx);
    if (vtValue)
      return AQH_Vars_GetStringData(vtValue, defaultValue);
  }
  return defaultValue;
}



int AQH_Vars_SetIntValue(AQH_VARS *vt, uint32_t flags, const char *path, int value)
{
  if (vt) {
    AQH_VARS *vtChild;
    AQH_VARS *vtValue;

    vtChild=_getPath(vt, path, flags | AQH_PATH_FLAGS_VARIABLE);
    if (vtChild==NULL) {
      DBG_ERROR(AQH_LOGDOMAIN, "Error getting path for var \"%s\"", path);
      return GWEN_ERROR_GENERIC;
    }
    vtValue=AQH_Vars_CreateIntValue(value);
    AQH_Vars_Tree2_AddChild(vtChild, vtValue);
    return 0;
  }
  return GWEN_ERROR_INVALID;
}



int AQH_Vars_GetIntValue(AQH_VARS *vt, const char *path, int idx, int defaultValue)
{
  if (vt && idx>=0) {
    AQH_VARS *vtChild;
    AQH_VARS *vtValue;

    vtChild=_getPath(vt, path, AQH_PATH_FLAGS_PATHMUSTEXIST | AQH_PATH_FLAGS_VARIABLE);
    if (vtChild==NULL)
      return defaultValue;

    vtValue=_getValueNodeByIdx(vtChild, idx);
    if (vtValue)
      return AQH_Vars_GetIntData(vtValue, defaultValue);
  }
  return defaultValue;
}



int AQH_Vars_SetDoubleValue(AQH_VARS *vt, uint32_t flags, const char *path, double value)
{
  if (vt) {
    AQH_VARS *vtChild;
    AQH_VARS *vtValue;

    vtChild=_getPath(vt, path, flags | AQH_PATH_FLAGS_VARIABLE);
    if (vtChild==NULL) {
      DBG_ERROR(AQH_LOGDOMAIN, "Error getting path for var \"%s\"", path);
      return GWEN_ERROR_GENERIC;
    }
    vtValue=AQH_Vars_CreateDoubleValue(value);
    AQH_Vars_Tree2_AddChild(vtChild, vtValue);
    return 0;
  }
  return GWEN_ERROR_INVALID;
}



double AQH_Vars_GetDoubleValue(AQH_VARS *vt, const char *path, int idx, double defaultValue)
{
  if (vt && idx>=0) {
    AQH_VARS *vtChild;
    AQH_VARS *vtValue;

    vtChild=_getPath(vt, path, AQH_PATH_FLAGS_PATHMUSTEXIST | AQH_PATH_FLAGS_VARIABLE);
    if (vtChild==NULL)
      return defaultValue;
    vtValue=_getValueNodeByIdx(vtChild, idx);
    if (vtValue)
      return AQH_Vars_GetDoubleData(vtValue, defaultValue);
  }
  return defaultValue;
}



AQH_VARS *_getValueNodeByIdx(const AQH_VARS *vt, int idx)
{
  if (vt && idx>=0) {
    AQH_VARS *vtValue;

    vtValue=AQH_Vars_Tree2_GetFirstChild(vt);
    while(vtValue && idx) {
      idx--;
      vtValue=AQH_Vars_Tree2_GetNext(vtValue);
    }
    return vtValue;
  }

  return NULL;
}



AQH_VARS *_getPath(AQH_VARS *vt, const char *s, uint32_t flags)
{
  return (AQH_VARS *)AQH_Path_Handle(s, (void*) vt, flags, "/", _pathHandlerFn);
}



void *_pathHandlerFn(const char *entry, void *data, int idx, uint32_t flags)
{
  AQH_VARS *vt;
  AQH_VARS *vtChild;
  AQH_VARS_DATATYPE dt;

  vt=(AQH_VARS*) data;
  dt=((flags & AQH_PATH_FLAGS_LAST) && (flags & AQH_PATH_FLAGS_VARIABLE))?AQH_Vars_DataType_Variable:AQH_Vars_DataType_Group;

  if (
      !(
        (flags & AQH_PATH_FLAGS_PATHCREATE) ||
        ((flags & AQH_PATH_FLAGS_LAST) && (flags & AQH_PATH_FLAGS_LASTCREATE))
       )
     ) {
    /* look it up */
    vtChild=_getNodeByTypeAndName(vt, dt, entry, idx);
    if (vtChild) {
      /* already exists, should it? */
      if (
          (flags & AQH_PATH_FLAGS_PATHMUSTNOTEXIST) ||
          ((flags & AQH_PATH_FLAGS_LAST) && (flags & AQH_PATH_FLAGS_LASTMUSTNOTEXIST))
         ) {
        DBG_INFO(AQH_LOGDOMAIN, "Element \"%s[%d]\" exists when it should not", entry, idx);
        return NULL;
      }
      /* already exists, should we overwrite it (i.e. remove children)? */
      if (
          (flags & AQH_PATH_FLAGS_LAST) &&
          (
           ((dt==AQH_Vars_DataType_Group) && (flags & AQH_VARS_PATHFLAGS_OVERWRITE_GROUPS)) ||
           ((dt==AQH_Vars_DataType_Variable) && (flags & AQH_VARS_PATHFLAGS_OVERWRITE_VARS))
          )
         )
        AQH_Vars_Tree2_ClearChildren(vtChild);
      return vtChild;
    }
    else {
      /* does not exist, should it? */
      if (flags & AQH_PATH_FLAGS_PATHMUSTEXIST) {
        DBG_INFO(AQH_LOGDOMAIN, "Element \"%s[%d]\" does not exist when it should", entry, idx);
        return NULL;
      }
    }
  }

  /* create new string element (either group or var) */
  vtChild=_newStringElement(strdup(entry), dt);
  AQH_Vars_Tree2_AddChild(vt, vtChild);
  return vtChild;
}



AQH_VARS *_getNodeByTypeAndName(const AQH_VARS *vt, AQH_VARS_DATATYPE dt, const char *name, int idx)
{
  AQH_VARS *vtChild=NULL;

  if (idx>=0) {
    /* look it up */
    vtChild=AQH_Vars_GetFirstChildByType(vt, dt);
    while(vtChild) {
      if (vtChild->data.dataString && strcasecmp(vtChild->data.dataString, name)==0) {
        if (idx==0)
          break;
        else
          idx--;
      }
      vtChild=AQH_Vars_GetNextByType(vtChild, dt);
    }
  }

  return vtChild;
}



void AQH_Vars_Dump(const AQH_VARS *vt, int indent)
{
  GWEN_BUFFER *dbuf;

  dbuf=GWEN_Buffer_new(0, 256, 0, 1);
  _dumpElement(vt, indent, dbuf);
  fprintf(stderr, "%s\n", GWEN_Buffer_GetStart(dbuf));
  GWEN_Buffer_free(dbuf);
}



void _dumpElement(const AQH_VARS *vt, int indent, GWEN_BUFFER *dbuf)
{
  const AQH_VARS *vtChild;

  if (indent)
    GWEN_Buffer_FillWithBytes(dbuf, ' ', indent);

  switch(vt->dataType) {
    case AQH_Vars_DataType_Unknown:
      GWEN_Buffer_AppendString(dbuf, "unknown\n");
      break;
    case AQH_Vars_DataType_Group:
      GWEN_Buffer_AppendArgs(dbuf, "group..\"%s\"\n", (vt->data.dataString)?vt->data.dataString:"<no name>");
      break;
    case AQH_Vars_DataType_Variable:
      GWEN_Buffer_AppendArgs(dbuf, "var....\"%s\"\n", (vt->data.dataString)?vt->data.dataString:"<no name>");
      break;
    case AQH_Vars_DataType_ValueString:
      GWEN_Buffer_AppendArgs(dbuf, "char...\"%s\"\n", (vt->data.dataString)?vt->data.dataString:"<no data>");
      break;
    case AQH_Vars_DataType_ValueInt:
      GWEN_Buffer_AppendArgs(dbuf, "int....\"%d\"\n", vt->data.dataInt);
      break;
    case AQH_Vars_DataType_ValueDouble:
      GWEN_Buffer_AppendArgs(dbuf, "double.\"%f\"\n", vt->data.dataDouble);
      break;
  }

  vtChild=AQH_Vars_Tree2_GetFirstChild(vt);
  while(vtChild) {
    _dumpElement(vtChild, indent+2, dbuf);
    vtChild=AQH_Vars_Tree2_GetNext(vtChild);
  }
}



int AQH_Vars_ReplaceVars(AQH_VARS *vars, const char *s, GWEN_BUFFER *dbuf)
{
  int rv;

  rv=GWEN_Text_ReplaceVars(s, dbuf, _replaceVarsCb, vars);
  if (rv<0) {
    DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
    return rv;
  }
  return 0;
}



int GWENHYWFAR_CB _replaceVarsCb(void *cbPtr, const char *name, int index, GWEN_UNUSED int maxLen, GWEN_BUFFER *dstBuf)
{
  AQH_VARS *vars;
  AQH_VARS *vtVariable;

  vars=(AQH_VARS*) cbPtr;
  vtVariable=_getPath(vars, name, AQH_PATH_FLAGS_PATHMUSTEXIST | AQH_PATH_FLAGS_VARIABLE);
  if (vtVariable) {
    AQH_VARS *vtValue;

    vtValue=_getValueNodeByIdx(vtVariable, index);
    if (vtValue) {
      switch(vtValue->dataType) {
        case AQH_Vars_DataType_ValueString: GWEN_Buffer_AppendArgs(dstBuf, "%s", vtValue->data.dataString); return 0;
        case AQH_Vars_DataType_ValueInt:    GWEN_Buffer_AppendArgs(dstBuf, "%d", vtValue->data.dataInt);    return 0;
        case AQH_Vars_DataType_ValueDouble: GWEN_Buffer_AppendArgs(dstBuf, "%f", vtValue->data.dataDouble); return 0;
        case AQH_Vars_DataType_Unknown:
        case AQH_Vars_DataType_Group:
        case AQH_Vars_DataType_Variable:
          break;
      }
      DBG_ERROR(AQH_LOGDOMAIN, "Unexpected data type \"%d\" (%s)", vtValue->dataType, AQH_Vars_DataTypeToString(vtValue->dataType));
      return GWEN_ERROR_INVALID;
    }
  }

  return 0;
}




#include "./vars-t.c"

