#include "HashTableBuffer.h"

//#include <stdlib.h>
//#include <string.h>

#include "YBWCManager.h"
extern YBWCManager* g_pYBWCManagerInstance;

HashTableBuffer* HashTableBuffer_Construct(HashTableBuffer* pThis, int nChunkSizeForFlush)
{
  pThis->nMaxSize           = MAX_HASH_TABLE_BUFFER_SIZE;
  pThis->nChunkSizeForFlush = nChunkSizeForFlush;

  if(pThis->nChunkSizeForFlush > pThis->nMaxSize)
  {
    pThis->nChunkSizeForFlush = pThis->nMaxSize;
  }

  if(pThis->nChunkSizeForFlush <= 0)
  {
    pThis->nChunkSizeForFlush = 1;
  }

  HashTableBuffer_Init(pThis);

  pThis->pBroadcastRequestBuffer = NULL;

  return pThis;
}

void HashTableBuffer_Destruct(HashTableBuffer* pThis)
{
  if(pThis)
  {
    if(pThis->pBroadcastRequestBuffer)
    {
      Request_Destruct(pThis->pBroadcastRequestBuffer);
    }

    free(pThis);
  }
}

void HashTableBuffer_Init(HashTableBuffer* pThis)
{
  int             i;

  HashTableEntry* pHashTableEntry;

  pThis->nCount   = 0;

  for(i = 0; i < pThis->nMaxSize; i++)
  {
    pHashTableEntry = &pThis->hashTableEntry[i];

    HashTableEntry_Init(pHashTableEntry);
  }
}

HashTableEntry* HashTableBuffer_AddEntryAndCheckBroadcast(HashTableBuffer* pThis,
                                                          uint64           nKey,
                                                          int              nMove,
                                                          int              nDepth,
                                                          int              nFlags,
                                                          int              nValue)
{
  HashTableEntry* pHashTableEntryResult;
  int             nResultTmp;

  int             nResult;

  pHashTableEntryResult = NULL;

  if(pThis)
  {
    ASSERT(pThis->nCount < pThis->nMaxSize);

    if(pThis->nCount >= pThis->nMaxSize)
    {
      if(g_pYBWCManagerInstance->pFruitConfiguration->nFruitDebugLevel >= FRUIT_DEBUG_LEVEL_ERROR_PRIO_1)
      {
        YBWCManager_Printf(g_pYBWCManagerInstance, 
                           "ERROR HashTableBuffer_AddEntryAndCheckBroadcast(... pThis->nCount (%d) >= pThis->nMaxSize (%d)\n", 
                           pThis->nCount,
                           pThis->nMaxSize);
      }

// kh 09.08.06 defensive (will probably not help any more...) because with this optimized version writing direct
// to the mpi packed buffer structures the count has already been "prefilled" as nChunkSizeForFlush
      HashTableBuffer_FlushWithBroadcast(pThis); 

    } // if(pThis->nCount >= pThis->nMaxSize)

    ASSERT(pThis->nCount < pThis->nMaxSize);

    if(pThis->nCount < pThis->nMaxSize)
    {
      if(pThis->pBroadcastRequestBuffer == NULL)
      {
        pThis->pBroadcastRequestBuffer = Request_Construct((Request*)malloc(sizeof(Request)));
        nResult           = Request_InitHashTableBroadcastBuffer(pThis->pBroadcastRequestBuffer, pThis);
        if(nResult == MPI_SUCCESS)
        {
        }
        else
        {
          if(g_pYBWCManagerInstance->pFruitConfiguration->nFruitDebugLevel >= FRUIT_DEBUG_LEVEL_ERROR_PRIO_1)
          {
            YBWCManager_Printf(g_pYBWCManagerInstance, "ERROR HashTableBuffer_Broadcast(... failed at Request_InitHashTableBroadcastBuffer(..., errorcode = %d\n", nResult);
          }
        }
      }

// kh 06.12.06 this is done directly now for a better performance
/*
      pHashTableEntryResult = &pThis->hashTableEntry[pThis->nCount++];

// kh 16.11.06 skip init (will be overwritten anyway)
//    HashTableEntry_Init(pHashTableEntryResult);

      pHashTableEntryResult->nKey       = nKey;
      pHashTableEntryResult->nMove      = nMove;
      pHashTableEntryResult->nDepth     = nDepth;
      pHashTableEntryResult->nMinValue  = nMinValue;
      pHashTableEntryResult->nMaxValue  = nMaxValue;
*/

// kh 06.12.06 start direct buffer fill 
      nResult = MPIWrapper_Pack_UINT64(MPIWrapper_Instance(),
                                       nKey, 
                                       pThis->pBroadcastRequestBuffer->pParameterBuffer, 
                                       MAX_REQUEST_PARAMETER_BUFFER_SIZE, 
                                       &pThis->pBroadcastRequestBuffer->nPosition, 
                                       MPI_COMM_WORLD);
      if(nResult == MPI_SUCCESS)
      {
        nResult = MPIWrapper_Pack_INT(MPIWrapper_Instance(),
                                      nMove, 
                                      pThis->pBroadcastRequestBuffer->pParameterBuffer, 
                                      MAX_REQUEST_PARAMETER_BUFFER_SIZE, 
                                      &pThis->pBroadcastRequestBuffer->nPosition, 
                                      MPI_COMM_WORLD);
        if(nResult == MPI_SUCCESS)
        {
          nResult = MPIWrapper_Pack_INT(MPIWrapper_Instance(),
                                        nDepth, 
                                        pThis->pBroadcastRequestBuffer->pParameterBuffer, 
                                        MAX_REQUEST_PARAMETER_BUFFER_SIZE, 
                                        &pThis->pBroadcastRequestBuffer->nPosition, 
                                        MPI_COMM_WORLD);
          if(nResult == MPI_SUCCESS)
          {
            nResult = MPIWrapper_Pack_INT(MPIWrapper_Instance(),
                                          nFlags, 
                                          pThis->pBroadcastRequestBuffer->pParameterBuffer, 
                                          MAX_REQUEST_PARAMETER_BUFFER_SIZE, 
                                          &pThis->pBroadcastRequestBuffer->nPosition, 
                                          MPI_COMM_WORLD);
            if(nResult == MPI_SUCCESS)
            {
              nResult = MPIWrapper_Pack_INT(MPIWrapper_Instance(),
                                            nValue, 
                                            pThis->pBroadcastRequestBuffer->pParameterBuffer, 
                                            MAX_REQUEST_PARAMETER_BUFFER_SIZE, 
                                            &pThis->pBroadcastRequestBuffer->nPosition, 
                                            MPI_COMM_WORLD);
              if(nResult == MPI_SUCCESS)
              {
              }
              else
              {
                if(g_pYBWCManagerInstance->pFruitConfiguration->nFruitDebugLevel >= FRUIT_DEBUG_LEVEL_ERROR_PRIO_1)
                {
                  YBWCManager_Printf(g_pYBWCManagerInstance, "ERROR HashTableBuffer_AddEntryAndCheckBroadcast(... failed at MPIWrapper_Pack_INT(... nValue..., errorcode = %d\n", nResult);
                }
              }
            }
            else
            {
              if(g_pYBWCManagerInstance->pFruitConfiguration->nFruitDebugLevel >= FRUIT_DEBUG_LEVEL_ERROR_PRIO_1)
              {
                YBWCManager_Printf(g_pYBWCManagerInstance, "ERROR HashTableBuffer_AddEntryAndCheckBroadcast(... failed MPIWrapper_Pack_INT(... nFlags..., errorcode = %d\n", nResult);
              }
            }
          }
          else
          {
            if(g_pYBWCManagerInstance->pFruitConfiguration->nFruitDebugLevel >= FRUIT_DEBUG_LEVEL_ERROR_PRIO_1)
            {
              YBWCManager_Printf(g_pYBWCManagerInstance, "ERROR HashTableBuffer_AddEntryAndCheckBroadcast(... failed MPIWrapper_Pack_INT(... nDepth..., errorcode = %d\n", nResult);
            }
          }
        }
        else
        {
          if(g_pYBWCManagerInstance->pFruitConfiguration->nFruitDebugLevel >= FRUIT_DEBUG_LEVEL_ERROR_PRIO_1)
          {
            YBWCManager_Printf(g_pYBWCManagerInstance, "ERROR HashTableBuffer_AddEntryAndCheckBroadcast(... failed MPIWrapper_Pack_INT(... nMove..., errorcode = %d\n", nResult);
          }
        }
      }
      else
      {
        if(g_pYBWCManagerInstance->pFruitConfiguration->nFruitDebugLevel >= FRUIT_DEBUG_LEVEL_ERROR_PRIO_1)
        {
          YBWCManager_Printf(g_pYBWCManagerInstance, "ERROR HashTableBuffer_AddEntryAndCheckBroadcast(... failed MPIWrapper_Pack_UINT64(... nKey..., errorcode = %d\n", nResult);
        }
      }
      
      pThis->nCount++;
// kh 06.12.06 end direct buffer fill 

      if(pThis->nCount >= pThis->nChunkSizeForFlush)
      {
        nResultTmp = HashTableBuffer_FlushWithBroadcast(pThis);

        if(nResultTmp == MPI_SUCCESS)
        {
        }

        ASSERT(pThis->nCount == 0);
      } // if(pThis->nCount >= pThis->nChunkSizeForFlush)
    } // if(pThis->nCount < pThis->nMaxSize)
    else
    {
      if(g_pYBWCManagerInstance->pFruitConfiguration->nFruitDebugLevel >= FRUIT_DEBUG_LEVEL_ERROR_PRIO_1)
      {
        YBWCManager_Printf(g_pYBWCManagerInstance, 
                           "ERROR HashTableBuffer_AddEntryAndCheckBroadcast(... still pThis->nCount (%d) >= pThis->nMaxSize (%d)\n", 
                           pThis->nCount,
                           pThis->nMaxSize);
      }
    } // !if(pThis->nCount < pThis->nMaxSize)
  } // if(pThis)

//return pHashTableEntryResult;
  return NULL; // kh 06.12.06 result should not be used anywhere with this optimized version
}

// kh 02.02.07 not used at the moment
/*
HashTableEntry* HashTableBuffer_AddEntry(HashTableBuffer* pThis,
                                         uint64           nKey,
                                         int              nMove,
                                         int              nDepth,
                                         int              nMinValue,
                                         int              nMaxValue)
{
  HashTableEntry* pHashTableEntryResult;

  pHashTableEntryResult = NULL;

  if(pThis)
  {
    ASSERT(pThis->nCount < pThis->nMaxSize);

    if(pThis->nCount >= pThis->nMaxSize)
    {
      if(g_pYBWCManagerInstance->pFruitConfiguration->nFruitDebugLevel >= FRUIT_DEBUG_LEVEL_ERROR_PRIO_1)
      {
        YBWCManager_Printf(g_pYBWCManagerInstance, 
                           "ERROR HashTableBuffer_AddEntry(... pThis->nCount (%d) >= pThis->nMaxSize (%d)\n", 
                           pThis->nCount,
                           pThis->nMaxSize);
      }
    } // if(pThis->nCount >= pThis->nMaxSize)
    else
    {
      pHashTableEntryResult = &pThis->hashTableEntry[pThis->nCount++];

      HashTableEntry_Init(pHashTableEntryResult);

      pHashTableEntryResult->nKey       = nKey;
      pHashTableEntryResult->nMove      = nMove;
      pHashTableEntryResult->nDepth     = nDepth;
      pHashTableEntryResult->nMinValue  = nMinValue;
      pHashTableEntryResult->nMaxValue  = nMaxValue;
    } // !if(pThis->nCount >= pThis->nMaxSize)
  } // if(pThis)

  return pHashTableEntryResult;
}
*/

int HashTableBuffer_Broadcast(HashTableBuffer* pThis)
{
  int               nResult;
  int               nResultTmp;

  int               i;
  BOOL              bBreak;

  ProcessorHandler* pProcessorHandler;
  Request*          pRequest;

  nResult     = MPI_SUCCESS;
  nResultTmp  = MPI_SUCCESS;

  ASSERT(pThis->pBroadcastRequestBuffer);
  ASSERT(pThis->pBroadcastRequestBuffer->nReferenceCount == 0);

  pThis->pBroadcastRequestBuffer->bPreventDestruct = TRUE;

  i       = 0; // kh 13.11.06 do not ignore RootMasterId 0
  bBreak  = FALSE;
  while((i < g_pYBWCManagerInstance->nSize) && !bBreak)
  {
    if(i == g_pYBWCManagerInstance->nRank)
    {
      // kh 13.11.06 skip
    } // if(i == g_pYBWCManagerInstance->nRank)
    else
    {
      pProcessorHandler = g_pYBWCManagerInstance->processorHandlerPool[i];
      if(pProcessorHandler->bHashTableBroadcastTarget || !g_pYBWCManagerInstance->pFruitConfiguration->bUseSharedMemoryTranspositionTable)
      {
        if(pProcessorHandler->nBufferedRequestCount < 1 || true) // kh 17.11.06 do not limit the bandwidth at the moment
        {

// kh 07.12.06 pBroadcastRequestBuffer->nReferenceCount is potentially already decremented here
// via the cleanup for completed sends
// to prevent the deletion of pBroadcastRequestBuffer when reaching eventually a reference count of 0 in between
// a lock flag is used
          pRequest          = ProcessorHandler_CreateRequest(pProcessorHandler);

// kh 07.12.06 pBroadcastRequestBuffer->nReferenceCount is incremented here
          nResult           = Request_InitHashTableReplication(pRequest, pThis); 

          if(nResult == MPI_SUCCESS)
          {
            nResult = Request_SendBroadcastBuffer(pRequest);

            if(nResult == MPI_SUCCESS)
            {
            }
            else
            {
              if(g_pYBWCManagerInstance->pFruitConfiguration->nFruitDebugLevel >= FRUIT_DEBUG_LEVEL_ERROR_PRIO_1)
              {
                YBWCManager_Printf(g_pYBWCManagerInstance, "ERROR HashTableBuffer_Broadcast(... failed at Request_SendBroadcastBuffer(..., errorcode = %d\n", nResult);
              }

// kh 13.11.06 save first error only
              if(nResultTmp == MPI_SUCCESS)
              {
                nResultTmp = nResult;
              }

// kh 13.11.06 defensive 
//            bBreak = TRUE;  
            }
          } // if(nResult == MPI_SUCCESS)
          else
          {
            if(g_pYBWCManagerInstance->pFruitConfiguration->nFruitDebugLevel >= FRUIT_DEBUG_LEVEL_ERROR_PRIO_1)
            {
              YBWCManager_Printf(g_pYBWCManagerInstance, "ERROR HashTableBuffer_Broadcast(... failed at Request_InitHashTableReplication(..., errorcode = %d\n", nResult);
            }

// kh 13.11.06 save first error only
            if(nResultTmp == MPI_SUCCESS)
            {
              nResultTmp = nResult;
            }

// kh 13.11.06 defensive
//          bBreak = TRUE; 
          } // !if(nResult == MPI_SUCCESS)
        } // if(pProcessorHandler->nBufferedRequestCount < 1 || true)
        else
        {
          if(g_pYBWCManagerInstance->pFruitConfiguration->nFruitDebugLevel >= FRUIT_DEBUG_LEVEL_INFO_PRIO_2)
          {
            YBWCManager_Printf(g_pYBWCManagerInstance, 
                               "HashTableBuffer_Broadcast(... nBufferedRequestCount for ProcessorHandler <%d> is still %d, send is suppressed\n",
                               pProcessorHandler->nId,
                               pProcessorHandler->nBufferedRequestCount);
          }
        } // !if(pProcessorHandler->nBufferedRequestCount < 1 || true)
      }  // if(pProcessorHandler->bHashTableBroadcastTarget || !g_pYBWCManagerInstance->pFruitConfiguration->bUseSharedMemoryTranspositionTable)
      else
      {

// kh 13.12.06 a) skip transmit to processors sharing another transposition table which have not 
// the bHashTableTarget property (i.e. they have not a transposition table master property for their own node) 

// kh 13.12.06 b) skip transmit to a processor which shares the transposition table

      } // !if(pProcessorHandler->bHashTableBroadcastTarget || !g_pYBWCManagerInstance->pFruitConfiguration->bUseSharedMemoryTranspositionTable)
    } // !if(i == g_pYBWCManagerInstance->nRank)

    i++;
  } // while((i < g_pYBWCManagerInstance->nSize) && !bBreak)

  pThis->pBroadcastRequestBuffer->bPreventDestruct = FALSE;

// kh 13.12.06 assertion does not hold any longer, if the transposition table is shared on nodes with the same name
//ASSERT((pThis->pBroadcastRequestBuffer->nReferenceCount > 0) || (g_pYBWCManagerInstance->nSize == 1));
  
  if(pThis->pBroadcastRequestBuffer->nReferenceCount > 0)
  {
  }
  else
  {
    Request_Destruct(pThis->pBroadcastRequestBuffer); // kh 06.12.06 nobody does it otherwise...
  }

  pThis->pBroadcastRequestBuffer = NULL; // kh 06.12.06 prepare lazy reinitialization

  nResult = nResultTmp;

  return nResult;
}

int HashTableBuffer_FlushWithBroadcast(HashTableBuffer* pThis)
{
  int nResult;

  nResult = HashTableBuffer_Broadcast(pThis);

  pThis->nCount = 0;

  return nResult;
}

void HashTableBuffer_Discard(HashTableBuffer* pThis)
{
  if(pThis->pBroadcastRequestBuffer)
  {
    Request_Destruct(pThis->pBroadcastRequestBuffer); 
  }

  pThis->pBroadcastRequestBuffer = NULL; // kh 08.02.08 prepare lazy reinitialization

  pThis->nCount = 0;
}

int HashTableBuffer_FlushToTranspositionTable(HashTableBuffer* pThis, trans_t* pTranspositionTable)
{
  int             nResult;

  HashTableEntry* pHashTableEntryTmp;

  int             i;

  nResult = MPI_SUCCESS;

  for(i = 0; i < pThis->nCount; i++)
  {
    pHashTableEntryTmp = &pThis->hashTableEntry[i];

    trans_store(pTranspositionTable, 
                pHashTableEntryTmp->nKey, 
                pHashTableEntryTmp->nMove, 
                pHashTableEntryTmp->nDepth, 
                pHashTableEntryTmp->nFlags, 
                pHashTableEntryTmp->nValue);
  } // for(i = 0; i < pThis->nCount; i++)

  pThis->nCount = 0;

  return nResult;
}
