/*******************************************************************************
 * Copyright (c) 2005 - 2015 Profactor GmbH, ACIN, fortiss GmbH
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Thomas Strasser, Alois Zoitl, Gunnar Grabmaier, Gerhard Ebenhofer,
 *    Martin Melik Merkumians, Ingo Hegny
 *      - initial implementation and rework communication infrastructure
 *******************************************************************************/
#include <string.h>
#include "cfb.h"
#include "adapter.h"
#include "resource.h"
#include "utils/criticalregion.h"
#include "if2indco.h"

CCompositeFB::CCompositeFB(CResource *pa_poSrcRes, const SFBInterfaceSpec *pa_pstInterfaceSpec,
    const CStringDictionary::TStringId pa_nInstanceNameId, const SCFB_FBNData * const pa_cpoFBNData,
    TForteByte *pa_acFBConnData, TForteByte *pa_acFBVarsData) :
        CFunctionBlock(pa_poSrcRes, pa_pstInterfaceSpec, pa_nInstanceNameId, pa_acFBConnData, pa_acFBVarsData),
        mIf2InDConns(0),
        m_apoIn2IfDConns(0),
        cm_cpoFBNData(pa_cpoFBNData),
        m_apoEventConnections(0),
        m_apoDataConnections(0),
        mInterface2InternalEventCons(0){

  createInternalFBs();

  createEventConnections();
  createDataConnections();
  setParams();

  //remove adapter-references for CFB
  for(TForteUInt8 i = 0; i < pa_pstInterfaceSpec->m_nNumAdapters; i++){
    if(0 != m_apoAdapters){
      static_cast<CAdapter*>(m_apoAdapters[i])->setParentFB(0, 0);
    }
  }

}

CCompositeFB::~CCompositeFB(){
  if(cm_cpoFBNData->m_nNumFBs){
    for(unsigned int i = 0; i < cm_cpoFBNData->m_nNumFBs; ++i){
      delete m_apoInternalFBs[i];
    }
    delete[] m_apoInternalFBs;
  }

  //only delete the interface to internal event connections all other connections are managed by their source's FBs
  //this has to be done even if we don't have any event connection to ensure correct behavior
  for(unsigned int i = 0; i < m_pstInterfaceSpec->m_nNumEIs; ++i){
    delete mInterface2InternalEventCons[i];
  }
  delete[] mInterface2InternalEventCons;

  if(cm_cpoFBNData->m_nNumEventConnections){
    delete[] m_apoEventConnections;
  }

  if(cm_cpoFBNData->m_nNumDataConnections){
    if(0 != m_apoDataConnections){
      delete[] m_apoDataConnections;
    }
    if(0 != mIf2InDConns){
      delete[] mIf2InDConns;
    }
    if(0 != m_apoIn2IfDConns){
      delete[] m_apoIn2IfDConns;
    }
  }
}

bool CCompositeFB::connectDI(TPortId paDIPortId, CDataConnection *paDataCon){
  bool retVal = false;

  if(cgInternal2InterfaceMarker & paDIPortId){
    paDIPortId = static_cast<TPortId>(paDIPortId & cgInternal2InterfaceRemovalMask);
    if(paDIPortId < m_pstInterfaceSpec->m_nNumDOs){
      m_apoIn2IfDConns[paDIPortId] = paDataCon;
      retVal = true;
    }
  }
  else if(paDIPortId < m_pstInterfaceSpec->m_nNumDIs){
    bool needsCloning = (getDI(paDIPortId)->getDataTypeID() == CIEC_ANY::e_ANY);
    retVal = CFunctionBlock::connectDI(paDIPortId, paDataCon);
    if((true == retVal) && (true == needsCloning) && (0 != paDataCon)
        && (0 != paDataCon->getValue())){
      //if internal connected update the connections data type and maybe internal further connection points
      (mIf2InDConns + paDIPortId)->setValue(getDI(paDIPortId));
      (mIf2InDConns + paDIPortId)->cloneInputInterfaceValue();
    }
  }
  return retVal;
}

bool CCompositeFB::configureGenericDO(TPortId paDOPortId, const CIEC_ANY &paRefValue){
  bool bRetVal = CFunctionBlock::configureGenericDO(paDOPortId, paRefValue);
  if(true == bRetVal && 0 != m_apoIn2IfDConns[paDOPortId]){
    //issue a reconfiguration attempt so that all connection end points in this connection are also correctly configured
    m_apoIn2IfDConns[paDOPortId]->connectToCFBInterface(this, paDOPortId);
  }
  return bRetVal;
}

EMGMResponse CCompositeFB::changeFBExecutionState(EMGMCommandType pa_unCommand){
  EMGMResponse nRetVal = CFunctionBlock::changeFBExecutionState(pa_unCommand);
  for(unsigned int i = 0; ((i < cm_cpoFBNData->m_nNumFBs) && (e_RDY == nRetVal)); ++i){
    if(m_apoInternalFBs[i]){
      nRetVal = m_apoInternalFBs[i]->changeFBExecutionState(pa_unCommand);
    }
  }
  //Update FB parameters that maybe got overwritten by default values of the FB
  if((cg_nMGM_CMD_Reset == pa_unCommand) && (e_IDLE == getState())){
    setParams();
  }
  return nRetVal;
}

#ifdef FORTE_SUPPORT_MONITORING

CFunctionBlock *CCompositeFB::getFB(forte::core::TNameIdentifier::CIterator &paNameListIt){
  CFunctionBlock *retVal = 0;
  for(unsigned int i = 0; i < cm_cpoFBNData->m_nNumFBs; ++i){
    if(m_apoInternalFBs[i]->getInstanceNameId() == *paNameListIt){
      if(paNameListIt.isLastEntry()){
        retVal = m_apoInternalFBs[i];
      } else {
         //we are looking for a child of this fB
        ++paNameListIt;
        retVal = m_apoInternalFBs[i]->getFB(paNameListIt);
      }
    }
  }
  if(0 == retVal){
    //check if it is an adapter of the function block
    retVal = CFunctionBlock::getFB(paNameListIt);
  }
  return retVal;
}

#endif

CIEC_ANY *CCompositeFB::getVar(CStringDictionary::TStringId *paNameList,
    unsigned int paNameListSize){
  CIEC_ANY *retVal = 0;

  if(1 > paNameListSize){
    for(unsigned int i = 0; i < cm_cpoFBNData->m_nNumFBs; ++i){
      if(*paNameList == m_apoInternalFBs[i]->getInstanceNameId()){
        paNameList++;
        paNameListSize--;
        retVal = m_apoInternalFBs[i]->getVar(paNameList, paNameListSize);
        break;
      }
    }
  }
  else{
    retVal = CFunctionBlock::getVar(paNameList, paNameListSize);
  }

  return retVal;
}

void CCompositeFB::executeEvent(int pa_nEIID){
  if(cgInternal2InterfaceMarker & pa_nEIID){
    sendInternal2InterfaceOutputEvent(static_cast<TEventID>(pa_nEIID
        & cgInternal2InterfaceRemovalMask));
  }
  else{
    if(pa_nEIID < m_pstInterfaceSpec->m_nNumEIs && 0 != mInterface2InternalEventCons[pa_nEIID]){
      mInterface2InternalEventCons[pa_nEIID]->triggerEvent(*m_poInvokingExecEnv);
    }
  }
}

void CCompositeFB::sendInternal2InterfaceOutputEvent(int pa_nEOID){
  //handle sampling of internal 2 interface data connections
  if((pa_nEOID < m_pstInterfaceSpec->m_nNumEOs) && (0 != m_pstInterfaceSpec->m_anEOWithIndexes) &&
    (-1 != m_pstInterfaceSpec->m_anEOWithIndexes[pa_nEOID])){
      const TDataIOID *poEOWithStart =
          &(m_pstInterfaceSpec->m_anEOWith[m_pstInterfaceSpec->m_anEOWithIndexes[pa_nEOID]]);

      // TODO think on this lock
      {
        CCriticalRegion criticalRegion(getResource().m_oResDataConSync);
        for(int i = 0; poEOWithStart[i] != 255; ++i){
          if(0 != m_apoIn2IfDConns[poEOWithStart[i]]){
            m_apoIn2IfDConns[poEOWithStart[i]]->readData(getDO(poEOWithStart[i]));
          }
        }
      }
  }

  sendOutputEvent(pa_nEOID);
}

void CCompositeFB::createInternalFBs(){
  if(cm_cpoFBNData->m_nNumFBs){
    m_apoInternalFBs = new TFunctionBlockPtr[cm_cpoFBNData->m_nNumFBs];
    for(unsigned int i = 0; i < cm_cpoFBNData->m_nNumFBs; ++i){
      m_apoInternalFBs[i] =
          CTypeLib::createFB(cm_cpoFBNData->m_pstFBInstances[i].m_nFBInstanceNameId, cm_cpoFBNData->m_pstFBInstances[i].m_nFBTypeNameId, getResourcePtr());
    }
  }
}

void CCompositeFB::createEventConnections(){
  prepareIf2InEventCons();  //the interface to internal event connections are needed even if they are not connected therefore we have to create them correctly in any case
  if(0 != cm_cpoFBNData->m_nNumEventConnections){
    m_apoEventConnections = new CEventConnection *[cm_cpoFBNData->m_nNumEventConnections]; //TODO for a major revison this list could be ommited but requires a change in the faned out connections

    CFunctionBlock *poSrcFB;
    CFunctionBlock *poDstFB;
    for(unsigned int i = 0; i < cm_cpoFBNData->m_nNumEventConnections; ++i){
      const SCFB_FBConnectionData *cpstCurrentConn = &(cm_cpoFBNData->m_pstEventConnections[i]);
      //FIXME implement way to inform FB creator that creation failed
      poSrcFB = getFunctionBlock(cpstCurrentConn->m_nSrcFBNum);
      poDstFB = getFunctionBlock(cpstCurrentConn->m_nDstFBNum);

      if((0 != poSrcFB) && (0 != poDstFB)){
        if(this == poSrcFB){
          m_apoEventConnections[i] =
              mInterface2InternalEventCons[getEIID(cpstCurrentConn->m_nSrcId)];
        }
        else{
          m_apoEventConnections[i] = poSrcFB->getEOConnection(cpstCurrentConn->m_nSrcId);
        }
        establishConnection(m_apoEventConnections[i], poDstFB, cpstCurrentConn->m_nDstId);
      }
      else{
        DEVLOG_ERROR("Could not create event connection in CFB");
      }
    }
    //now handle the fanned out connections
    for(unsigned int i = 0; i < cm_cpoFBNData->m_nNumFannedOutEventConnections; ++i){
      const SCFB_FBFannedOutConnectionData *cpstCurrentFannedConn = &(cm_cpoFBNData->m_pstFannedOutEventConnections[i]);
      poDstFB = getFunctionBlock(cpstCurrentFannedConn->m_nDstFBNum);
      establishConnection(m_apoEventConnections[cpstCurrentFannedConn->m_nConnectionNum],
          poDstFB, cpstCurrentFannedConn->m_nDstId);
    }
  }
}

void CCompositeFB::prepareIf2InEventCons(){
  if(0 != m_pstInterfaceSpec->m_nNumEIs){
    mInterface2InternalEventCons = new TEventConnectionPtr[m_pstInterfaceSpec->m_nNumEIs];
    //FIXME find a way to avoid that each connection has to be allocated separately
    for(TPortId i = 0; i < m_pstInterfaceSpec->m_nNumEIs; i++){
      mInterface2InternalEventCons[i] = new CEventConnection(this, i);
    }
  }
}

void CCompositeFB::establishConnection(CConnection *paCon, CFunctionBlock *paDstFb,
    CStringDictionary::TStringId paDstNameId) {
  if(this == paDstFb){
    paCon->connectToCFBInterface(paDstFb, paDstNameId);
  }
  else{
    paCon->connect(paDstFb, paDstNameId);
  }
}

void CCompositeFB::createDataConnections(){
  if(cm_cpoFBNData->m_nNumDataConnections){
    if(m_pstInterfaceSpec->m_nNumDIs){
      prepareIf2InDataCons();
    }
    if(m_pstInterfaceSpec->m_nNumDOs){
      m_apoIn2IfDConns = new CDataConnection *[m_pstInterfaceSpec->m_nNumDOs];
      for(TPortId i = 0; i < m_pstInterfaceSpec->m_nNumDOs; i++){
        m_apoIn2IfDConns[i] = 0;
      }
    }

    m_apoDataConnections = new CDataConnection *[cm_cpoFBNData->m_nNumDataConnections];

    CFunctionBlock *poSrcFB;
    CFunctionBlock *poDstFB;
    for(unsigned int i = 0; i < cm_cpoFBNData->m_nNumDataConnections; ++i){
      const SCFB_FBConnectionData *cpstCurrentConn = &(cm_cpoFBNData->m_pstDataConnections[i]);
      //FIXME implement way to inform FB creator that creation failed
      poSrcFB = getFunctionBlock(cpstCurrentConn->m_nSrcFBNum);
      poDstFB = getFunctionBlock(cpstCurrentConn->m_nDstFBNum);

      if((0 != poSrcFB) && (0 != poDstFB)){
        if(this == poSrcFB){
          m_apoDataConnections[i] = &(mIf2InDConns[getDIID(cpstCurrentConn->m_nSrcId)]);
        }
        else{
          m_apoDataConnections[i] = poSrcFB->getDOConnection(cpstCurrentConn->m_nSrcId);
        }
        establishConnection(m_apoDataConnections[i], poDstFB, cpstCurrentConn->m_nDstId);
      }
      else{
        DEVLOG_ERROR("Could not create event connection in CFB");
      }
    }
    //now handle the fanned out connections
    for(unsigned int i = 0; i < cm_cpoFBNData->m_nNumFannedOutDataConnections; ++i){
      const SCFB_FBFannedOutConnectionData *cpstCurrentFannedConn = &(cm_cpoFBNData->m_pstFannedOutDataConnections[i]);
      poDstFB = getFunctionBlock(cpstCurrentFannedConn->m_nDstFBNum);

      establishConnection(m_apoDataConnections[cpstCurrentFannedConn->m_nConnectionNum],
          poDstFB, cpstCurrentFannedConn->m_nDstId);
    }
  }
}

void CCompositeFB::prepareIf2InDataCons(){
  mIf2InDConns = new CInterface2InternalDataConnection[m_pstInterfaceSpec->m_nNumDIs];
  for(TPortId i = 0; i < m_pstInterfaceSpec->m_nNumDIs; i++){
    (mIf2InDConns + i)->setSource(this, i);
  }
}

void CCompositeFB::setParams() {
  for(unsigned int i = 0; i < cm_cpoFBNData->m_nNumParams; ++i){
    const SCFB_FBParameter *pstCurrentParam = &(cm_cpoFBNData->m_pstParams[i]);
    CIEC_ANY *poDI =
        m_apoInternalFBs[pstCurrentParam->m_nFBNum]->getDataInput(pstCurrentParam->m_nDINameID);
    if(0 != poDI){
      poDI->fromString(pstCurrentParam->m_acParamValue);
    }
    else{
      DEVLOG_ERROR("Could not get date input for setting a parameter");
    }
  }
}

CFunctionBlock *CCompositeFB::getFunctionBlock(int pa_nFBNum){
  CFunctionBlock *poRetVal = 0;
  if(-1 == pa_nFBNum){
    poRetVal = this;
  }
  else if(0 <= pa_nFBNum){
    if(scm_nAdapterMarker == (scm_nAdapterMarker & pa_nFBNum)){
      pa_nFBNum &= scm_nAdapterFBRange;
      if(pa_nFBNum < m_pstInterfaceSpec->m_nNumAdapters){
        poRetVal = m_apoAdapters[pa_nFBNum];
      }
    }
    else{
      if(static_cast<unsigned int>(pa_nFBNum) < cm_cpoFBNData->m_nNumFBs){
        poRetVal = m_apoInternalFBs[pa_nFBNum];
      }
    }
  }
  return poRetVal;
}
