#include "cmGlobal.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmLinkedHeap.h"
#include "cmJson.h"
#include "cmThread.h"
#include "cmUdpPort.h"
#include "cmUdpNet.h"
#include "cmVirtNet.h"
#include "cmText.h"   // use by cmVnTest()

enum { kOwnerVnFl = 0x01, kLocalVnFl = 0x02 };

struct cmVn_str;

typedef struct cmVnNode_str
{
  cmChar_t*            label;
  unsigned             id;
  unsigned             flags;
  cmVnH_t              vnH;     // handle of a local virtual net that is owned by this node
  cmTsMp1cH_t          qH;      // MPSC non-blocking queue to hold incoming msg's from local virt. net's
  struct cmVn_str*     p;       // pointer back to the cmVn_t which owns this NULL
  struct cmVnNode_str* link;
} cmVnNode_t;

typedef struct cmVn_str
{
  cmErr_t     err;
  cmUdpNetH_t udpH;
  cmVnNode_t* nodes;
  cmVnCb_t    cbFunc;
  void*       cbArg;
  unsigned    ownerNodeId;
} cmVn_t;

cmVnH_t cmVnNullHandle = cmSTATIC_NULL_HANDLE;

cmVn_t* _cmVnHandleToPtr( cmVnH_t h )
{
  cmVn_t* p = (cmVn_t*)h.h;
  assert(p != NULL);
  return p;
}

void _cmVnNodeFree( cmVnNode_t* np )
{
  if( np == NULL )
    return;
  
  if( cmTsMp1cIsValid(np->qH) )
    cmTsMp1cDestroy(&np->qH);
  
  np->vnH = cmVnNullHandle;

  cmMemFree(np->label);
  cmMemFree(np);
}

cmVnRC_t _cmVnDestroy( cmVn_t* p )
{
  cmVnRC_t rc = kOkVnRC;

  if( cmUdpNetFree(&p->udpH) != kOkUnRC )
    return cmErrMsg(&p->err,kUdpNetFailVnRC,"The UDP network release failed.");

  cmVnNode_t* np = p->nodes;
  while( np != NULL )
  {
    cmVnNode_t* nnp = np->link;
    _cmVnNodeFree(np);
    np = nnp;
  }

  cmMemFree(p);

  return rc;
}

// This function is called by cmUdpNetReceive() which is called by cmVnReceive().
void _cmVnUdpNetCb( void* cbArg, cmUdpNetH_t h, const char* data, unsigned dataByteCnt, unsigned remoteNodeId )
{
  cmVnNode_t* np = (cmVnNode_t*)cbArg;
  assert( np->id == remoteNodeId );
  np->p->cbFunc(np->p->cbArg,np->id,dataByteCnt,data);
}


cmVnRC_t _cmVnSend( cmVn_t* p, cmVnNode_t* np, unsigned byteCnt, const cmChar_t* buf )
{
  cmVnRC_t rc = kOkVnRC;

  // if the node is local then send the msg directly
  if( cmIsFlag(np->flags,kLocalVnFl)  )
  {
    if( cmVnIsValid(np->vnH) )
      rc = cmVnRecvFromLocal(np->vnH,p->ownerNodeId,byteCnt,buf);
  }
  else
  {
    // if the node is remote then send the msg via UDP
    if( cmUdpNetSendById(p->udpH,np->id,buf,byteCnt) != kOkUnRC )
      rc = cmErrMsg( &p->err, kUdpNetFailVnRC, "UDP Net send to remote node '%s' failed.",cmStringNullGuard(np->label));
  }

  return rc;
}

cmVnNode_t* _cmVnNodeFindById( cmVn_t* p, unsigned id )
{
  cmVnNode_t* np = p->nodes;
  for(; np!=NULL; np=np->link)
    if( np->id == id )
      return np;

  return NULL;
}

cmVnNode_t* _cmVnNodeFindByLabel( cmVn_t* p, const cmChar_t* label )
{
  cmVnNode_t* np = p->nodes;
  for(; np!=NULL; np=np->link)
    if( strcmp(np->label,label)==0 )
      return np;

  return NULL;
}

cmVnNode_t* _cmVnNodeFindOwner( cmVn_t* p )
{
  cmVnNode_t* np = p->nodes;
  for(; np!=NULL; np=np->link)
    if( cmIsFlag(np->flags,kOwnerVnFl) )
      return np;

  return NULL;
}

cmVnRC_t  _cmVnCreateNode( cmVn_t* p, const cmChar_t* label, unsigned nodeId, unsigned flags, cmVnNode_t** npp )
{
  cmVnNode_t* np;

  if((np = _cmVnNodeFindById(p,nodeId)) != NULL )
    return cmErrMsg(&p->err,kDuplicateNodeIdVnRC,"The node id=%i is already in use for '%s'.",cmStringNullGuard(np->label));

  if((np = _cmVnNodeFindByLabel(p,label)) != NULL )
    return cmErrMsg(&p->err,kDuplicateNodeLabelVnRC,"The node label '%s' is already used by another node.",cmStringNullGuard(label));

  np         = cmMemAllocZ(cmVnNode_t,1);
  np->label  = cmMemAllocStr(label);
  np->id     = nodeId;
  np->flags  = flags;
  np->p      = p;

  *npp = np;

  return kOkVnRC;
}


void _cmVnNodeLink( cmVn_t* p, cmVnNode_t* np )
{
  cmVnNode_t* cnp = p->nodes;
  if( cnp == NULL )
    p->nodes = np;
  else
  {
    while( cnp->link != NULL )
      cnp = cnp->link;

    cnp->link = np;
  }
}

cmVnRC_t _cmVnCreateOwnerNode(  cmVn_t* p, const cmChar_t* label, unsigned nodeId, unsigned udpRecvBufByteCnt, unsigned udpRecvTimeOutMs, const cmChar_t* ipAddr, unsigned ipPort )
{
  cmVnRC_t    rc = kOkVnRC;
  cmVnNode_t* np;

  // create a generic node
  if((rc = _cmVnCreateNode(p, label,nodeId, kOwnerVnFl, &np )) != kOkVnRC )
    goto errLabel;

  // initialize the UDP net with the owner node
  if( cmUdpNetInit(p->udpH,label,nodeId,ipPort,_cmVnUdpNetCb,np,udpRecvBufByteCnt,udpRecvTimeOutMs) != kOkUnRC )
  {
    rc = cmErrMsg(&p->err,kUdpNetFailVnRC,"UDP network initialization failed for node:'%s'.",cmStringNullGuard(label));
    goto errLabel;
  }

  _cmVnNodeLink(p,np);

  p->ownerNodeId = nodeId;

 errLabel:
  if( rc != kOkVnRC )
    _cmVnNodeFree(np);
  return rc;
}

//------------------------------------------------------------------------------------------------------------

cmVnRC_t cmVnCreate( cmCtx_t* ctx, cmVnH_t* hp, cmVnCb_t cbFunc, void* cbArg, const cmChar_t* label, unsigned nodeId, unsigned udpRecvBufByteCnt, unsigned udpRecvTimeOutMs, const cmChar_t* ipAddr, unsigned ipPort  )
{
  cmVnRC_t rc;
  if((rc = cmVnDestroy(hp)) != kOkVnRC )
    return rc;

  cmVn_t* p = cmMemAllocZ(cmVn_t,1);

  cmErrSetup(&p->err,&ctx->rpt,"cmVirtNet");

  if( cmUdpNetAlloc(ctx,&p->udpH) != kOkUnRC )
  {
    rc = cmErrMsg(&p->err,kUdpNetFailVnRC,"The UDP network allocation failed.");
    goto errLabel;
  }

  // create the owner node
  if((rc = _cmVnCreateOwnerNode(p,label,nodeId, udpRecvBufByteCnt, udpRecvTimeOutMs, ipAddr, ipPort )) != kOkVnRC )
    goto errLabel;

  p->ownerNodeId = nodeId;
  p->cbFunc      = cbFunc;
  p->cbArg       = cbArg;

  hp->h = p;

 errLabel:

  if( rc != kOkUdpRC )
    _cmVnDestroy(p);

  return rc;
}

cmVnRC_t cmVnDestroy( cmVnH_t* hp )
{
  cmVnRC_t rc = kOkVnRC;

  if( hp == NULL || cmVnIsValid(*hp)==false )
    return rc;

  cmVn_t* p = _cmVnHandleToPtr(*hp);

  if((rc = _cmVnDestroy(p)) != kOkVnRC )
    return rc;

  hp->h = NULL;

  return rc;
}

bool     cmVnIsValid( cmVnH_t h )
{ return h.h != NULL; }

cmVnRC_t cmVnEnableListen( cmVnH_t h, bool enableFl )
{
  cmVn_t* p = _cmVnHandleToPtr(h);

  if( cmUdpNetEnableListen( p->udpH, enableFl ) != kOkUnRC )
    return cmErrMsg(&p->err,kUdpNetFailVnRC,"UDP listen enable failed.");
  return kOkVnRC;
}

// This function is called by cmTsMp1cDequeueMsg() which is called by cmVnReceive().
cmRC_t _cmVnTsQueueCb(void* userCbPtr, unsigned msgByteCnt, const void* msgDataPtr )
{
  cmVnNode_t* np = (cmVnNode_t*)userCbPtr;
  return np->p->cbFunc(np->p->cbArg,np->id,msgByteCnt,msgDataPtr);
}

cmVnRC_t cmVnCreateLocalNode(  cmVnH_t h, const cmChar_t* label, unsigned nodeId, cmVnH_t vnH, unsigned queBufByteCnt )
{
  cmVnRC_t    rc = kOkVnRC;
  cmVn_t*     p  = _cmVnHandleToPtr(h);
  cmVnNode_t* np = NULL; 

  // create a generic node
  if((rc = _cmVnCreateNode(p,label,nodeId,kLocalVnFl,&np )) != kOkVnRC )
    goto errLabel;

  if( cmTsMp1cCreate( &np->qH, queBufByteCnt, _cmVnTsQueueCb, np, p->err.rpt ) != kOkThRC )
  {
    rc = cmErrMsg(&p->err,kQueueFailVnRC,"The internal thread-safe queue creation failed for local node '%s'.",cmStringNullGuard(label));
    goto errLabel;
  }

  np->vnH = vnH;

  _cmVnNodeLink(p,np);

 errLabel:
  if( rc != kOkVnRC )
    _cmVnNodeFree(np);
  return rc;
}

cmVnRC_t cmVnCreateRemoteNode( cmVnH_t h, const cmChar_t* label, unsigned nodeId,  const cmChar_t* ipAddr, unsigned ipPort )
{
  cmVnRC_t    rc = kOkVnRC;
  cmVn_t*     p  = _cmVnHandleToPtr(h);
  cmVnNode_t* np;

  // creaet a generic node
  if((rc = _cmVnCreateNode(p, label,nodeId, 0, &np )) != kOkVnRC )
    goto errLabel;

  if( ipAddr!=NULL )
  {
    if( cmUdpNetRegisterRemote(p->udpH,label,nodeId,ipAddr,ipPort) != kOkUnRC )
    {
      rc = cmErrMsg(&p->err,kUdpNetFailVnRC,"UDP network remote registration failed for node:'%s'.",cmStringNullGuard(label));
      goto errLabel;
    }
  }

  _cmVnNodeLink(p,np);

 errLabel:
  if( rc != kOkVnRC )
    _cmVnNodeFree(np);
  return rc;
}

cmVnRC_t cmVnRecvFromLocal( cmVnH_t h, unsigned srcNodeId, unsigned byteCnt, const cmChar_t* buf )
{
  cmVnRC_t    rc = kOkVnRC;
  cmVnNode_t* np;
  cmVn_t*     p  = _cmVnHandleToPtr(h);

  if(( np = _cmVnNodeFindById(p,srcNodeId)) == NULL )
    return cmErrMsg(&p->err,kNodeNotFoundVnRC,"The node with id=%i could not be found.",srcNodeId);
  
  if( cmTsMp1cIsValid(np->qH) == false )
    return cmErrMsg(&p->err,kQueueFailVnRC,"The internal MPSC queue for the node '%s' is not valid. Is this a local node?",cmStringNullGuard(np->label));

  if( cmTsMp1cEnqueueMsg(np->qH,buf,byteCnt) != kOkThRC )
    return cmErrMsg(&p->err,kQueueFailVnRC,"Enqueue failed on the internal MPSC queue for the node '%s'.",cmStringNullGuard(np->label));
    
  return rc;
}

cmVnRC_t cmVnSendById( cmVnH_t h, unsigned nodeId, unsigned byteCnt, const cmChar_t* buf )
{
  cmVnNode_t* np;
  cmVn_t*     p  = _cmVnHandleToPtr(h);

  if(( np = _cmVnNodeFindById(p,nodeId)) == NULL )
    return cmErrMsg(&p->err,kNodeNotFoundVnRC,"The node with id=%i could not be found.",nodeId);

  return _cmVnSend(p,np,byteCnt,buf);
}

cmVnRC_t cmVnSendByLabel( cmVnH_t h, const cmChar_t* nodeLabel, unsigned byteCnt, const cmChar_t* buf )
{
  cmVnNode_t* np;
  cmVn_t*     p  = _cmVnHandleToPtr(h);

  if(( np = _cmVnNodeFindByLabel(p,nodeLabel)) == NULL )
    return cmErrMsg(&p->err,kNodeNotFoundVnRC,"The node named '%s' could not be found.",cmStringNullGuard(nodeLabel));

  return _cmVnSend(p,np,byteCnt,buf);
}

cmVnRC_t _cmVnRecvQueueMsgs( cmVn_t* p, cmVnNode_t* np, unsigned *msgCntPtr )
{
  unsigned i;
  unsigned mn = *msgCntPtr;
  *msgCntPtr = 0;

  for(i=0; (i<mn || mn==cmInvalidCnt) && cmTsMp1cMsgWaiting(np->qH); ++i)
  {
    // calling this function results in calls to _cmVnTsQueueCb()
    if( cmTsMp1cDequeueMsg( np->qH,NULL,0) != kOkThRC )
      return cmErrMsg(&p->err,kQueueFailVnRC,"Msg deque failed for node '%s'.",cmStringNullGuard(np->label));
  }

  *msgCntPtr = i;

  return kOkVnRC;
}

cmVnRC_t cmVnReceive( cmVnH_t h, unsigned* msgCntPtr )
{
  cmVnRC_t    rc = kOkVnRC;
  cmVn_t*     p  = _cmVnHandleToPtr(h);
  cmVnNode_t* np = p->nodes;
  unsigned    mn = msgCntPtr == NULL ? cmInvalidCnt : *msgCntPtr;

  if( msgCntPtr != NULL )
    *msgCntPtr = 0;
  
  for(; np!=NULL && rc==kOkVnRC; np=np->link)
  {
    unsigned msgCnt = mn;
    switch( np->flags & (kOwnerVnFl | kLocalVnFl) )
    {
      case kOwnerVnFl:
        break;

      case kLocalVnFl:
        rc = _cmVnRecvQueueMsgs(p,np,&msgCnt);
        break;

      default:
        if( cmUdpNetReceive(p->udpH,msgCntPtr==NULL?NULL:&msgCnt) != kOkUnRC )
          rc = cmErrMsg(&p->err,kUdpNetFailVnRC,"The UDP net receive failed on node '%s'.",cmStringNullGuard(np->label));
        break;
    }

    if( rc == kOkVnRC && msgCntPtr != NULL )
      *msgCntPtr += msgCnt;

  }

  return rc;
}

//---------------------------------------------------------------------------------------------------

unsigned udpRecvBufByteCnt = 8192;
unsigned udpRecvTimeOutMs  = 100;
unsigned queueBufByteCnt   = 8192;
  void*  cbArg             = NULL;


typedef struct
{
  const cmChar_t* label;
  unsigned        id;
  const cmChar_t* ipAddr;
  unsigned        ipPort;
  cmVnH_t         vnH;
} node_t;

cmVnRC_t _cmVnTestCb( void* cbArg, unsigned srcNodeId, unsigned byteCnt, const char* buf )
{
  printf("src node:%i bytes:%i %s\n",srcNodeId,byteCnt,buf);
  return kOkVnRC;
}

cmVnRC_t _cmVnTestCreateLocalNet( cmCtx_t* ctx, cmErr_t* err, unsigned id, node_t* nodeArray   )
{
  cmVnRC_t rc = kOkVnRC;

  if((rc = cmVnCreate(ctx, &nodeArray[id].vnH, _cmVnTestCb, cbArg, nodeArray[id].label, nodeArray[id].id, udpRecvBufByteCnt, udpRecvTimeOutMs, nodeArray[id].ipAddr, nodeArray[id].ipPort )) != kOkVnRC )
    rc = cmErrMsg(err,rc,"Virtual network create failed.");

  return rc;
}

cmVnRC_t _cmVnTestCreateVirtNodes( cmErr_t* err, unsigned id, node_t* nodeArray )
{
  unsigned rc = kOkVnRC;
  unsigned i;

  for(i=0; nodeArray[i].label != NULL; ++i)
  {
    // if this is a local node
    if( strcmp(nodeArray[i].ipAddr,nodeArray[id].ipAddr) == 0 )
    {
      // if this is not the owner node
      if( i != id )
        if((rc = cmVnCreateLocalNode( nodeArray[id].vnH, nodeArray[i].label, nodeArray[i].id, nodeArray[i].vnH, queueBufByteCnt )) != kOkVnRC )
        {
          cmErrMsg(err,rc,"Local node create failed for node:'%s'.",nodeArray[i].label);
          goto errLabel;
        }
    }
    else  // this must be a remote node
    {
      if((rc = cmVnCreateRemoteNode(nodeArray[id].vnH, nodeArray[i].label, nodeArray[i].id, nodeArray[i].ipAddr, nodeArray[i].ipPort )) != kOkVnRC )
      {
        cmErrMsg(err,rc,"Remote node create failed for node '%s'.",nodeArray[i].label);
        goto errLabel;
      }
    }
  }

 errLabel:
  return rc;
}

cmVnRC_t _cmVnTestSend( cmErr_t* err, node_t* nodeArray, unsigned id, unsigned i, unsigned n )
{
  cmVnRC_t rc = kOkVnRC;

  if( id == i )
  {
    printf("A node cannot send to itself.\n");
    return rc;
  }

  const cmChar_t* buf = cmTsPrintfS("msg-%i",n);
  unsigned bufByteCnt = strlen(buf)+1;

  printf("Sending from '%s' to '%s'.\n", cmStringNullGuard(nodeArray[id].label), cmStringNullGuard(nodeArray[i].label));


  if((rc =  cmVnSendById(nodeArray[id].vnH, nodeArray[i].id, bufByteCnt, buf ))!= kOkVnRC )
    cmErrMsg(err,rc,"Send from '%s' to '%s' failed.", cmStringNullGuard(nodeArray[id].label), cmStringNullGuard(nodeArray[i].label));

  return rc;
}

cmVnRC_t cmVnTest( cmCtx_t* ctx )
{
  cmVnRC_t rc                = kOkVnRC;
  cmErr_t  err;  
  unsigned id                = 0;
  unsigned i;
  unsigned n = 0;

  node_t nodeArray[] =
  {
    { "whirl-0", 10, "192.168.15.109", 5768, cmVnNullHandle },
    { "whirl-1", 20, "192.168.15.109", 5767, cmVnNullHandle },
    { "thunk-0", 30, "192.168.15.111", 5766, cmVnNullHandle },
    { NULL, -1, NULL }
  };

  cmErrSetup(&err,&ctx->rpt,"cmVnTest");

  // create the virt networks for the local nodes
  for(i=0; nodeArray[i].label !=NULL; ++i)
    if( strcmp(nodeArray[i].ipAddr,nodeArray[id].ipAddr ) == 0 )
      if((rc = _cmVnTestCreateLocalNet(ctx,&err,i,nodeArray)) != kOkVnRC )
        goto errLabel;

  // create the virtual nodes for each local virtual network
  for(i=0; nodeArray[i].label != NULL; ++i)
    if( cmVnIsValid(nodeArray[i].vnH) )
      if((rc = _cmVnTestCreateVirtNodes(&err, i, nodeArray )) != kOkVnRC )
        break;

  char c;
  while((c=getchar()) != 'q')
  {
    bool promptFl = true;

    switch( c )
    {
      case '0':
      case '1':
      case '2':
        _cmVnTestSend(&err, nodeArray, id, (unsigned)c - (unsigned)'0', n );
        ++n;
        break;
    }
    
    for(i=0; nodeArray[i].label!=NULL; ++i)
      if( cmVnIsValid(nodeArray[i].vnH) )
        cmVnReceive(nodeArray[i].vnH,NULL);

    if( promptFl )
      cmRptPrintf(&ctx->rpt,"%i> ",n);

  }

 errLabel:
  // destroy the virtual networks
  for(i=0; nodeArray[i].label!=NULL; ++i)
    if( cmVnIsValid(nodeArray[i].vnH) )
      if((rc = cmVnDestroy(&nodeArray[i].vnH)) != kOkVnRC )
        cmErrMsg(&err,rc,"Node destroy failed for node '%s'.",cmStringNullGuard(nodeArray[i].label));

  return rc;
}