/****************************************************************************
 * 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 "./flash.h"
#include "../client.h"

#include "aqhome/msg/ipc/nodes/m_ipcn.h"
#include "aqhome/msg/node/m_node.h"
#include "aqhome/msg/node/m_flashready.h"
#include "aqhome/msg/node/m_flashstart.h"
#include "aqhome/msg/node/m_flashend.h"
#include "aqhome/msg/node/m_flashdata.h"
#include "aqhome/msg/node/m_flashresponse.h"
#include "aqhome/msg/node/m_reboot.h"
#include "aqhome/hexfile/hexfile.h"
#include "aqhome/hexfile/flashrecord.h"

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

#include <unistd.h>



/* ------------------------------------------------------------------------------------------------
 * defs
 * ------------------------------------------------------------------------------------------------
 */

#define I18S(msg) msg
#define I18N(msg) GWEN_I18N_Translate(PACKAGE, msg)

#define A_ARG GWEN_ARGS_FLAGS_HAS_ARGUMENT
#define A_END (GWEN_ARGS_FLAGS_HELP | GWEN_ARGS_FLAGS_LAST)
#define A_CHAR GWEN_ArgsType_Char
#define A_INT GWEN_ArgsType_Int


#define FLASH_TOOL_PAGESIZE                  16

#define FLASH_TOOL_MAX_REPEAT                16
#define FLASH_TOOL_DEFAULT_TIMEOUTINSECS      5
#define FLASH_TOOL_WAITFORFLASHREADY_INSECS  30
#define FLASH_TOOL_WAITFORFLASHRESPONSE_SECS  1



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

static int doFlash(AQH_OBJECT *o);
static int _rebootNode(AQH_OBJECT *o, unsigned int uid, int timeoutInSeconds);
static int _performFlashProcedure(AQH_OBJECT *o,
                                  unsigned int uid,
                                  const AQH_FLASHRECORD_LIST *flashRecordList,
                                  int pageSize,
                                  int timeoutInSeconds);
static int _flashStart(AQH_OBJECT *o, unsigned int uid, int timeoutInSeconds);
static int _flashRecord(AQH_OBJECT *o,
                        const AQH_FLASHRECORD *flashRecord,
                        uint16_t pageSize,
                        int timeoutInSeconds);
static int _flashData(AQH_OBJECT *o, uint32_t address, const uint8_t *ptr, uint32_t len, int timeoutInSeconds);
static int _flashEnd(AQH_OBJECT *o, int reason, int timeoutInSeconds);

/** frees msg! */
static int _trySendMsgAndWaitForFlashResponse(AQH_OBJECT *o, AQH_MESSAGE *msg, const char *msgInfo, int timeoutInSeconds);
static int _waitForFlashResponse(AQH_OBJECT *o, int timeoutInSeconds);
static AQH_FLASHRECORD_LIST *_readHexfileIntoFlashRecordList(const char *hexFilename);



/* ------------------------------------------------------------------------------------------------
 * code
 * ------------------------------------------------------------------------------------------------
 */

int AQH_Tool_Flash(GWEN_DB_NODE *dbGlobalArgs, int argc, char **argv)
{
  AQH_EVENT_LOOP *eventLoop;
  AQH_OBJECT *o;
  int rv;
  const GWEN_ARGS args[]= {
    /* flags type    name              min max s    long           short_descr, long_descr */
    { A_ARG, A_CHAR, "tcpAddress",     0,  1,  "t", "tcpaddress",  I18S("TCP address to connect to [127.0.0.1]"), NULL},
    { A_ARG, A_INT,  "tcpPort",        0,  1,  "P", "tcpport",     I18S("Specify the TCP port to listen on"), NULL},
    { A_ARG, A_INT,  "timeout",        0,  1,  "T", NULL,          I18S("Specify timeout in seconds for response"), NULL},
    { A_ARG, A_CHAR, "userId",         0,  1,  "u", "userid",      I18S("Specify user id"), NULL},
    { A_ARG, A_CHAR, "password",       0,  1,  "p", "password",    I18S("Specify service password"), NULL},
    { A_ARG, A_CHAR, "uid",            1,  1,  "U", "uid",         I18S("Specify UID of the node to flash"),NULL},
    { A_ARG, A_CHAR, "hexFilename",    1,  1,  "H", NULL,          I18S("Specify hexfile to flash"), NULL},
    { 0,     A_INT,  "reboot",         0,  1,  "R", NULL,          I18S("Request node reboot before trying to flash"), NULL},
    { A_END, A_INT,  "help",           0,  0,  "h", "help",        I18S("Show this help screen"), NULL}
  };

  eventLoop=AQH_EventLoop_new();
  o=AQH_ToolClient_new(eventLoop, AQH_IPC_PROTOCOL_NODES_ID, AQH_IPC_PROTOCOL_NODES_VERSION, dbGlobalArgs, args);
  rv=AQH_ToolClient_ReadLocalArgs(o, argc, argv);
  if (rv!=0)
    return rv;

  rv=AQH_ToolClient_Connect(o, AQH_TOOL_CLIENT_CONNECTFLAGS_WITHCONNECTMSG | AQH_TOOL_CLIENT_CONNECTFLAGS_WITHGRPMSG,
                            0, AQH_MSG_TYPEGROUP_FLASH);
  if (rv<0) {
    DBG_INFO(NULL, "here(%d)", rv);
    AQH_Object_free(o);
    AQH_EventLoop_free(eventLoop);
    return rv;
  }

  rv=doFlash(o);
  if (rv<0) {
    DBG_INFO(NULL, "here(%d)", rv);
    AQH_Object_free(o);
    AQH_EventLoop_free(eventLoop);
    return rv;
  }

  AQH_Object_free(o);
  AQH_EventLoop_free(eventLoop);
  return 0;
}



int doFlash(AQH_OBJECT *o)
{
  GWEN_DB_NODE *dbArgs;
  int rv;
  int timeoutInSeconds;
  int doReboot;
  const char *s;
  unsigned int uid;
  const char *hexFilename;
  AQH_FLASHRECORD_LIST *flashRecordList=NULL;
  AQH_MESSAGE *msg;
  uint16_t pageSize;

  dbArgs=AQH_ToolClient_GetDbLocalArgs(o);

  /* read data */
  timeoutInSeconds=GWEN_DB_GetIntValue(dbArgs, "timeout", 0, FLASH_TOOL_DEFAULT_TIMEOUTINSECS);
  doReboot=GWEN_DB_GetIntValue(dbArgs, "reboot", 0, 0);
  s=GWEN_DB_GetCharValue(dbArgs, "uid", 0, NULL);
  if (1!=sscanf(s, "%x", &uid)) {
    DBG_ERROR(NULL, "Bad uid \"%s\"", s);
    return 1;
  }

  /* read hex file */
  hexFilename=GWEN_DB_GetCharValue(dbArgs, "hexFilename", 0, NULL);
  flashRecordList=_readHexfileIntoFlashRecordList(hexFilename);
  if (flashRecordList==NULL) {
    DBG_ERROR(NULL, "Error reading flash record list from hexfile");
    return 2;
  }

  /* probably reboot node */
  if (doReboot) {
    fprintf(stdout, "Sending REBOOT request\n");
    rv=_rebootNode(o, uid, timeoutInSeconds);
    if (rv<0) {
      DBG_INFO(NULL, "here (%d)", rv);
      fprintf(stderr, "No REBOOT response received.\n");
      return 3;
    }
    fprintf(stdout, "Reboot in progress\n");
  }

  /* wait for FLASH_READY message */
  fprintf(stdout, "Waiting for node to become ready for flashing\n");
  msg=AQH_ToolClient_WaitForNodeMsg(o, 0, AQH_MSG_TYPE_FLASH_READY, FLASH_TOOL_WAITFORFLASHREADY_INSECS);
  if (msg==NULL) {
    DBG_INFO(NULL, "No FLASH_READY message received.");
    fprintf(stderr, "No FLASH_READY received.\n");
    return 3;
  }
  DBG_INFO(NULL, "FLASH_READY message received");
  pageSize=AQH_FlashReadyMessage_GetPagesize(msg);
  fprintf(stdout, "Node is ready for flashing (pagesize=%d bytes)\n", pageSize);
  AQH_Message_free(msg);

  /* perform flash */
//  rv=_performFlashProcedure(o, uid, flashRecordList, pageSize, timeoutInSeconds);
  rv=_performFlashProcedure(o, uid, flashRecordList, FLASH_TOOL_PAGESIZE, timeoutInSeconds);
  if (rv<0) {
    if (rv<0) {
      DBG_INFO(NULL, "here (%d)", rv);
      AQH_FlashRecord_List_free(flashRecordList);
      return 4;
    }
  }

  AQH_FlashRecord_List_free(flashRecordList);
  return 0;
}



int _rebootNode(AQH_OBJECT *o, unsigned int uid, int timeoutInSeconds)
{
  AQH_MESSAGE *nodeMsg;

  /* send REBOOT_REQUEST message */
  fprintf(stdout, "- Sending REBOOT request\n");
  DBG_INFO(NULL, "Sending REBOOT REQUEST message");
  nodeMsg=AQH_RebootMessage_new(0xff, 0x00, AQH_MSG_TYPE_REBOOT_REQ, uid);
  if (nodeMsg==NULL) {
    DBG_ERROR(NULL, "Error creating message");
    return GWEN_ERROR_GENERIC;
  }

  AQH_ToolClient_SendNodeMsg(o, nodeMsg);
#if 0
  nodeMsg=AQH_ToolClient_WaitForNodeMsg(o, 0, AQH_MSG_TYPE_REBOOT_RSP, timeoutInSeconds);
  if (nodeMsg==NULL) {
    DBG_INFO(NULL, "Bad or no reboot response received.");
    return GWEN_ERROR_GENERIC;
  }

  DBG_INFO(NULL, "REBOOT_RESPONSE message received");
  AQH_Message_free(nodeMsg);
#endif
  return 0;
}



int _performFlashProcedure(AQH_OBJECT *o,
                           unsigned int uid,
                           const AQH_FLASHRECORD_LIST *flashRecordList,
                           int pageSize,
                           int timeoutInSeconds)
{
  int rv;
  const AQH_FLASHRECORD *flashRecord;

  rv=_flashStart(o, uid, timeoutInSeconds);
  if (rv<0) {
    DBG_INFO(NULL, "here (%d)", rv);
    return rv;
  }

  flashRecord=AQH_FlashRecord_List_First(flashRecordList);
  while(flashRecord) {
    DBG_ERROR(NULL, "Sending flash record at %08x", AQH_FlashRecord_GetAddress(flashRecord));
    rv=_flashRecord(o, flashRecord, pageSize, timeoutInSeconds);
    if (rv!=0) {
      DBG_ERROR(NULL, "Error sending flash data (%d)", rv);
      return rv;
    }
    flashRecord=AQH_FlashRecord_List_Next(flashRecord);
  }

  rv=_flashEnd(o, 0, timeoutInSeconds);
  if (rv<0) {
    DBG_INFO(NULL, "here (%d)", rv);
    return rv;
  }
  DBG_ERROR(NULL, "Flash finished.");
  return 0;
}



int _flashStart(AQH_OBJECT *o, unsigned int uid, int timeoutInSeconds)
{
  AQH_MESSAGE *nodeMsg;

  nodeMsg=AQH_FlashStartMessage_new(0xc1, 0x00, AQH_MSG_TYPE_FLASH_START, uid);
  if (nodeMsg==NULL) {
    DBG_ERROR(NULL, "Error creating message");
    return GWEN_ERROR_GENERIC;
  }
  return _trySendMsgAndWaitForFlashResponse(o, nodeMsg, "FLASH_START", timeoutInSeconds);
}



int _flashRecord(AQH_OBJECT *o,
                 const AQH_FLASHRECORD *flashRecord,
                 uint16_t pageSize,
                 int timeoutInSeconds)
{
  const uint8_t *ptr;
  uint32_t len;
  uint32_t address;

  ptr=AQH_FlashRecord_GetDataPointer(flashRecord);
  len=AQH_FlashRecord_GetDataLength(flashRecord);
  address=AQH_FlashRecord_GetAddress(flashRecord);
  DBG_INFO(NULL, "Sending record: addr=%04x, len=%d, pagesize=%d", address, len, pageSize);
  fprintf(stdout, "- sending record: addr=%04x, len=%d, pagesize=%d\n", address, len, pageSize);
  while(ptr && len) {
    int rv;
    uint32_t sendLen;

    sendLen=(len>pageSize)?pageSize:len;
    fprintf(stdout, "  - sending page: addr=%04x, len=%d (%d bytes left)\n", address, sendLen, len);
//    GWEN_Text_DumpString((const char*)ptr, sendLen, 5);

    rv=_flashData(o, address, ptr, sendLen, timeoutInSeconds);
    if (rv<0) {
      DBG_INFO(NULL, "here (%d)", rv);
      return rv;
    }
    ptr+=sendLen;
    address+=sendLen;
    len-=sendLen;
  } /* while */
  DBG_ERROR(NULL, "Full record sent and acknowledged");
  return 0;
}



int _flashData(AQH_OBJECT *o, uint32_t address, const uint8_t *ptr, uint32_t len, int timeoutInSeconds)
{
  AQH_MESSAGE *nodeMsg;
  int rv;
  GWEN_BUFFER *dbuf;

  dbuf=GWEN_Buffer_new(0, 256, 0, 1);
  GWEN_Buffer_AppendString(dbuf, "FLASH_DATA (");
  GWEN_Buffer_AppendArgs(dbuf, "addr=%04x, data len=%d)", address, len);

  nodeMsg=AQH_FlashDataMessage_new(0xc1, 0x00, AQH_MSG_TYPE_FLASH_DATA, address, ptr, len);
  if (nodeMsg==NULL) {
    DBG_ERROR(NULL, "Error creating message");
    return GWEN_ERROR_GENERIC;
  }

  if (0) {
    GWEN_BUFFER *dbuf;

    dbuf=GWEN_Buffer_new(0, 256, 0, 1);
    AQH_NodeMessage_DumpToBuffer(nodeMsg, dbuf, "sent");
    fprintf(stderr, "%s\n", GWEN_Buffer_GetStart(dbuf));
    GWEN_Buffer_free(dbuf);
  }

  rv=_trySendMsgAndWaitForFlashResponse(o, nodeMsg, GWEN_Buffer_GetStart(dbuf), timeoutInSeconds);
  GWEN_Buffer_free(dbuf);
  return rv;
}



int _flashEnd(AQH_OBJECT *o, int reason, int timeoutInSeconds)
{
  AQH_MESSAGE *nodeMsg;

  nodeMsg=AQH_FlashEndMessage_new(0xc1, 0x00, AQH_MSG_TYPE_FLASH_END, reason);
  if (nodeMsg==NULL) {
    DBG_ERROR(NULL, "Error creating message");
    return GWEN_ERROR_GENERIC;
  }
  return _trySendMsgAndWaitForFlashResponse(o, nodeMsg, "FLASH_END", timeoutInSeconds);
}



int _trySendMsgAndWaitForFlashResponse(AQH_OBJECT *o, AQH_MESSAGE *msg, const char *msgInfo, int timeoutInSeconds)
{
  int i;

  for (i=0; i<FLASH_TOOL_MAX_REPEAT; i++) {
    AQH_MESSAGE *nodeMsg;
    int rv;

    if (i)
      fprintf(stdout, "  - retry sending %s (retry %d)\n", msgInfo, i);

    /* send message */
    nodeMsg=AQH_Message_dup(msg);
    DBG_INFO(NULL, "Sending %s message (%d)", msgInfo, i);
    AQH_ToolClient_SendNodeMsg(o, nodeMsg);
    rv=_waitForFlashResponse(o, FLASH_TOOL_WAITFORFLASHRESPONSE_SECS);
    if (rv==0) {
      AQH_Message_free(msg);
      return 0;
    }
  }
  DBG_INFO(NULL, "No FLASH_RESPONSE message received");
  fprintf(stderr, "No FLASH_RESPONSE received.\n");
  AQH_Message_free(msg);
  return GWEN_ERROR_GENERIC;
}




int _waitForFlashResponse(AQH_OBJECT *o, int timeoutInSeconds)
{
  AQH_MESSAGE *nodeMsg;

  nodeMsg=AQH_ToolClient_WaitForNodeMsg(o, 0, AQH_MSG_TYPE_FLASH_RSP, timeoutInSeconds);
  if (nodeMsg==NULL) {
    DBG_INFO(NULL, "Bad or no flash response received.");
    return GWEN_ERROR_GENERIC;
  }
  else {
    uint8_t result;

    result=AQH_FlashResponseMessage_GetCode(nodeMsg);
    AQH_Message_free(nodeMsg);
    if (result!=0) {
      DBG_ERROR(NULL, "Negative FLASH_RESPONSE received (%d)", result);
      return GWEN_ERROR_GENERIC;
    }
    else {
      DBG_INFO(NULL, "FLASH_RESPONSE message received");
      return 0;
    }
  }
  DBG_INFO(NULL, "No FLASH_RESPONSE message received");
  return GWEN_ERROR_GENERIC;
}





AQH_FLASHRECORD_LIST *_readHexfileIntoFlashRecordList(const char *hexFilename)
{
  AQH_HEXFILE *hexfile;
  AQH_FLASHRECORD_LIST *flashRecordList;

  fprintf(stdout, "Reading hexfile \"%s\"\n", hexFilename);

  /* read hexfile */
  hexfile=AQH_Hexfile_fromFile(hexFilename);
  if (hexfile==NULL) {
    DBG_ERROR(NULL, "Error loading hexfile \"%s\"", hexFilename);
    return NULL;
  }
  flashRecordList=AQH_FlashRecord_fromHexfileRecords(AQH_Hexfile_GetRecordList(hexfile));
  AQH_Hexfile_free(hexfile);
  if (flashRecordList==NULL) {
    DBG_ERROR(NULL, "Error reading flash record list from hexfile");
    return NULL;
  }

  return flashRecordList;
}




