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

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

#include <ctype.h>
#include <stdio.h>
#include <errno.h>



/* ------------------------------------------------------------------------------------------------
 * definitions
 * ------------------------------------------------------------------------------------------------
 */

const char *_acceptableChars=",.:;-+/&";



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

static int _groupToBuffer(const AQH_VARS *vt, GWEN_BUFFER *dbuf, int indent);
static int _groupChildrenToBuffer(const AQH_VARS *vt, GWEN_BUFFER *dbuf, int indent);
static int _varToBuffer(const AQH_VARS *vt, GWEN_BUFFER *dbuf, int indent);
static void _appendEscapedString(const char *s, GWEN_BUFFER *dbuf);



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



int AQH_Vars_WriteDbFile(const AQH_VARS *vt, const char *filename)
{
  int rv;
  GWEN_BUFFER *fbuf;
  GWEN_BUFFER *dbuf;

  fbuf=GWEN_Buffer_new(0, 256, 0, 1);
  GWEN_Buffer_AppendString(fbuf, filename);
  GWEN_Buffer_AppendString(fbuf, ".tmp");

  dbuf=GWEN_Buffer_new(0, 256, 0, 1);
  rv=AQH_Vars_WriteDbFormat(vt, dbuf);
  if (rv<0) {
    DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
    GWEN_Buffer_free(dbuf);
    GWEN_Buffer_free(fbuf);
    return rv;
  }

  rv=GWEN_SyncIo_Helper_WriteFile(GWEN_Buffer_GetStart(fbuf),
                                  (const uint8_t*) GWEN_Buffer_GetStart(dbuf),
                                  GWEN_Buffer_GetUsedBytes(dbuf));
  if (rv<0) {
    DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
    GWEN_Buffer_free(dbuf);
    GWEN_Buffer_free(fbuf);
    return rv;
  }
  GWEN_Buffer_free(dbuf);

  if (rename(GWEN_Buffer_GetStart(fbuf), filename)) {
    DBG_INFO(AQH_LOGDOMAIN, "here (%d: %s)", errno, strerror(errno));
    GWEN_Buffer_free(fbuf);
    return GWEN_ERROR_IO;
  }
  GWEN_Buffer_free(fbuf);

  return 0;
}



int AQH_Vars_WriteDbFormat(const AQH_VARS *vt, GWEN_BUFFER *dbuf)
{
  if (vt && dbuf) {
    int rv;

    rv=_groupChildrenToBuffer(vt, dbuf, 0);
    if (rv<0) {
      DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
      return rv;
    }
  }
  return 0;
}



int _groupToBuffer(const AQH_VARS *vt, GWEN_BUFFER *dbuf, int indent)
{
  int rv;

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

  if (vt->data.dataString && *(vt->data.dataString)) {
    _appendEscapedString(vt->data.dataString, dbuf);
    GWEN_Buffer_AppendString(dbuf, " {\n");
  }
  else {
    DBG_ERROR(AQH_LOGDOMAIN, "Group has no name");
    return GWEN_ERROR_GENERIC;
  }

  rv=_groupChildrenToBuffer(vt, dbuf, indent+2);
  if (rv<0) {
    DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
    return rv;
  }
  if (indent)
    GWEN_Buffer_FillWithBytes(dbuf, ' ', indent);
  GWEN_Buffer_AppendString(dbuf, "}\n");
  return 0;
}



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

  vtChild=AQH_Vars_Tree2_GetFirstChild(vt);
  while(vtChild) {
    int rv;

    switch(vtChild->dataType) {
    case AQH_Vars_DataType_Group:    rv=_groupToBuffer(vtChild, dbuf, indent+2); break;
    case AQH_Vars_DataType_Variable: rv=_varToBuffer(vtChild, dbuf, indent+2); break;
    default:
      DBG_ERROR(AQH_LOGDOMAIN,
                "Unexpected data type \"%d\" (%s)",
                vtChild->dataType,
                AQH_Vars_DataTypeToString(vtChild->dataType));
      return GWEN_ERROR_INVALID;
    }

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

    vtChild=AQH_Vars_Tree2_GetNext(vtChild);
  }

  return 0;
}



int _varToBuffer(const AQH_VARS *vt, GWEN_BUFFER *dbuf, int indent)
{
  const AQH_VARS *vtChild;
  int isFirst=1;

  vtChild=AQH_Vars_Tree2_GetFirstChild(vt);
  if (vtChild) {
    if (vt->data.dataString && *(vt->data.dataString)) {
      if (indent)
        GWEN_Buffer_FillWithBytes(dbuf, ' ', indent);
      GWEN_Buffer_AppendArgs(dbuf, "%s ", AQH_Vars_DataTypeToString(vtChild->dataType));
      _appendEscapedString(vt->data.dataString, dbuf);
      GWEN_Buffer_AppendString(dbuf, "=");
    }
    else {
      DBG_ERROR(AQH_LOGDOMAIN, "Variable has no name");
      return GWEN_ERROR_GENERIC;
    }

    while(vtChild) {
      if (!isFirst) {
        GWEN_Buffer_AppendString(dbuf, ", ");
        isFirst=0;
      }

      switch(vtChild->dataType) {
      case AQH_Vars_DataType_ValueString:
        GWEN_Buffer_AppendByte(dbuf, '\"');
        _appendEscapedString(vtChild->data.dataString, dbuf);
        GWEN_Buffer_AppendByte(dbuf, '\"');
        break;
      case AQH_Vars_DataType_ValueInt:
        GWEN_Buffer_AppendArgs(dbuf, "\"%d\"", vtChild->data.dataInt);
        break;
      case AQH_Vars_DataType_ValueDouble:
        GWEN_Buffer_AppendArgs(dbuf, "\"%f\"", vtChild->data.dataDouble);
        break;
      default:
        DBG_ERROR(AQH_LOGDOMAIN, "Unexpected data type \"%d\" (%s)", vtChild->dataType, AQH_Vars_DataTypeToString(vtChild->dataType));
        return GWEN_ERROR_INVALID;
      }

      vtChild=AQH_Vars_Tree2_GetNext(vtChild);
    }
    GWEN_Buffer_AppendString(dbuf, "\n");
  } /* if vtChild */
  return 0;
}



void _appendEscapedString(const char *s, GWEN_BUFFER *dbuf)
{
  while(*s) {
    unsigned char x;

    x=*s;
    if ((x>='A' && x<='Z') || (x>='a' && x<='z') || (x>='0' && x<='9') || strchr(_acceptableChars, x))
      GWEN_Buffer_AppendByte(dbuf, x);
    else
      GWEN_Buffer_AppendArgs(dbuf, "%%%02x", x);

    s++;
  }
}





#include "./vars_dbwrite-t.c"

