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

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

#include <string.h>




static const char *_readRecord(AQH_HEXFILERECORD *hr, const char *s);
static void _writeRecord(AQH_HEXFILERECORD *hr, GWEN_BUFFER *buffer);
static int _sumValuesForChecksum(const AQH_HEXFILERECORD *hr);
static int _readWord(const char *s); /* big endian */
static int _readByte(const char *s);
static int _readNibble(const char *s);





AQH_HEXFILE *AQH_Hexfile_new()
{
  AQH_HEXFILE *h;

  GWEN_NEW_OBJECT(AQH_HEXFILE, h);
  h->recordList=AQH_HexfileRecord_List_new();
  return h;
}



AQH_HEXFILE *AQH_Hexfile_fromString(const char *s)
{
  AQH_HEXFILE *h;
  AQH_HEXFILERECORD_LIST *recordList;

  h=AQH_Hexfile_new();
  recordList=AQH_Hexfile_GetRecordList(h);
  while(s && *s) {
    AQH_HEXFILERECORD *hr;
    uint8_t rt;

    while(s && *s && *s!=':')
      s++;
    hr=AQH_HexfileRecord_new();
    s=_readRecord(hr, s);
    if (s==NULL) {
      AQH_HexfileRecord_free(hr);
      AQH_Hexfile_free(h);
      return NULL;
    }
    rt=AQH_HexfileRecord_GetRecordType(hr);
    DBG_INFO(AQH_LOGDOMAIN, "Adding record %02x", rt);
    AQH_HexfileRecord_List_Add(hr, recordList);
    if ((rt==AQH_HEXFILERECORD_TYPE_EOF) ||
        (rt==AQH_HEXFILERECORD_TYPE_DATA && AQH_HexfileRecord_GetByteCount(hr)==0)) {
      DBG_INFO(AQH_LOGDOMAIN, "End record found");
      break;
    }
  }

  return h;
}



AQH_HEXFILE *AQH_Hexfile_fromFile(const char *filename)
{
  GWEN_BUFFER *buffer;
  int rv;
  AQH_HEXFILE *h;

  buffer=GWEN_Buffer_new(0, 1024, 0, 1);
  rv=GWEN_SyncIo_Helper_ReadFile(filename, buffer);
  if (rv<0) {
    DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
    GWEN_Buffer_free(buffer);
    return NULL;
  }

  h=AQH_Hexfile_fromString(GWEN_Buffer_GetStart(buffer));
  GWEN_Buffer_free(buffer);
  if (h==NULL) {
    DBG_INFO(AQH_LOGDOMAIN, "here");
  }

  return h;
}



void AQH_Hexfile_toBuffer(const AQH_HEXFILE *h, GWEN_BUFFER *buffer)
{
  if (h && h->recordList) {
    AQH_HEXFILERECORD *hr;

    hr=AQH_HexfileRecord_List_First(h->recordList);
    while(hr) {
      _writeRecord(hr, buffer);
      GWEN_Buffer_AppendString(buffer, "\r\n");
      hr=AQH_HexfileRecord_List_Next(hr);
    }
  }
}



void AQH_Hexfile_free(AQH_HEXFILE *h)
{
  if (h) {
    AQH_HexfileRecord_List_free(h->recordList);
    GWEN_FREE_OBJECT(h);
  }
}



AQH_HEXFILERECORD_LIST *AQH_Hexfile_GetRecordList(const AQH_HEXFILE *h)
{
  if (h)
    return h->recordList;
  return NULL;
}






const char *_readRecord(AQH_HEXFILERECORD *hr, const char *s)
{
  int v;
  int dataLen;

  /* find ":" */
  s=strchr(s, ':');
  if (s==NULL)
    return NULL;
  s++;

  v=_readByte(s);
  if (v<0)
    return NULL;
  AQH_HexfileRecord_SetByteCount(hr, (uint8_t) v);
  dataLen=v;
  s+=2;

  v=_readWord(s);
  if (v<0)
    return NULL;
  AQH_HexfileRecord_SetAddress(hr, (uint16_t) v);
  s+=4;

  v=_readByte(s);
  if (v<0)
    return NULL;
  AQH_HexfileRecord_SetRecordType(hr, (uint8_t) v);
  s+=2;

  if (dataLen) {
    uint8_t *ptr;
    int i;

    ptr=(uint8_t*) malloc(dataLen);
    if (ptr==NULL) {
      return NULL;
    }
    AQH_HexfileRecord_SetDataPointer(hr, ptr);

    for (i=0; i<dataLen; i++) {
      v=_readByte(s);
      if (v<0)
        return NULL;
      ptr[i]=(uint8_t) v;
      s+=2;
    }
  }

  /* checksum */
  v=_readByte(s);
  if (v<0)
    return NULL;
  s+=2;

  v+=_sumValuesForChecksum(hr);
  if (v & 0xff) {
    DBG_ERROR(AQH_LOGDOMAIN, "Invalid checksum (%d, %d)", (v & 0xff), -(v & 0xff));
    return NULL;
  }

  return s;
}



void _writeRecord(AQH_HEXFILERECORD *hr, GWEN_BUFFER *buffer)
{
  uint8_t len;
  int v;

  len=AQH_HexfileRecord_GetByteCount(hr);
  GWEN_Buffer_AppendArgs(buffer, ":%02X%04X%02X",
                         AQH_HexfileRecord_GetByteCount(hr),
                         AQH_HexfileRecord_GetAddress(hr),
                         AQH_HexfileRecord_GetRecordType(hr));
  if (len) {
    const uint8_t *ptr;
    int i;

    ptr=AQH_HexfileRecord_GetDataPointer(hr);
    for(i=0; i<len; i++)
      GWEN_Buffer_AppendArgs(buffer, "%02X", ptr[i]);
  }

  v=_sumValuesForChecksum(hr);
  GWEN_Buffer_AppendArgs(buffer, "%02X", (-v) & 0xff);
}



int _sumValuesForChecksum(const AQH_HEXFILERECORD *hr)
{
  int v;
  int dataLen;

  dataLen=AQH_HexfileRecord_GetByteCount(hr);
  v=AQH_HexfileRecord_GetByteCount(hr)+
    ((AQH_HexfileRecord_GetAddress(hr)>>8) & 0xff)+(AQH_HexfileRecord_GetAddress(hr)  & 0xff)+
    AQH_HexfileRecord_GetRecordType(hr);
  if (dataLen) {
    const uint8_t *ptr;
    int i;

    ptr=AQH_HexfileRecord_GetDataPointer(hr);
    for(i=0; i<dataLen; i++)
      v+=ptr[i];
  }

  return v;
}




int _readWord(const char *s)
{
  uint16_t result;
  int b;

  b=_readByte(s);
  if (b<0)
    return -1;
  result=(uint16_t)(b & 0xff);
  s+=2;

  b=_readByte(s++);
  if (b<0)
    return -1;
  result<<=8;
  result|=(uint16_t)(b & 0xff);
  return (int) result;
}



int _readByte(const char *s)
{
  uint8_t result;
  int c;

  c=_readNibble(s++);
  if (c<0)
    return -1;
  result=(uint8_t) (c & 0xf);
  c=_readNibble(s);
  if (c<0)
    return -1;
  result<<=4;
  result|=(uint8_t) (c & 0xf);
  return (int) result;
}



int _readNibble(const char *s)
{
  uint8_t c;

  c=*s;
  if (c==0)
    return -1;
  if (c>='0' && c<='9')
    c-='0';
  else if (c>='A' && c<='F')
    c-='A'-10;
  else if (c>='a' && c<='f')
    c-='a'-10;
  else
    return -1;
  return c;
}



