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

#include "aqhome/msg/node/m_device.h"
#include "aqhome/msg/node/m_recvstats.h"
#include "aqhome/msg/node/m_sendstats.h"
#include "aqhome/msg/node/m_memstats.h"
#include "aqhome/msg/node/m_sysstats.h"
#include "aqhome/msg/node/m_addr.h"
#include "aqhome/msg/node/m_needaddr.h"
#include "aqhome/msg/node/m_ping.h"
#include "aqhome/msg/node/m_pong.h"
#include "aqhome/msg/node/m_heartbeat.h"
#include "aqhome/msg/node/m_reboot.h"
#include "aqhome/msg/node/m_value.h"
#include "aqhome/msg/node/m_flashstart.h"
#include "aqhome/msg/node/m_flashdata.h"
#include "aqhome/msg/node/m_flashend.h"
#include "aqhome/msg/node/m_flashready.h"
#include "aqhome/msg/node/m_flashresponse.h"
#include "aqhome/msg/node/m_range.h"
#include "aqhome/msg/node/m_time.h"

#include <gwenhywfar/text.h>



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

static uint8_t _calcCrc8Checksum(const uint8_t *ptr, uint8_t len);



/* ------------------------------------------------------------------------------------------------
 * implementation
 * ------------------------------------------------------------------------------------------------
 */

AQH_MESSAGE *AQH_NodeMessage_new(uint8_t destAddr, uint8_t srcAddr, uint8_t code, uint8_t payloadLen, const uint8_t *payload)
{
  AQH_MESSAGE *msg;
  uint8_t *ptr;
  uint32_t len;

  len=AQH_MSG_OFFS_ALL_DATA_BEGIN+payloadLen+1;       /* dest, len, code, src, payload, crc8 */
  msg=AQH_Message_new();
  AQH_Message_SetData(msg, NULL, len);                /* auto-malloc len bytes */

  ptr=AQH_Message_GetMsgPointer(msg);
  ptr[AQH_MSG_OFFS_ALL_DEST_ADDRESS]=destAddr & 0xff;
  ptr[AQH_MSG_OFFS_ALL_PAYLOAD_LEN]=payloadLen+2;     /* code, src, payload */
  ptr[AQH_MSG_OFFS_ALL_MSG_TYPE]=code;
  ptr[AQH_MSG_OFFS_ALL_SRC_ADDRESS]=srcAddr;

  if (payloadLen && payload)
    memmove(ptr+AQH_MSG_OFFS_ALL_DATA_BEGIN, payload, payloadLen);
  AQH_Message_SetUsedSize(msg, len-1);
  AQH_NodeMessage_AddChecksum(msg);

  return msg;
}



AQH_MESSAGE *AQH_NodeMessage_prepare(uint8_t destAddr, uint8_t srcAddr, uint8_t code, uint8_t payloadLen)
{
  AQH_MESSAGE *msg;
  uint8_t *ptr;
  uint32_t len;

  len=AQH_MSG_OFFS_ALL_DATA_BEGIN+payloadLen+1;       /* dest, len, code, src, payload, crc8 */
  msg=AQH_Message_new();
  AQH_Message_SetData(msg, NULL, len);                /* auto-malloc len bytes */

  ptr=AQH_Message_GetMsgPointer(msg);
  ptr[AQH_MSG_OFFS_ALL_DEST_ADDRESS]=destAddr & 0xff;
  ptr[AQH_MSG_OFFS_ALL_PAYLOAD_LEN]=payloadLen+2;     /* code, src, payload */
  ptr[AQH_MSG_OFFS_ALL_MSG_TYPE]=code;
  ptr[AQH_MSG_OFFS_ALL_SRC_ADDRESS]=srcAddr;

  AQH_Message_SetUsedSize(msg, len-1);

  return msg;
}



AQH_MESSAGE *AQH_NodeMessage_fromBuffer(const uint8_t *ptr, uint32_t len)
{
  if (ptr && len) {
    AQH_MESSAGE *msg;

    msg=AQH_Message_new();
    AQH_Message_SetData(msg, ptr, len);
    AQH_Message_SetUsedSize(msg, len);
    return msg;
  }
  return NULL;
}



uint8_t AQH_NodeMessage_GetDestAddress(const AQH_MESSAGE *msg)
{
  return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_DEST_ADDRESS, 0);
}



uint8_t AQH_NodeMessage_GetMsgType(const AQH_MESSAGE *msg)
{
  return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_MSG_TYPE, 0);
}



uint8_t AQH_NodeMessage_GetSourceAddress(const AQH_MESSAGE *msg)
{
  return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_SRC_ADDRESS, 0);
}



uint8_t AQH_NodeMessage_GetPayloadLength(const AQH_MESSAGE *msg)
{
  return AQH_Message_ReadUint8At(msg, AQH_MSG_OFFS_ALL_PAYLOAD_LEN, 2)-2; /* minus src addr, command */
}



uint8_t *AQH_NodeMessage_GetPayloadPointer(const AQH_MESSAGE *msg)
{
  return msg?(AQH_Message_GetMsgPointer(msg)+AQH_MSG_OFFS_ALL_DATA_BEGIN):NULL;
}



void AQH_NodeMessage_AddChecksum(AQH_MESSAGE *msg)
{
  if (msg) {
    uint8_t *ptr;
    uint32_t msgLenWithoutChecksum;
    uint8_t checksum;

    ptr=AQH_Message_GetMsgPointer(msg);
    msgLenWithoutChecksum=AQH_Message_GetUsedSize(msg);
    checksum=_calcCrc8Checksum(ptr, msgLenWithoutChecksum);
    AQH_Message_WriteUint8At(msg, msgLenWithoutChecksum, checksum);
    AQH_Message_IncUsedSize(msg, 1);
  }
}



int AQH_NodeMessage_IsValid(AQH_MESSAGE *msg)
{
  if (msg) {
    uint8_t *ptr;
    uint32_t len;
    uint8_t checksum;

    ptr=AQH_Message_GetMsgPointer(msg);
    len=AQH_Message_GetUsedSize(msg);
    checksum=_calcCrc8Checksum(ptr, len);
    return (checksum==0)?1:0;
  }
  return 0;
}



void AQH_NodeMessage_DumpToBuffer(const AQH_MESSAGE *msg, GWEN_BUFFER *dbuf, const char *sText)
{
  if (msg) {
    GWEN_Buffer_AppendArgs(dbuf,
                           "0x%02x->0x%02x: %s %d (\"%s\")\n",
                           AQH_NodeMessage_GetSourceAddress(msg),
                           AQH_NodeMessage_GetDestAddress(msg),
                           sText,
                           AQH_NodeMessage_GetMsgType(msg),
                           AQH_NodeMessage_MsgTypeToChar(AQH_NodeMessage_GetMsgType(msg)));
    GWEN_Text_DumpString2Buffer((const char*)AQH_Message_GetMsgPointer(msg), AQH_Message_GetUsedSize(msg), dbuf, 34);
  }
}



const char *AQH_NodeMessage_MsgTypeToChar(uint8_t i)
{
  switch(i) {
  case AQH_MSG_TYPE_PING:           return "Ping";
  case AQH_MSG_TYPE_PONG:           return "Pong";
  case AQH_MSG_TYPE_HEARTBEAT:      return "Heartbeat";
  case AQH_MSG_TYPE_COMSENDSTATS:   return "SendStats";
  case AQH_MSG_TYPE_COMRECVSTATS:   return "RecvStats";
  case AQH_MSG_TYPE_TWIBUSMEMBER:   return "TwiBusMember";
  case AQH_MSG_TYPE_DEBUG:          return "Debug";
  case AQH_MSG_TYPE_VALUE:          return "Value";
  case AQH_MSG_TYPE_VALUE2:         return "Value2";
  case AQH_MSG_TYPE_NEED_ADDRESS:   return "NeedAddress";
  case AQH_MSG_TYPE_HAVE_ADDRESS:   return "HaveAddress";
  case AQH_MSG_TYPE_CLAIM_ADDRESS:  return "ClaimAddress";
  case AQH_MSG_TYPE_DENY_ADDRESS:   return "DenyAddress";
  case AQH_MSG_TYPE_ADDRESS_RANGE:  return "Range";
  case AQH_MSG_TYPE_REENUM:         return "Reenum";

  case AQH_MSG_TYPE_FLASH_START:    return "FlashStart";
  case AQH_MSG_TYPE_FLASH_END:      return "FlashEnd";
  case AQH_MSG_TYPE_FLASH_READY:    return "FlashReady";
  case AQH_MSG_TYPE_FLASH_DATA:     return "FlashData";
  case AQH_MSG_TYPE_FLASH_RSP:      return "FlashResponse";

  case AQH_MSG_TYPE_DEVICE:         return "Device";
  case AQH_MSG_TYPE_MEMSTATS:       return "MemStats";
  case AQH_MSG_TYPE_SYSSTATS:       return "SysStats";
  case AQH_MSG_TYPE_REBOOT_REQ:     return "RebootRequest";
  case AQH_MSG_TYPE_REBOOT_RSP:     return "RebootResponse";
  case AQH_MSG_TYPE_VALUE_REPORT:   return "ValueReport";
  case AQH_MSG_TYPE_VALUE_SET:      return "ValueSet";
  case AQH_MSG_TYPE_VALUE_SET_ACK:  return "ValueSetAck";
  case AQH_MSG_TYPE_VALUE_SET_NACK: return "ValueSetNack";
  case AQH_MSG_TYPE_TIME_ANNOUNCE:  return "TimeAnnounce";
  case AQH_MSG_TYPE_TIME_REQSET:    return "TimeSetRequest";
  case AQH_MSG_TYPE_TIME_RSPSET:    return "TimeSetResponse";
  default:                          return "(unknown)";
  }
}



void AQH_NodeMessage_DumpSpecificToBuffer(const AQH_MESSAGE *msg, GWEN_BUFFER *dbuf, const char *sText)
{
  if (msg && dbuf) {
    switch(AQH_NodeMessage_GetMsgType(msg)) {
    case AQH_MSG_TYPE_PING:           AQH_PingMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_PONG:           AQH_PongMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_HEARTBEAT:      AQH_HeartbeatMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_COMSENDSTATS:   AQH_SendStatsMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_COMRECVSTATS:   AQH_RecvStatsMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_NEED_ADDRESS:   AQH_AddrMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_HAVE_ADDRESS:   AQH_AddrMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_CLAIM_ADDRESS:  AQH_AddrMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_DENY_ADDRESS:   AQH_AddrMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_ADDRESS_RANGE:  AQH_RangeMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_REENUM:         AQH_RangeMessage_DumpToBuffer(msg, dbuf, sText); break;
  
    case AQH_MSG_TYPE_FLASH_START:    AQH_FlashStartMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_FLASH_END:      AQH_FlashEndMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_FLASH_READY:    AQH_FlashReadyMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_FLASH_DATA:     AQH_FlashDataMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_FLASH_RSP:      AQH_FlashResponseMessage_DumpToBuffer(msg, dbuf, sText); break;
  
    case AQH_MSG_TYPE_DEVICE:         AQH_DeviceMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_MEMSTATS:       AQH_MemStatsMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_SYSSTATS:       AQH_SysStatsMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_REBOOT_REQ:     AQH_RebootMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_REBOOT_RSP:     AQH_RebootMessage_DumpToBuffer(msg, dbuf, sText); break;

    case AQH_MSG_TYPE_VALUE_REPORT:   AQH_ValueMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_VALUE_SET:      AQH_ValueMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_VALUE_SET_ACK:  AQH_ValueMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_VALUE_SET_NACK: AQH_ValueMessage_DumpToBuffer(msg, dbuf, sText); break;

    case AQH_MSG_TYPE_TIME_ANNOUNCE:  AQH_TimeMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_TIME_REQSET:    AQH_TimeMessage_DumpToBuffer(msg, dbuf, sText); break;
    case AQH_MSG_TYPE_TIME_RSPSET:    AQH_TimeMessage_DumpToBuffer(msg, dbuf, sText); break;

    case AQH_MSG_TYPE_DEBUG:         
    case AQH_MSG_TYPE_TWIBUSMEMBER:  
    default:                         AQH_NodeMessage_DumpToBuffer(msg, dbuf, sText); break;
    }
  }
}



uint32_t AQH_NodeMessage_GetMsgGroup(uint8_t msgType)
{
  switch(msgType) {
  case AQH_MSG_TYPE_PING:
  case AQH_MSG_TYPE_PONG:
  case AQH_MSG_TYPE_COMSENDSTATS:
  case AQH_MSG_TYPE_COMRECVSTATS:
  case AQH_MSG_TYPE_TWIBUSMEMBER:
  case AQH_MSG_TYPE_DEBUG:
  case AQH_MSG_TYPE_DEVICE:
  case AQH_MSG_TYPE_MEMSTATS:
  case AQH_MSG_TYPE_SYSSTATS:
    return AQH_MSG_TYPEGROUP_INFO;

  case AQH_MSG_TYPE_VALUE:
  case AQH_MSG_TYPE_VALUE2:
  case AQH_MSG_TYPE_VALUE_REPORT:
  case AQH_MSG_TYPE_VALUE_SET:
  case AQH_MSG_TYPE_VALUE_SET_ACK:
  case AQH_MSG_TYPE_VALUE_SET_NACK:
    return AQH_MSG_TYPEGROUP_VALUES;

  case AQH_MSG_TYPE_TIME_ANNOUNCE:
  case AQH_MSG_TYPE_TIME_REQSET:
  case AQH_MSG_TYPE_TIME_RSPSET:
    return AQH_MSG_TYPEGROUP_TIME;

  case AQH_MSG_TYPE_NEED_ADDRESS:
  case AQH_MSG_TYPE_HAVE_ADDRESS:
  case AQH_MSG_TYPE_CLAIM_ADDRESS:
  case AQH_MSG_TYPE_DENY_ADDRESS:
  case AQH_MSG_TYPE_ADDRESS_RANGE:
    return AQH_MSG_TYPEGROUP_ADDRESS;

  case AQH_MSG_TYPE_NET_SET_ACCEPTED_MSGGROUPS:
    return AQH_MSG_TYPEGROUP_ADMIN;

  case AQH_MSG_TYPE_FLASH_START:
  case AQH_MSG_TYPE_FLASH_END:
  case AQH_MSG_TYPE_FLASH_READY:
  case AQH_MSG_TYPE_FLASH_DATA:
  case AQH_MSG_TYPE_FLASH_RSP:
  case AQH_MSG_TYPE_REBOOT_REQ:
  case AQH_MSG_TYPE_REBOOT_RSP:
    return AQH_MSG_TYPEGROUP_FLASH;

  default:
    return 0;
  }
}



uint8_t _calcCrc8Checksum(const uint8_t *ptr, uint8_t len)
{
  int i;
  uint8_t x=0xff;

  for (i=0; i<len; i++, ptr++) {
    int j;

    x^=*ptr;
    for (j=0; j<8; j++) {
      if (x & 0x80)
	x=(uint8_t) (x<<1)^0x97;
      else
	x<<=1;
    }
  }

  return x;
}


