/*******************************************************************************
 * Copyright (c) 2006-2014 ACIN, Profactor GmbH, fortiss GmbH
 *                      2018 Johannes Kepler University
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Rene Smodic, Alois Zoitl, Michael Hofmann, Martin Melik Merkumians,
 *    Patrick Smejkal
 *      - initial implementation and rework communication infrastructure
 *    Alois Zoitl - introduced new CGenFB class for better handling generic FBs
 *******************************************************************************/
#include <fortenew.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "commfb.h"
#ifdef FORTE_ENABLE_GENERATED_SOURCE_CPP
#include "commfb_gen.cpp"
#endif
#include "../resource.h"
#include "comlayer.h"
#include "comlayersmanager.h"

using namespace forte::com_infra;

const CStringDictionary::TStringId CCommFB::scm_aunRequesterEventInputNameIds[2] = { g_nStringIdINIT, g_nStringIdREQ };
const CStringDictionary::TStringId CCommFB::scm_aunRequesterEventOutputNameIds[2] = { g_nStringIdINITO, g_nStringIdCNF };

const CStringDictionary::TStringId CCommFB::scm_aunResponderEventInputNameIds[2] = { g_nStringIdINIT, g_nStringIdRSP };
const CStringDictionary::TStringId CCommFB::scm_aunResponderEventOutputNameIds[2] = { g_nStringIdINITO, g_nStringIdIND };

const TForteInt16 CCommFB::scm_anEIWithIndexes[] = { 0, 3 };
const TForteInt16 CCommFB::scm_anEOWithIndexes[] = { 0, 3, -1 };

CCommFB::CCommFB(const CStringDictionary::TStringId pa_nInstanceNameId, CResource *pa_poSrcRes, forte::com_infra::EComServiceType pa_eCommServiceType) :
  CBaseCommFB(pa_nInstanceNameId, pa_poSrcRes, pa_eCommServiceType) {
}

CCommFB::~CCommFB() {

}

EMGMResponse CCommFB::changeFBExecutionState(EMGMCommandType pa_unCommand) {
  EMGMResponse retVal = CEventSourceFB::changeFBExecutionState(pa_unCommand);
  if ((e_RDY == retVal) && (cg_nMGM_CMD_Kill == pa_unCommand)) {
    //when we are killed we'll close the connection so that it can safely be opened again after an reset
    closeConnection();
  }
  return retVal;
}

void CCommFB::executeEvent(int pa_nEIID) {
  EComResponse resp = e_Nothing;

  switch (pa_nEIID) {
  case scm_nEventINITID:
    if (true == QI()) {
      resp = openConnection();
    }
    else {
      closeConnection();
      resp = e_InitTerminated;
    }
    break;
  case scm_nSendNotificationEventID:
    resp = sendData();
    break;
  case cg_nExternalEventID:
    resp = receiveData();
    break;
  default:
    break;
  }

  if (resp & e_Terminated) {
    if (m_eCommServiceType == e_Server) {
      //servers will not send information on client termination and should silently start to listen again
      resp = e_Nothing;
    }
    else {
      //subscribers and clients will close the connection and inform the user
      closeConnection();
    }
  }

  if (e_Nothing != resp) {
    STATUS() = scm_sResponseTexts[resp & 0xF];
    QO() = !(resp & scg_unComNegative);

    if (scg_unINIT & resp) {
      sendOutputEvent(scm_nEventINITOID);
    }
    else {
      sendOutputEvent(scm_nReceiveNotificationEventID);
    }
  }
}

EComResponse CCommFB::sendData() {
  EComResponse resp = e_Nothing;
  if (true == QI()) {
    if (m_eCommServiceType != e_Subscriber) {
      if (0 != m_poTopOfComStack) {
        resp = m_poTopOfComStack->sendData(static_cast<void*>(getSDs()), m_pstInterfaceSpec->m_nNumDIs - 2);
        if ((resp == e_ProcessDataOk) && (m_eCommServiceType != e_Publisher)) {
          // client and server will not directly send a cnf/ind event
          resp = e_Nothing;
        }
      }
      else {
        resp = e_ProcessDataNoSocket;
      }
    }
  }
  else {
    resp = e_ProcessDataInhibited; // we are not allowed to send data
  }
  return resp;
}

bool CCommFB::createInterfaceSpec(const char *paConfigString, SFBInterfaceSpec &paInterfaceSpec){
  TIdentifier tempstring;
  char *sParamA = 0;
  char *sParamB = 0;

  paInterfaceSpec.m_nNumEIs = 2;
  paInterfaceSpec.m_nNumEOs = 2;

  memcpy(tempstring, paConfigString, cg_nIdentifierLength);
  tempstring[cg_nIdentifierLength] = '\0';
  size_t inlength = strlen(tempstring);
  size_t i;
  for (i = 0; i < inlength - 1; i++) { // search first underscore
    if (tempstring[i] == '_') {
      sParamA = sParamB = &(tempstring[i + 1]);
      break;
    }
  }
  if (0 != sParamA) // search for 2nd underscore
    for (i = i + 1; i < inlength - 1; i++) {
      if (tempstring[i] == '_') {
        tempstring[i] = '\0';
        sParamB = &(tempstring[i + 1]);
        break;
      }
    }

  if (0 == sParamB){ // no underscore found
    return false;
  }

  configureDIs(sParamA, paInterfaceSpec);
  configureDOs(sParamB, paInterfaceSpec);

  if (forte::com_infra::e_Requester == (forte::com_infra::e_Requester & m_eCommServiceType)) {
    paInterfaceSpec.m_aunEINames = const_cast<CStringDictionary::TStringId*>(scm_aunRequesterEventInputNameIds);
    paInterfaceSpec.m_aunEONames = const_cast<CStringDictionary::TStringId*>(scm_aunRequesterEventOutputNameIds);
  }
  else {
    if (forte::com_infra::e_Responder == (forte::com_infra::e_Responder & m_eCommServiceType)) {
      paInterfaceSpec.m_aunEINames = const_cast<CStringDictionary::TStringId*>(scm_aunResponderEventInputNameIds);
      paInterfaceSpec.m_aunEONames = const_cast<CStringDictionary::TStringId*>(scm_aunResponderEventOutputNameIds);
    }
  }
  paInterfaceSpec.m_anEIWithIndexes = scm_anEIWithIndexes;
  paInterfaceSpec.m_anEOWithIndexes = scm_anEOWithIndexes;

  return true;
}

void CCommFB::configureDIs(const char *paDIConfigString, SFBInterfaceSpec &paInterfaceSpec){
  CStringDictionary::TStringId* diDataTypeNames;
  CStringDictionary::TStringId* diNames;
  TDataIOID* eiWith;

  paInterfaceSpec.m_nNumDIs = 2;

  if (forte::com_infra::e_DataInputs == (forte::com_infra::e_DataInputs & m_eCommServiceType)) {
      //TODO: Check range of sParamA
      paInterfaceSpec.m_nNumDIs = static_cast<TForteUInt8>(paInterfaceSpec.m_nNumDIs + forte::core::util::strtol(paDIConfigString, 0, 10));
      diDataTypeNames = new CStringDictionary::TStringId[paInterfaceSpec.m_nNumDIs];
      diNames = new CStringDictionary::TStringId[paInterfaceSpec.m_nNumDIs];
      eiWith = new TDataIOID[paInterfaceSpec.m_nNumDIs - 2 + scmMinWithLength];

      generateGenericDataPointArrays("SD_", &(diDataTypeNames[2]), &(diNames[2]), paInterfaceSpec.m_nNumDIs - 2);
    }
    else {
      diDataTypeNames = new CStringDictionary::TStringId[paInterfaceSpec.m_nNumDIs];
      diNames = new CStringDictionary::TStringId[paInterfaceSpec.m_nNumDIs];
      eiWith = new TDataIOID[scmMinWithLength];
    }
    paInterfaceSpec.m_aunDIDataTypeNames = diDataTypeNames;
    paInterfaceSpec.m_aunDINames = diNames;
    paInterfaceSpec.m_anEIWith = eiWith;

    eiWith[0] = 0;
    eiWith[1] = 1;
    eiWith[2] = scmWithListDelimiter;
    eiWith[3] = 0;
    eiWith[4] = 1;

    size_t i;
    for (i = 0; i < paInterfaceSpec.m_nNumDIs - 2U; i++) {
      eiWith[i + 5U] = static_cast<TForteUInt8>(i + 2U);
    }
    eiWith[i + 5U] = scmWithListDelimiter;

    diDataTypeNames[0] = g_nStringIdBOOL;
    diNames[0] = g_nStringIdQI;
  #ifdef FORTE_USE_WSTRING_DATATYPE
    diDataTypeNames[1] = g_nStringIdWSTRING;
  #else
    diDataTypeNames[1] = g_nStringIdSTRING;
  #endif
    diNames[1] = g_nStringIdID;
}

void CCommFB::configureDOs(const char *paDOConfigString, SFBInterfaceSpec &paInterfaceSpec){
  CStringDictionary::TStringId* doDataTypeNames;
  CStringDictionary::TStringId* doNames;
  TDataIOID* eoWith;

  paInterfaceSpec.m_nNumDOs = 2;

  if(forte::com_infra::e_DataOutputs == (forte::com_infra::e_DataOutputs & m_eCommServiceType)){
    //TODO: Check range of sParamA
    paInterfaceSpec.m_nNumDOs = static_cast<TForteUInt8>(paInterfaceSpec.m_nNumDOs + forte::core::util::strtol(paDOConfigString, 0, 10));
    doDataTypeNames  = new CStringDictionary::TStringId[paInterfaceSpec.m_nNumDOs];
    doNames = new CStringDictionary::TStringId[paInterfaceSpec.m_nNumDOs];
    eoWith = new TDataIOID[paInterfaceSpec.m_nNumDOs - 2 + scmMinWithLength];

    generateGenericDataPointArrays("RD_", &(doDataTypeNames[2]), &(doNames[2]), paInterfaceSpec.m_nNumDOs - 2);
  }
  else{
    doDataTypeNames = new CStringDictionary::TStringId[paInterfaceSpec.m_nNumDOs];
    doNames = new CStringDictionary::TStringId[paInterfaceSpec.m_nNumDOs];
    eoWith = new TDataIOID[scmMinWithLength];
  }

  paInterfaceSpec.m_aunDONames = doNames;
  paInterfaceSpec.m_aunDODataTypeNames = doDataTypeNames;
  paInterfaceSpec.m_anEOWith = eoWith;

  eoWith[0] = 0;
  eoWith[1] = 1;
  eoWith[2] = scmWithListDelimiter;
  eoWith[3] = 0;
  eoWith[4] = 1;

  size_t i;
  for(i = 0; i < paInterfaceSpec.m_nNumDOs - 2U; i++){
    eoWith[i + 5U] = static_cast<TForteUInt8>(i + 2U);
  }
  eoWith[i + 5U] = scmWithListDelimiter;

  doDataTypeNames[0] = g_nStringIdBOOL;
  doNames[0] = g_nStringIdQO;
#ifdef FORTE_USE_WSTRING_DATATYPE
  doDataTypeNames[1] = g_nStringIdWSTRING;
#else
  doDataTypeNames[1] = g_nStringIdSTRING;
#endif
  doNames[1] = g_nStringIdSTATUS;

}

EComResponse CCommFB::receiveData() {
  EComResponse eResp;
  EComResponse eRetVal = e_Nothing;

  const unsigned int comInterruptQueueCountCopy = m_unComInterruptQueueCount;
  for (size_t i = 0; i < comInterruptQueueCountCopy; ++i) {
    eResp = m_apoInterruptQueue[i]->processInterrupt();
    if (eResp > eRetVal) {
      eRetVal = eResp;
    }
  }
  m_unComInterruptQueueCount -= comInterruptQueueCountCopy;
  for (unsigned int i = 0; i < m_unComInterruptQueueCount; ++i) {
    m_apoInterruptQueue[i] = m_apoInterruptQueue[i + comInterruptQueueCountCopy];
  }

  return eRetVal;
}

char *CCommFB::getDefaultIDString(const char *paID) {
  return buildIDString("fbdk[].ip[", paID, "]");
}
