/*******************************************************************************
 * Copyright (c) 2016 - 2018 Johannes Messmer (admin@jomess.com), 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:
 *   Johannes Messmer - initial API and implementation and/or initial documentation
 *   Jose Cabral - Cleaning of namespaces
 *******************************************************************************/

#include "slave.h"
#include <handler/bus.h>
#include <io/mapper/io_mapper.h>
#include <processinterface.h>
#include "criticalregion.h"

const int EmbrickSlaveHandler::scmMaxUpdateErrors = 50;

EmbrickSlaveHandler::EmbrickSlaveHandler(EmbrickBusHandler* paBus, int paAddress, EmbrickSlaveInitPackage paInit) :
    mDelegate(0), mAddress(paAddress), mType((SlaveType) paInit.mDeviceId), mBus(paBus), mDataSendLength(paInit.mDataSendLength),
        mDataReceiveLength(paInit.mDataReceiveLength), mStatus(NotInitialized), mOldStatus(NotInitialized) {
  mUpdateSendImage = new unsigned char[mDataSendLength];
  mUpdateReceiveImage = new unsigned char[mDataReceiveLength];
  mUpdateReceiveImageOld = new unsigned char[mDataReceiveLength];

  memset(mUpdateSendImage, 0, mDataSendLength);
  memset(mUpdateReceiveImage, 0, mDataReceiveLength);
  memset(mUpdateReceiveImageOld, 0, mDataReceiveLength);

  mUpdateErrorCounter = 0;

  // Default config
  mConfig.mUpdateInterval = 25;
}

EmbrickSlaveHandler::~EmbrickSlaveHandler() {
  dropHandles();

  delete mUpdateSendImage;
  delete mUpdateReceiveImage;
  delete mUpdateReceiveImageOld;

  if(mDelegate != 0) {
    mDelegate->onSlaveDestroy();
  }
}

void EmbrickSlaveHandler::setConfig(Config paConfig) {
  if(paConfig.mUpdateInterval < 20) {
    DEVLOG_WARNING("embrick[EmbrickSlaveHandler]: Configured UpdateInterval not in range (>= 20). Set to 25.\n");
    paConfig.mUpdateInterval = 25;
  }

  this->mConfig = paConfig;
}

EmbrickSlaveHandler* EmbrickSlaveHandler::sendInit(EmbrickBusHandler* paBus, int paAddress) {
  EmbrickMasterInitPackage masterInit;
  masterInit.mSlaveAddress = (unsigned char) paAddress;
  masterInit.mSyncGapMultiplicator = SyncGapMultiplicator;

  unsigned char sendBuffer[sizeof(EmbrickMasterInitPackage)];
  unsigned char receiveBuffer[sizeof(EmbrickSlaveInitPackage)];

  masterInit.toBuffer(sendBuffer);

  // Send init via broadcast. Due to the sequential slave select activation, only one slave will respond.
  if(!paBus->broadcast(EmbrickBusHandler::Init, sendBuffer, sizeof(EmbrickMasterInitPackage), receiveBuffer, sizeof(EmbrickSlaveInitPackage))) {
    return 0;
  }

  EmbrickSlaveInitPackage initPackage = EmbrickSlaveInitPackage::fromBuffer(receiveBuffer);

  // Alter the value of data receive length -> the status byte is handled in the BusHandler
  initPackage.mDataReceiveLength--;

  DEVLOG_INFO("emBrick[EmbrickSlaveHandler]: ID - %d, ReceiveLength - %d, SendLength - %d, Producer - %d \n", initPackage.mDeviceId,
    initPackage.mDataReceiveLength, initPackage.mDataSendLength, initPackage.mProducerId);

  // Return slave instance
  return new EmbrickSlaveHandler(paBus, paAddress, initPackage);
}

int EmbrickSlaveHandler::update() {
  // Send update request to bus
  if(!mBus->transfer(mAddress, EmbrickBusHandler::Data, mUpdateSendImage, mDataSendLength, mUpdateReceiveImage, mDataReceiveLength, &mStatus, &mUpdateMutex)) {
    mUpdateErrorCounter++;
    if(mUpdateErrorCounter % 10 == 0) {
      DEVLOG_WARNING("emBrick[EmbrickSlaveHandler]: Slave %d reached transfer error threshold - %d out of %d\n", mAddress, mUpdateErrorCounter,
        scmMaxUpdateErrors);
    }
    return mUpdateErrorCounter <= scmMaxUpdateErrors ? 0 : -1;
  }

  // forte::core::IO::IOHandle the received image
  {
    CCriticalRegion criticalRegion(mHandleMutex);
    TSlaveHandleList::Iterator itEnd = mInputs.end();
    for(TSlaveHandleList::Iterator it = mInputs.begin(); it != itEnd; ++it) {
      if((*it)->hasObserver() && !(*it)->equal(mUpdateReceiveImageOld)) {
        // Inform Process Interface about change
        (*it)->onChange();
      }
    }
  }

  // Clone current image to old image
  memcpy(mUpdateReceiveImageOld, mUpdateReceiveImage, mDataReceiveLength);

  // Reset error counter
  if(mUpdateErrorCounter > 0) {
    mUpdateErrorCounter = 0;
  }

  // Check status
  if(mDelegate != 0 && mOldStatus != mStatus) {
    mDelegate->onSlaveStatus(mStatus, mOldStatus);
    mOldStatus = mStatus;
  }

  return 1;
}

void EmbrickSlaveHandler::forceUpdate() {
  return mBus->forceUpdate(index());
}

void EmbrickSlaveHandler::dropHandles() {
  CCriticalRegion criticalRegion(mHandleMutex);

  forte::core::io::IOMapper& mapper = forte::core::io::IOMapper::getInstance();

  TSlaveHandleList::Iterator itEnd = mInputs.end();
  for(TSlaveHandleList::Iterator it = mInputs.begin(); it != itEnd; ++it) {
    mapper.deregisterHandle(*it);
    delete *it;
  }
  itEnd = mOutputs.end();
  for(TSlaveHandleList::Iterator it = mOutputs.begin(); it != itEnd; ++it) {
    mapper.deregisterHandle(*it);
    delete *it;
  }

  mInputs.clearAll();
  mOutputs.clearAll();

}

void EmbrickSlaveHandler::addHandle(TSlaveHandleList* paList, EmbrickSlaveHandle* paHandle) {
  CCriticalRegion criticalRegion(mHandleMutex);
  paList->pushBack(paHandle);

  // TODO Maybe send indication event after connecting
}

EmbrickSlaveHandle* EmbrickSlaveHandler::getHandle(TSlaveHandleList* paList, int paIndex) {
  TSlaveHandleList::Iterator itEnd = paList->end();
  int i = 0;
  for(TSlaveHandleList::Iterator it = paList->begin(); it != itEnd; ++it, i++) {
    if(paIndex == i) {
      return *it;
    }
  }
  return NULL;
}

