/*******************************************************************************
 * Copyright (c) 2012 - 2015 ACIN, fortiss GmbH
 * 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:
 *  Alois Zoitl - initial API and implementation and/or initial documentation
 *******************************************************************************/
#include "muxedsercommlayer.h"
#include "commfb.h"
#include "../devlog.h"
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <criticalregion.h>

using namespace forte::com_infra;

CMuxedSerCommLayer::CMuxedSerPortsManager CMuxedSerCommLayer::smMuxedSerPortsManager;

CMuxedSerCommLayer::CMuxedSerCommLayer(CComLayer* paUpperLayer, CBaseCommFB * paFB) :
    CComLayer(paUpperLayer, paFB), mBufFillSize(0), mFD(scmInvalidFileDescriptor),
  mInterruptResp(forte::com_infra::EComResponse::e_Nothing), mSerMuxId(0){

}

CMuxedSerCommLayer::~CMuxedSerCommLayer(){
  closeConnection();
}

EComResponse CMuxedSerCommLayer::sendData(void *paData, unsigned int paSize){
  if(CFDSelectHandler::scmInvalidFileDescriptor != mFD){
    //first send id

    mRecvBuffer[0] = mSerMuxId;
    memcpy(&(mRecvBuffer[1]), paData, paSize);
    paSize++;

    ssize_t nToSend = paSize;
    while(0 < nToSend){
      ssize_t nSentBytes = write(mFD, &(mRecvBuffer[0]), nToSend);
      if(nSentBytes <= 0){
        DEVLOG_ERROR("CMuxedSerCommLayer: Send failed: %s\n", strerror(errno));
        return e_ProcessDataSendFailed;
      }
      nToSend -= nSentBytes;
      paData = static_cast<char*>(paData) + nSentBytes;
    }
  }

  return e_ProcessDataOk;
}

EComResponse CMuxedSerCommLayer::recvData(const void *, unsigned int){
  ssize_t nReadCount = read(mFD, &mRecvBuffer[mBufFillSize], cg_unIPLayerRecvBufferSize - mBufFillSize);

  switch (nReadCount){
    case 0:
      DEVLOG_INFO("CMuxedSerCommLayer: Connection closed by peer\n");
      mInterruptResp = e_InitTerminated;
      closeConnection();
      break;
    case -1:
      DEVLOG_ERROR("CMuxedSerCommLayer: read failed: %s\n", strerror(errno));
      mInterruptResp = e_ProcessDataRecvFaild;
      break;
    default:
      //we successfully received data
      mBufFillSize += nReadCount;
      mInterruptResp = e_ProcessDataOk;
      break;
  }

  m_poFb->interruptCommFB(this);
  return mInterruptResp;
}

EComResponse CMuxedSerCommLayer::processInterrupt(){
  if(e_ProcessDataOk == mInterruptResp){
    mInterruptResp = m_poTopLayer->recvData(mRecvBuffer, mBufFillSize);
    mBufFillSize = 0;
  }
  return mInterruptResp;
}

EComResponse CMuxedSerCommLayer::openConnection(char *paLayerParameter){
  EComResponse eRetVal = e_InitInvalidId;

  char *acPort = strchr(paLayerParameter, ':');
  if(0 != acPort){
    *acPort = '\0';
    ++acPort;
    mSerMuxId = static_cast<TForteUInt8>(forte::core::util::strtoul(acPort, 0, 10));
    mFD = smMuxedSerPortsManager.addMuxedSerLayer(paLayerParameter, this);
    if(CFDSelectHandler::scmInvalidFileDescriptor != mFD){
      eRetVal = e_InitOk;
    }
  }

  return eRetVal;
}

void CMuxedSerCommLayer::closeConnection(){
  if(CFDSelectHandler::scmInvalidFileDescriptor != mFD){
    smMuxedSerPortsManager.removeMuxedSerLayer(mFD, this);
    mFD = CFDSelectHandler::scmInvalidFileDescriptor;
  }
}

//**************************************************************************************************************************
CMuxedSerCommLayer::CMuxedSerPortsManager::CMuxedSerPortsManager(){

}

CFDSelectHandler::TFileDescriptor CMuxedSerCommLayer::CMuxedSerPortsManager::addMuxedSerLayer(char* paSerPort, CMuxedSerCommLayer *paComCallBack){
  CCriticalRegion criticalRegion(mSync);
  CFDSelectHandler::TFileDescriptor nRetVal = CFDSelectHandler::scmInvalidFileDescriptor;
  SSerPortEntry *mSerPortEntry = getSerPortEntry(paSerPort);
  if(0 != mSerPortEntry){
    mSerPortEntry->mConnectionsList.pushBack(paComCallBack);
    nRetVal = mSerPortEntry->mFD;
  }
  return nRetVal;
}

void CMuxedSerCommLayer::CMuxedSerPortsManager::removeMuxedSerLayer(CFDSelectHandler::TFileDescriptor paFD, CMuxedSerCommLayer *paComCallBack){
  CCriticalRegion criticalRegion(mSync);
  SSerPortEntry *mSerPortEntry = getOpendSerPortEntry(paFD);
  if(0 != mSerPortEntry){
    TConnectionContainer::Iterator itRunner(mSerPortEntry->mConnectionsList.begin());
    TConnectionContainer::Iterator itRevNode(mSerPortEntry->mConnectionsList.end());
    TConnectionContainer::Iterator itEnd(mSerPortEntry->mConnectionsList.end());

    while(itRunner != itEnd){
      if((*itRunner) == paComCallBack){
        if(itRevNode == itEnd){
          mSerPortEntry->mConnectionsList.popFront();
        }else{
           mSerPortEntry->mConnectionsList.eraseAfter(itRevNode);
        }
        break;
      }

      itRevNode = itRunner;
      ++itRunner;
    }

    if(mSerPortEntry->mConnectionsList.isEmpty()){
      closePort(mSerPortEntry);
    }
  }
}

EComResponse CMuxedSerCommLayer::CMuxedSerPortsManager::SSerPortEntry::recvData(const void *, unsigned int){
  EComResponse eRetVal = e_Nothing;
  char nID;

  ssize_t nReadCount = read(mFD, &nID, 1);

  if(1 == nReadCount){

    TConnectionContainer::Iterator itEnd(mConnectionsList.end());
    for(TConnectionContainer::Iterator itRunner = mConnectionsList.begin(), itCurrent = mConnectionsList.begin(); itRunner != itEnd;){
      itCurrent = itRunner;
      ++itRunner;
      if(nID == itCurrent->getSerMuxId()){
        eRetVal = itCurrent->recvData(0,0);
        if(e_Nothing != eRetVal){
          m_poFb = itCurrent->getCommFB();
        }
        break;
      }
    }
  }
  else{
    //FIXME handle error close connections and inform FBs
  }

  return eRetVal;
}

EComResponse CMuxedSerCommLayer::CMuxedSerPortsManager::SSerPortEntry::processInterrupt(){
  return e_Nothing;
}

CMuxedSerCommLayer::CMuxedSerPortsManager::SSerPortEntry *CMuxedSerCommLayer::CMuxedSerPortsManager::getSerPortEntry(char* paSerPort){
  for(TSerPortList::Iterator itRunner = mPortList.begin(); itRunner != mPortList.end(); ++itRunner){
    if(0 == strcmp(itRunner->mSerPort, paSerPort)){
      return &(*itRunner);
    }
  }

  TSerPortList::Iterator itRef(mPortList.back());
//this port is not yet managed by us
  mPortList.pushBack(SSerPortEntry());
  TSerPortList::Iterator itCurrent(mPortList.back());
  SSerPortEntry *pstRetVal = &(*itCurrent);
  openPort(paSerPort, pstRetVal);
  if(CFDSelectHandler::scmInvalidFileDescriptor == pstRetVal->mFD){
    if(itCurrent == mPortList.begin()){
      mPortList.popFront();
    }
    else{
      mPortList.eraseAfter(itRef);
    }
    pstRetVal = 0;
  }
  else{
    pstRetVal->mSerPort = new char[strlen(paSerPort) + 1];
    strcpy(pstRetVal->mSerPort, paSerPort);
  }
  return pstRetVal;
}

CMuxedSerCommLayer::CMuxedSerPortsManager::SSerPortEntry *CMuxedSerCommLayer::CMuxedSerPortsManager::getOpendSerPortEntry(CFDSelectHandler::TFileDescriptor paFD){
  SSerPortEntry *pstRetVal = 0;
  for(TSerPortList::Iterator itRunner = mPortList.begin(); itRunner != mPortList.end(); ++itRunner){
    if(paFD == itRunner->mFD){
      pstRetVal = &(*itRunner);
      break;
    }
  }
  return pstRetVal;
}

void CMuxedSerCommLayer::CMuxedSerPortsManager::openPort(char* paSerPort, SSerPortEntry *paPortEntry){
  paPortEntry->mFD = open(paSerPort, O_RDWR | O_NOCTTY);

  if(CFDSelectHandler::scmInvalidFileDescriptor != paPortEntry->mFD){
    getExtEvHandler<CFDSelectHandler>().addComCallback(paPortEntry->mFD, paPortEntry);
  }
  else{
    DEVLOG_ERROR("CSerCommLayer: open failed: %s\n", strerror(errno));
  }
}

void CMuxedSerCommLayer::CMuxedSerPortsManager::closePort(SSerPortEntry *paSerPortEntry){
  getExtEvHandler<CFDSelectHandler>().removeComCallback(paSerPortEntry->mFD);
  close(paSerPortEntry->mFD);

  delete[] paSerPortEntry->mSerPort;

  TSerPortList::Iterator itRunner(mPortList.begin());
  TSerPortList::Iterator itRefNode(mPortList.end());

  while(itRunner != mPortList.end()){
    if(itRunner->mFD == paSerPortEntry->mFD){
      if(itRefNode == mPortList.end()){
        mPortList.popFront();
      }
      else{
        mPortList.eraseAfter(itRefNode);
      }
      break;
    }

    itRefNode = itRunner;
    ++itRunner;
  }
}
