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

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

#include <ctype.h>



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

const uint32_t _textFlags=
  GWEN_TEXT_FLAGS_DEL_LEADING_BLANKS |
  GWEN_TEXT_FLAGS_DEL_TRAILING_BLANKS |
  GWEN_TEXT_FLAGS_DEL_QUOTES;



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

static const char *_readLine(AQH_VARS **pVars, const char *src, GWEN_BUFFER *wbuf);
static const char *_readGroupOrVar(AQH_VARS **pVars, const char *src, GWEN_BUFFER *wbuf);
static const char *_contAsTypedVar(AQH_VARS **pVars, const char *src, const char *tname, GWEN_BUFFER *wbuf);
static const char *_contAsVar(AQH_VARS **pVars, AQH_VARS_DATATYPE dataType, const char *src, const char *vname, GWEN_BUFFER *wbuf);
static const char *_readValue(AQH_VARS *vtVar, AQH_VARS_DATATYPE dataType, const char *s, GWEN_BUFFER *wbuf);
static AQH_VARS *_mkStringValue(const char *s);
static AQH_VARS *_mkIntValue(const char *s);
static AQH_VARS *_mkDoubleValue(const char *s);
static const char *_getWordToBuffer(const char *src, const char *delims, GWEN_BUFFER *buf, uint32_t flags);
static const char *_skipBlanks(const char *src, const char *delims);
static const char *_readEscapeSequence(const char *src, GWEN_BUFFER *wbuf);



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


AQH_VARS *AQH_Vars_ReadDbFormat(const char *src)
{
  AQH_VARS *vtRoot;
  AQH_VARS *vt;
  GWEN_BUFFER *wbuf;
  const char *s;

  vtRoot=AQH_Vars_CreateGroup("root");
  vt=vtRoot;
  wbuf=GWEN_Buffer_new(0, 64, 0, 1);
  s=src;
  while(*s) {
    GWEN_Buffer_Reset(wbuf);
    s=_readLine(&vt, s, wbuf);
    if (s==NULL) {
      DBG_INFO(AQH_LOGDOMAIN, "here");
      GWEN_Buffer_free(wbuf);
      AQH_Vars_free(vtRoot);
      return NULL;
    }
  }
  GWEN_Buffer_free(wbuf);

  if (vt!=vtRoot) {
    DBG_ERROR(AQH_LOGDOMAIN, "Incomplete groups read");
    AQH_Vars_free(vtRoot);
    return NULL;
  }

  return vtRoot;
}



const char *_readLine(AQH_VARS **pVars, const char *src, GWEN_BUFFER *wbuf)
{
  const char *s;

  s=src;
  while(*s && isblank(*s))
    s++;

  if (*s=='}') {
    AQH_VARS *vt;

    /* current group ends */
    vt=AQH_Vars_Tree2_GetParent(*pVars);
    *pVars=vt;
    s++;
  }
  else if (*s=='#') {
    /* rest of line is a comment */
    s++;
    while(*s && !(*s==10 || *s==13))
      s++;
  }
  else if (*s==10 || *s==13) {
    /* end of line, so empty line */
  }
  else {
    s=_readGroupOrVar(pVars, s, wbuf);
    if (s==NULL) {
      DBG_INFO(AQH_LOGDOMAIN, "here");
      return NULL;
    }
  }

  /* handle end of line */
  while(*s && isblank(*s))
    s++;
  while(*s==10 || *s==13)
    s++;

  return s;
}



const char *_readGroupOrVar(AQH_VARS **pVars, const char *src, GWEN_BUFFER *wbuf)
{
  const char *s;

  GWEN_Buffer_Reset(wbuf);
  s=_getWordToBuffer(src, " ={#\t\r\n", wbuf, _textFlags);
  if (s==NULL) {
    DBG_INFO(AQH_LOGDOMAIN, "here");
    return NULL;
  }

  while(*s && isblank(*s))
    s++;

  if (*s=='{') {
    AQH_VARS *vt;

    /* read group */
    vt=AQH_Vars_CreateGroup(GWEN_Buffer_GetStart(wbuf));
    AQH_Vars_Tree2_AddChild(*pVars, vt);
    *pVars=vt;
    s++;
  }
  else if (*s=='=') {
    /* read untyped var, assume string */
    s=_contAsVar(pVars, AQH_Vars_DataType_ValueString, s, GWEN_Buffer_GetStart(wbuf), wbuf);
    if (s==NULL) {
      DBG_INFO(AQH_LOGDOMAIN, "here");
      return NULL;
    }
  }
  else if (*s=='#' || *s=='\r' || *s=='\n') {
    /* line logically or physically ends after first word, syntax error */
    DBG_ERROR(AQH_LOGDOMAIN, "Unexpected end of line");
    return NULL;
  }
  else {
    s=_contAsTypedVar(pVars, s, GWEN_Buffer_GetStart(wbuf), wbuf);
    if (s==NULL) {
      DBG_INFO(AQH_LOGDOMAIN, "here");
      return NULL;
    }
  }
  return s;
}



const char *_contAsTypedVar(AQH_VARS **pVars, const char *src, const char *tname, GWEN_BUFFER *wbuf)
{
  AQH_VARS_DATATYPE dataType;
  const char *s;

  /* got type, convert to type */
  dataType=AQH_Vars_DataTypeFromString(tname);
  if (dataType<AQH_Vars_DataType_ValueString || dataType>AQH_Vars_DataType_ValueDouble) {
    DBG_ERROR(AQH_LOGDOMAIN, "Invalid type \"%s\"", tname);
    return NULL;
  }

  /* read name */
  GWEN_Buffer_Reset(wbuf);
  s=_getWordToBuffer(src, " \t={#\r\n", wbuf, _textFlags);
  if (s==NULL) {
    DBG_INFO(AQH_LOGDOMAIN, "here");
    return NULL;
  }

  while(*s && isblank(*s))
    s++;

  s=_contAsVar(pVars, dataType, s, GWEN_Buffer_GetStart(wbuf), wbuf);
  if (s==NULL) {
    DBG_INFO(AQH_LOGDOMAIN, "here");
    return NULL;
  }
  return s;
}



const char *_contAsVar(AQH_VARS **pVars, AQH_VARS_DATATYPE dataType, const char *src, const char *vname, GWEN_BUFFER *wbuf)
{
  const char *s;
  AQH_VARS *vtVar;

  s=src;
  if (*s!='=') {
    DBG_ERROR(AQH_LOGDOMAIN, "Expected \"=\"");
    return NULL;
  }
  s++;

  vtVar=AQH_Vars_CreateVariable(vname);
  AQH_Vars_Tree2_AddChild(*pVars, vtVar);

  while(*s) {
    GWEN_Buffer_Reset(wbuf);
    s=_readValue(vtVar, dataType, s, wbuf);
    if (s==NULL) {
      DBG_INFO(AQH_LOGDOMAIN, "here");
      return NULL;
    }

    while(*s && isblank(*s))
      s++;
    if (*s==';') {
      s++;
      break;
    }
    if (*s!=',')
      break;
    s++;
  }
  return s;
}



const char *_readValue(AQH_VARS *vtVar, AQH_VARS_DATATYPE dataType, const char *s, GWEN_BUFFER *wbuf)
{
  AQH_VARS *vtValue;

  s=_getWordToBuffer(s, " \t,;#\r\n", wbuf, _textFlags);
  if (s==NULL) {
    DBG_INFO(AQH_LOGDOMAIN, "here");
    return NULL;
  }

  switch(dataType) {
    case AQH_Vars_DataType_ValueString: vtValue=_mkStringValue(GWEN_Buffer_GetStart(wbuf)); break;
    case AQH_Vars_DataType_ValueInt:    vtValue=_mkIntValue(GWEN_Buffer_GetStart(wbuf));    break;
    case AQH_Vars_DataType_ValueDouble: vtValue=_mkDoubleValue(GWEN_Buffer_GetStart(wbuf)); break;
    default: vtValue=NULL; break;
  }
  if (vtValue==NULL) {
    DBG_INFO(AQH_LOGDOMAIN, "here");
    return NULL;
  }
  AQH_Vars_Tree2_AddChild(vtVar, vtValue);
  return s;
}


// @TODO: unescape (write own GWEN_Text_GetWordToBuffer here)
AQH_VARS *_mkStringValue(const char *s)
{
  return AQH_Vars_CreateStringValue(strdup(s));
}



AQH_VARS *_mkIntValue(const char *s)
{
  int v;

  if (1!=sscanf(s, "%i", &v)) {
    DBG_ERROR(AQH_LOGDOMAIN, "Not an int value [%s]", s);
    return NULL;
  }
  return AQH_Vars_CreateIntValue(v);
}



AQH_VARS *_mkDoubleValue(const char *s)
{
  int rv;
  double v;

  rv=GWEN_Text_StringToDouble(s, &v);
  if (rv<0) {
    DBG_ERROR(AQH_LOGDOMAIN, "Not a double value [%s] (%d)", s, rv);
    return NULL;
  }
  return AQH_Vars_CreateDoubleValue(v);
}




const char *_getWordToBuffer(const char *src, const char *delims, GWEN_BUFFER *buf, uint32_t flags)
{

  /* skip leading blanks, if wanted */
  if (flags & GWEN_TEXT_FLAGS_DEL_LEADING_BLANKS)
    src=_skipBlanks(src, delims);

  if (*src=='"') {
    /* inside brackets */
    if (flags & GWEN_TEXT_FLAGS_DEL_QUOTES)
      src++;
    while(*src && strchr(delims, *src)==NULL) {
      unsigned char x;

      x=(unsigned char)*src;
      if (x=='"') {
        if (flags & GWEN_TEXT_FLAGS_DEL_QUOTES)
          src++;
        return src;
      }
      else {
        if (x=='%') {
          src=_readEscapeSequence(src, buf);
          if (src==NULL) {
            DBG_INFO(AQH_LOGDOMAIN, "here");
            return NULL;
          }
        }
        else {
          GWEN_Buffer_AppendByte(buf, x);
          src++;
        }
      }
    }
  }
  else {
    while(*src && strchr(delims, *src)==NULL) {
      unsigned char x;

      x=*src;
      if (x=='%') {
        src=_readEscapeSequence(src, buf);
        if (src==NULL) {
          DBG_INFO(AQH_LOGDOMAIN, "here");
          return NULL;
        }
      }
      else {
        GWEN_Buffer_AppendByte(buf, x);
        src++;
      }
      if ((flags & GWEN_TEXT_FLAGS_DEL_MULTIPLE_BLANKS) && isblank(x))
        src=_skipBlanks(src, delims);
    }
  }

  return src;
}



const char *_skipBlanks(const char *src, const char *delims)
{
  while (*src && isblank(*src)) {
    if (strchr(delims, *src)) {
      return src; /* empty buffer */
    }
    src++;
  }
  return src;
}



const char *_readEscapeSequence(const char *src, GWEN_BUFFER *wbuf)
{
  if (src[1] && src[2]) {
    unsigned char d1, d2;
    unsigned char c;
  
    src++; /* skip "%" */

    if (!(*src) || !isxdigit((int)*src)) {
      DBG_ERROR(GWEN_LOGDOMAIN, "Incomplete escape sequence (no digits)");
      return NULL;
    }
    d1=(unsigned char)(toupper(*src));
    src++;

    if (!(*src) || !isxdigit((int)*src)) {
      DBG_ERROR(GWEN_LOGDOMAIN, "Incomplete escape sequence (only 1 digit)");
      return NULL;
    }
    d2=(unsigned char)(toupper(*src));
    src++;
  
    d1-='0';
    if (d1>9)
      d1-=7;

    d2-='0';
    if (d2>9)
      d2-=7;
    c=((d1<<4) & 0xf0) | (d2 & 0x0f);
    GWEN_Buffer_AppendByte(wbuf, c);
    return src;
  }
  else {
    DBG_ERROR(AQH_LOGDOMAIN, "Incomplete escape sequence");
    return NULL;
  }
}


//if ((x>='A' && x<='Z') || (x>='a' && x<='z') || (x>='0' && x<='9') || strchr(_acceptableChars, x))



#include "./vars_dbread-t.c"

