/*******************************************************************************
 * Copyright (c) 2012 -2014 AIT, 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:
 *   Filip Andren, Alois Zoitl - initial API and implementation and/or initial documentation
 *******************************************************************************/
#include "opcconnection.h"
#include "opcconnectionimpl.h"
#include "opceventhandler.h"
#include "opcprocessvar.h"
#include "Cmd_AddConnection.h"
#include "Cmd_AddOPCProcessVar.h"
#include "Cmd_SetProcessVarValue.h"

using namespace forte::com_infra;

COpcConnection::COpcConnection(const char *pa_acHost, const char *pa_acServerName, COpcEventHandler* pa_eventHandler) :
    m_nGroupCount(0), m_eConnectionEvent(e_Disconnected), m_acHost(pa_acHost), m_acServerName(pa_acServerName), m_acGroupName(0), m_nReqUpdateRate(0), m_nRealUpdateRate(0), m_nDeadBand(0), m_bIsConnected(false), m_bBlockingConnect(false),
   m_eventHandler(pa_eventHandler){
  m_pOpcConnectionImpl = new COpcConnectionImpl(pa_acHost, pa_acServerName, this);
}

COpcConnection::~COpcConnection(){
  delete m_pOpcConnectionImpl;
}

void COpcConnection::addGroup(const char* pa_acGroupName, unsigned long pa_nReqUpdateRate, float pa_nDeadBand, CComLayer* pa_pComCallback){
  m_oSync.lock();
  m_lOpcGroupMapList.pushBack(new SOpcGroupMap(pa_acGroupName, m_eventHandler->addComCallback(pa_pComCallback)));
  m_oSync.unlock();

  m_pOpcConnectionImpl->addGroup(pa_acGroupName, pa_nReqUpdateRate, pa_nDeadBand);

  m_nGroupCount++;
}

void COpcConnection::removeGroup(const char* pa_acGroupName){
  m_oSync.lock();
  TOpcGroupMapList::Iterator itDelete = m_lOpcGroupMapList.begin();
  TOpcGroupMapList::Iterator it_group = m_lOpcGroupMapList.begin();
  TOpcGroupMapList::Iterator itEnd_group = m_lOpcGroupMapList.end();

  if(it_group != itEnd_group){
    if(strcmp(it_group->m_acGroupName, pa_acGroupName) == 0){
      m_lOpcGroupMapList.popFront();
      m_nGroupCount--;
      m_oSync.unlock();
      return;
    }
    ++it_group;
    while(it_group != itEnd_group){
      if(strcmp(it_group->m_acGroupName, pa_acGroupName) == 0){
        m_lOpcGroupMapList.eraseAfter(itDelete);
        m_nGroupCount--;
        m_oSync.unlock();
        return;
      }

      ++itDelete;
      ++it_group;
    }
  }

  m_oSync.unlock();
}

int COpcConnection::send_connect(){
  switch (m_eConnectionEvent){
    case e_Disconnected:
      m_eConnectionEvent = e_Connecting;
      m_eventHandler->sendCommand(new CCmd_AddConnection(m_pOpcConnectionImpl));
      return 0;
    case e_Connecting:
      return 0;
    default: // all other connection states
      return 1;
  }
}

int COpcConnection::send_connect(bool pa_bBlocking){
  m_bBlockingConnect = pa_bBlocking;
  if(pa_bBlocking && !m_bIsConnected){
    m_pOpcConnectionImpl->connect();
    return 1;
  }

  return send_connect();
}

int COpcConnection::send_addItem(COpcProcessVar* pa_pNewItem){
  m_oSync.lock();
  TOpcGroupMapList::Iterator itEnd_group = m_lOpcGroupMapList.end();
  for(TOpcGroupMapList::Iterator it_group = m_lOpcGroupMapList.begin(); it_group != itEnd_group; ++it_group){
    if(strcmp(it_group->m_acGroupName, pa_pNewItem->getItemGroupName()) == 0){
      if(pa_pNewItem->getItemFunction() == COpcProcessVar::e_FBOutput){

        //check if item already added
        TItemDataList::Iterator itEnd_item = (*it_group)->m_lReadItemsList.end();
        for(TItemDataList::Iterator it_item = (*it_group)->m_lReadItemsList.begin(); it_item != itEnd_item; ++it_item){
          if(strcmp(it_item->m_acItemName, pa_pNewItem->getItemName()) == 0){
            m_oSync.unlock();
            return 1;
          }
        }

        it_group->m_lReadItemsList.pushBack(new SOpcItemData(pa_pNewItem->getItemName()));
        break;

      }
      else if(pa_pNewItem->getItemFunction() == COpcProcessVar::e_FBInput){
        //check if item already added
        TItemDataList::Iterator itEnd_item = (*it_group)->m_lWriteItemsList.end();
        for(TItemDataList::Iterator it_item = (*it_group)->m_lWriteItemsList.begin(); it_item != itEnd_item; ++it_item){
          if(strcmp(it_item->m_acItemName, pa_pNewItem->getItemName()) == 0){
            m_oSync.unlock();
            return 1;
          }
        }

        it_group->m_lWriteItemsList.pushBack(new SOpcItemData(pa_pNewItem->getItemName()));
        break;

      }
    }
  }
  m_oSync.unlock();

  if(m_eConnectionEvent == e_Connected){
    m_eventHandler->sendCommand(new CCmd_AddOPCProcessVar(m_pOpcConnectionImpl, pa_pNewItem));

    return 0;
  }

  return -1;
}

int COpcConnection::send_sendItemData(COpcProcessVar* pa_pItem){
  if(pa_pItem->getIsActive()) {
    m_eventHandler->sendCommand(new CCmd_SetProcessVarValue(pa_pItem));
  }
  return 0;
}

void COpcConnection::response_connect(bool pa_bConnectionState){
  m_bIsConnected = pa_bConnectionState;
  if(pa_bConnectionState) {
    m_eConnectionEvent = e_Connected;
  } else {
    m_eConnectionEvent = e_ConnectionFailed;
  }
    //m_eConnectionEvent = e_Disconnected;

  if(!m_bBlockingConnect){
    m_oSync.lock();

    TOpcGroupMapList::Iterator itEnd = m_lOpcGroupMapList.end();
    for(TOpcGroupMapList::Iterator it = m_lOpcGroupMapList.begin(); it != itEnd; ++it){
      m_eventHandler->executeComCallback((*it)->m_nCallbackDesc);
    }

    m_oSync.unlock();
  }
}

void COpcConnection::response_dataReceived(const char *pa_acGroupName, TItemDataList & pa_lItemDataList){
  // Loop through OpcGroups
  m_oSync.lock();
  TOpcGroupMapList::Iterator itEnd_group = m_lOpcGroupMapList.end();
  for(TOpcGroupMapList::Iterator it_group = m_lOpcGroupMapList.begin(); it_group != itEnd_group; ++it_group){

    if(strcmp(it_group->m_acGroupName, pa_acGroupName) == 0){

      // Loop through OpcItems in OpcGroup
      TItemDataList::Iterator itEnd_item = (*it_group)->m_lReadItemsList.end();
      for(TItemDataList::Iterator it_item = (*it_group)->m_lReadItemsList.begin(); it_item != itEnd_item; ++it_item){

        // Loop through OpcItems in ItemDataList
        TItemDataList::Iterator itEnd_newItem = pa_lItemDataList.end();
        TItemDataList::Iterator itErase = pa_lItemDataList.begin();
        for(TItemDataList::Iterator it_newItem = pa_lItemDataList.begin(); it_newItem != itEnd_newItem; ++it_newItem){

          if(strcmp(it_newItem->m_acItemName, it_item->m_acItemName) == 0){
            it_item->m_oItemData = it_newItem->m_oItemData;

            if(it_newItem == pa_lItemDataList.begin()) {
              pa_lItemDataList.popFront();
            } else {
              pa_lItemDataList.eraseAfter(itErase);
            }

            break;
          }

          if(it_newItem != pa_lItemDataList.begin()) {
            ++itErase;
          }
        }
          if(pa_lItemDataList.isEmpty()) {
            break;
          }
      }

      // Change state
      m_eConnectionEvent = e_DataReceived;

      // Notify Com Layer
      m_eventHandler->executeComCallback(it_group->m_nCallbackDesc);
      break;
    }
  }
  m_oSync.unlock();
}

void COpcConnection::response_itemAdded(COpcProcessVar* pa_pOpcItem){
  // Loop through OpcGroups
  m_oSync.lock();
  TOpcGroupMapList::Iterator itEnd_group = m_lOpcGroupMapList.end();
  for(TOpcGroupMapList::Iterator it_group = m_lOpcGroupMapList.begin(); it_group != itEnd_group; ++it_group){
    if(strcmp(it_group->m_acGroupName, pa_pOpcItem->getItemGroupName()) == 0){
      // Change state
      if (pa_pOpcItem->getIsActive()) {
        m_eConnectionEvent = e_ItemAddedOk;
      } else {
        m_eConnectionEvent = e_ItemAddedFailed;
      }
      // Notify Com Layer
      m_eventHandler->executeComCallback(it_group->m_nCallbackDesc);
      break;
    }
  }
  m_oSync.unlock();
}

int COpcConnection::receiveData(const char* pa_acGroupName, TOpcProcessVarList * pa_lOpcProcessVarList){
  int nrData = 0;
  // TODO Case when lists do not match in size

  m_oSync.lock();
  TOpcGroupMapList::Iterator itEnd_group = m_lOpcGroupMapList.end();
  for(TOpcGroupMapList::Iterator it_group = m_lOpcGroupMapList.begin(); it_group != itEnd_group; ++it_group){

    if(strcmp(it_group->m_acGroupName, pa_acGroupName) == 0){

      TItemDataList::Iterator itEnd_item = it_group->m_lReadItemsList.end();
      TItemDataList::Iterator it_item = it_group->m_lReadItemsList.begin();

      TOpcProcessVarList::Iterator itEnd_procVar = pa_lOpcProcessVarList->end();
      TOpcProcessVarList::Iterator it_procVar = pa_lOpcProcessVarList->begin();

      while(it_procVar != itEnd_procVar && it_item != itEnd_item){

        it_procVar->setNewValue(it_item->m_oItemData);

        nrData++;

        ++it_procVar;
        ++it_item;
      }

      break;
    }

  }
  m_oSync.unlock();
  return nrData;
}

