/*******************************************************************************
 * Copyright (c) 2018 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:
 *    Jose Cabral - initial API and implementation and/or initial documentation
 *******************************************************************************/

#include "http_handler.h"

#include "../../core/devexec.h"
#include "../../core/iec61131_functions.h"
#include "../../core/cominfra/basecommfb.h"
#include <criticalregion.h>
#include "httpparser.h"
#include <forte_printer.h>
#include "comlayer.h"
#include <forte_config.h>

using namespace forte::com_infra;

CIPComSocketHandler::TSocketDescriptor CHTTP_Handler::smServerListeningSocket = CIPComSocketHandler::scmInvalidSocketDescriptor;

char CHTTP_Handler::sRecvBuffer[];
unsigned int CHTTP_Handler::sBufFillSize = 0;
const unsigned int CHTTP_Handler::scmSendTimeout = 20;
const unsigned int CHTTP_Handler::scmAcceptedTimeout = 5;

DEFINE_HANDLER(CHTTP_Handler);

CHTTP_Handler::CHTTP_Handler(CDeviceExecution& pa_poDeviceExecution) :
    CExternalEventHandler(pa_poDeviceExecution) {
  memset(sRecvBuffer, 0, cg_unIPLayerRecvBufferSize);
}

CHTTP_Handler::~CHTTP_Handler() {
  stopTimeoutThread();
  closeHTTPServer();
  clearServerLayers();
  clearClientLayers();
  clearAcceptedSockets();
}

void CHTTP_Handler::enableHandler(void) {
  startTimeoutThread();
}

void CHTTP_Handler::disableHandler(void) {
  stopTimeoutThread();
}

void CHTTP_Handler::clearServerLayers() {
  CCriticalRegion criticalRegion(mServerMutex);
  for(CSinglyLinkedList<HTTPServerWaiting *>::Iterator iter = mServerLayers.begin(); iter != mServerLayers.end(); ++iter) {
    for(CSinglyLinkedList<CIPComSocketHandler::TSocketDescriptor>::Iterator iter1 = (*iter)->mSockets.begin(); iter1 != (*iter)->mSockets.end(); ++iter1) {
      removeAndCloseSocket(*iter1);
    }
    (*iter)->mSockets.clearAll();
    delete (*iter);
  }
  mServerLayers.clearAll();
}

void CHTTP_Handler::clearClientLayers() {
  CCriticalRegion criticalRegion(mClientMutex);
  for(CSinglyLinkedList<HTTPClientWaiting *>::Iterator iter = mClientLayers.begin(); iter != mClientLayers.end(); ++iter) {
    removeAndCloseSocket((*iter)->mSocket);
    delete (*iter);
  }
  mClientLayers.clearAll();
}

void CHTTP_Handler::clearAcceptedSockets() {
  CCriticalRegion criticalRegion(mAcceptedMutex);
  for(CSinglyLinkedList<HTTPAcceptedSockets *>::Iterator iter = mAcceptedSockets.begin(); iter != mAcceptedSockets.end(); ++iter) {
    removeAndCloseSocket((*iter)->mSocket);
    delete (*iter);
  }
  mAcceptedSockets.clearAll();
}

void CHTTP_Handler::setPriority(int) {
  //currently we are doing nothing here.
}

int CHTTP_Handler::getPriority(void) const {
  //the same as for setPriority
  return 0;
}

forte::com_infra::EComResponse CHTTP_Handler::recvData(const void* paData, unsigned int) { //TODO: do something with the size parameter of the received data?
  CIPComSocketHandler::TSocketDescriptor socket = *(static_cast<const CIPComSocketHandler::TSocketDescriptor*>(paData));

  if(socket == smServerListeningSocket) {
    CIPComSocketHandler::TSocketDescriptor newConnection = CIPComSocketHandler::acceptTCPConnection(socket);
    if(CIPComSocketHandler::scmInvalidSocketDescriptor != newConnection) {
      CCriticalRegion criticalRegion(mAcceptedMutex);
      HTTPAcceptedSockets* accepted = new HTTPAcceptedSockets();
      accepted->mSocket = newConnection;
      accepted->mStartTime = NOW();
      mAcceptedSockets.pushBack(accepted);
      getExtEvHandler<CIPComSocketHandler>().addComCallback(newConnection, this);
      resumeSelfsuspend();
    } else {
      DEVLOG_ERROR("[HTTP Handler] Couldn't accept new HTTP connection\n");
    }
  } else {
    int recvLen = CIPComSocketHandler::receiveDataFromTCP(socket, &sRecvBuffer[sBufFillSize], cg_unIPLayerRecvBufferSize - sBufFillSize);
    if(0 == recvLen) {
      removeAndCloseSocket(socket);
    } else if(-1 == recvLen) {
      removeAndCloseSocket(socket);
      DEVLOG_ERROR("[HTTP handler] Error receiving packet\n");
    } else {
      if(!recvClients(socket, recvLen) && !recvServers(socket)) {
        DEVLOG_WARNING("[HTTP Handler]: A packet arrived to the wrong place\n");
      }
    }
  }

  return e_Nothing;
}

bool CHTTP_Handler::recvClients(const CIPComSocketHandler::TSocketDescriptor paSocket, const int paRecvLength) { //check clients
  CCriticalRegion criticalRegion(mClientMutex);
  for(CSinglyLinkedList<HTTPClientWaiting *>::Iterator iter = mClientLayers.begin(); iter != mClientLayers.end(); ++iter) {
    if((*iter)->mSocket == paSocket) {
      if(e_ProcessDataOk == (*iter)->mLayer->recvData(sRecvBuffer, paRecvLength)) {
        startNewEventChain((*iter)->mLayer->getCommFB());
      }
      removeAndCloseSocket(paSocket);
      HTTPClientWaiting * toDelete = *iter;
      mClientLayers.erase(toDelete);
      delete toDelete;
      return true;
    }
  }

  return false;
}

bool CHTTP_Handler::recvServers(const CIPComSocketHandler::TSocketDescriptor paSocket) {
  CCriticalRegion criticalRegion(mServerMutex);
  removeSocketFromAccepted(paSocket);

  bool found = false;
  if(!mServerLayers.isEmpty()) {
    CIEC_STRING path;
    CSinglyLinkedList<CIEC_STRING> parameterNames;
    CSinglyLinkedList<CIEC_STRING> parameterValues;
    if(CHttpParser::parseGetRequest(path, parameterNames, parameterValues, sRecvBuffer)) {
      for(CSinglyLinkedList<HTTPServerWaiting *>::Iterator iter = mServerLayers.begin(); iter != mServerLayers.end(); ++iter) {
        if((*iter)->mPath == path) {
          (*iter)->mSockets.pushBack(paSocket);
          if(e_ProcessDataOk == (*iter)->mLayer->recvServerData(parameterNames, parameterValues)) {
            startNewEventChain((*iter)->mLayer->getCommFB());
          }
          found = true;
          break;
        }
      }
    } else {
      DEVLOG_ERROR("[HTTP Handler] Wrong HTTP Get request\n");
    }

    if(!found) {
      handlerReceivedWrongPath(paSocket, path);
    }
  }

  return found;
}

void CHTTP_Handler::removeSocketFromAccepted(const CIPComSocketHandler::TSocketDescriptor paSocket) {
  CCriticalRegion criticalRegion(mAcceptedMutex);
  for(CSinglyLinkedList<HTTPAcceptedSockets *>::Iterator iter = mAcceptedSockets.begin(); iter != mAcceptedSockets.end(); ++iter) {
    if((*iter)->mSocket == paSocket) {
      HTTPAcceptedSockets *toDelete = *iter;
      mAcceptedSockets.erase(toDelete);
      delete toDelete;
      break;
    }
  }
}

void CHTTP_Handler::handlerReceivedWrongPath(const CIPComSocketHandler::TSocketDescriptor paSocket, CIEC_STRING& paPath) {
  DEVLOG_ERROR("[HTTP Handler] Path %s has no FB registered\n", paPath.getValue());

  CIEC_STRING toSend;
  CIEC_STRING result = "HTTP/1.1 404 Not Found";
  CIEC_STRING mContentType = "text/html";
  CIEC_STRING mReqData = "";
  CHttpParser::createResponse(toSend, result, mContentType, mReqData);
  if(toSend.length() != CIPComSocketHandler::sendDataOnTCP(paSocket, toSend.getValue(), toSend.length())) {
    DEVLOG_ERROR("[HTTP Handler]: Error sending back the answer %s \n", toSend.getValue());
  }
  removeAndCloseSocket(paSocket);
}

bool CHTTP_Handler::sendClientData(forte::com_infra::CHttpComLayer* paLayer, CIEC_STRING& paToSend) {
  CIPComSocketHandler::TSocketDescriptor newSocket = CIPComSocketHandler::openTCPClientConnection(paLayer->getHost().getValue(), paLayer->getPort());
  if(CIPComSocketHandler::scmInvalidSocketDescriptor != newSocket) {
    if(paToSend.length() == CIPComSocketHandler::sendDataOnTCP(newSocket, paToSend.getValue(), paToSend.length())) {
      CCriticalRegion criticalRegion(mClientMutex);
      HTTPClientWaiting* toAdd = new HTTPClientWaiting();
      toAdd->mLayer = paLayer;
      toAdd->mSocket = newSocket;
      toAdd->mStartTime = NOW();
      startTimeoutThread();
      mClientLayers.pushBack(toAdd);
      getExtEvHandler<CIPComSocketHandler>().addComCallback(newSocket, this);
      resumeSelfsuspend();
      return true;
    } else {
      DEVLOG_ERROR("[HTTP Handler]: Couldn't send data to client %s:%u\n", paLayer->getHost().getValue(), paLayer->getPort());
      removeAndCloseSocket(newSocket);
    }
  } else {
    DEVLOG_ERROR("[HTTP Handler]: Couldn't open client connection for %s:%u\n", paLayer->getHost().getValue(), paLayer->getPort());
  }
  return false;
}

bool CHTTP_Handler::addServerPath(forte::com_infra::CHttpComLayer* paLayer, CIEC_STRING& paPath) {
  CCriticalRegion criticalRegion(mServerMutex);

  for(CSinglyLinkedList<HTTPServerWaiting *>::Iterator iter = mServerLayers.begin(); iter != mServerLayers.end(); ++iter) {
    if((*iter)->mPath == paPath) {
      DEVLOG_ERROR("[HTTP Handler]: The listening  path \"%s\" was already added to the http server. Cannot add it again\n", paPath.getValue());
      return false;
    }
  }

  openHTTPServer();
  HTTPServerWaiting* toAdd = new HTTPServerWaiting();
  toAdd->mLayer = paLayer;
  toAdd->mPath = paPath;
  mServerLayers.pushBack(toAdd);
  DEVLOG_INFO("[HTTP Handler]: The listening  path \"%s\" was added to the http server\n", paPath.getValue());
  return true;
}

void CHTTP_Handler::removeServerPath(CIEC_STRING& paPath) {
  CCriticalRegion criticalRegion(mServerMutex);

  for(CSinglyLinkedList<HTTPServerWaiting *>::Iterator iter = mServerLayers.begin(); iter != mServerLayers.end(); ++iter) {
    if((*iter)->mPath == paPath) {
      for(CSinglyLinkedList<CIPComSocketHandler::TSocketDescriptor>::Iterator iter_ = (*iter)->mSockets.begin(); iter_ != (*iter)->mSockets.end(); ++iter_) {
        removeAndCloseSocket(*iter_);
      }
      HTTPServerWaiting * toDelete = *iter;
      mServerLayers.erase(toDelete);
      delete toDelete;
      break;
    }
  }

  if(mServerLayers.isEmpty()) {
    closeHTTPServer();
  }
}

void CHTTP_Handler::sendServerAnswer(forte::com_infra::CHttpComLayer* paLayer, CIEC_STRING& paAnswer) {
  sendServerAnswerHelper(paLayer, paAnswer, false);
}

void CHTTP_Handler::sendServerAnswerFromRecv(forte::com_infra::CHttpComLayer* paLayer, CIEC_STRING& paAnswer) {
  sendServerAnswerHelper(paLayer, paAnswer, true);
}

void CHTTP_Handler::forceClose(forte::com_infra::CHttpComLayer* paLayer) {
  forceCloseHelper(paLayer, false);
}

void CHTTP_Handler::forceCloseFromRecv(forte::com_infra::CHttpComLayer* paLayer) {
  forceCloseHelper(paLayer, true);
}

void CHTTP_Handler::run() {
  DEVLOG_INFO("[HTTP Handler]: Starting HTTP timeout thread\n");

  mThreadStarted.inc();
  while(isAlive()) {
    if(mClientLayers.isEmpty() && mAcceptedSockets.isEmpty()) {
      selfSuspend();
    }
    if(!isAlive()) {
      break;
    }

    checkClientLayers();
    checkAcceptedSockets();
    sleepThread(100);

  }
}

void CHTTP_Handler::checkClientLayers() {
  CCriticalRegion criticalRegion(mClientMutex);
  if(!mClientLayers.isEmpty()) {
    CSinglyLinkedList<HTTPClientWaiting *> clientsToDelete;
    for(CSinglyLinkedList<HTTPClientWaiting *>::Iterator iter = mClientLayers.begin(); iter != mClientLayers.end(); ++iter) {
      // wait until result is ready
      CIEC_DATE_AND_TIME currentTime(NOW());
      if(currentTime.getMilliSeconds() - (*iter)->mStartTime.getMilliSeconds() > scmSendTimeout * 1000) {
        DEVLOG_ERROR("[HTTP Handler]: Timeout at client %s:%u \n", (*iter)->mLayer->getHost().getValue(), (*iter)->mLayer->getPort());
        removeAndCloseSocket((*iter)->mSocket);
        clientsToDelete.pushBack(*iter);
        (*iter)->mLayer->recvData(0, 0); //indicates timeout
      }
    }
    for(CSinglyLinkedList<HTTPClientWaiting *>::Iterator iter = clientsToDelete.begin(); iter != clientsToDelete.end(); ++iter) {
      for(CSinglyLinkedList<HTTPClientWaiting *>::Iterator iter_ = mClientLayers.begin(); iter_ != mClientLayers.end(); ++iter_) {
        if(*iter_ == *iter) {
          mClientLayers.erase(*iter_);
          delete (*iter);
          break;
        }
      }
    }
  }
}

void CHTTP_Handler::checkAcceptedSockets() {
  CCriticalRegion criticalRegion(mAcceptedMutex);
  if(!mAcceptedSockets.isEmpty()) {
    CSinglyLinkedList<HTTPAcceptedSockets *> acceptedToDelete;
    for(CSinglyLinkedList<HTTPAcceptedSockets *>::Iterator iter = mAcceptedSockets.begin(); iter != mAcceptedSockets.end(); ++iter) {
      // wait until result is ready
      CIEC_DATE_AND_TIME currentTime(NOW());
      if(currentTime.getMilliSeconds() - (*iter)->mStartTime.getMilliSeconds() > scmAcceptedTimeout * 1000) {
        DEVLOG_ERROR("[HTTP Handler]: Timeout at accepted socket\n");
        removeAndCloseSocket((*iter)->mSocket);
        acceptedToDelete.pushBack(*iter);
      }
    }

    for(CSinglyLinkedList<HTTPAcceptedSockets *>::Iterator iter = acceptedToDelete.begin(); iter != acceptedToDelete.end(); ++iter) {
      for(CSinglyLinkedList<HTTPAcceptedSockets *>::Iterator iter_ = mAcceptedSockets.begin(); iter_ != mAcceptedSockets.end(); ++iter_) {
        if(*iter_ == *iter) {
          mAcceptedSockets.erase(*iter_);
          delete (*iter);
          break;
        }
      }
    }
  }
}

void CHTTP_Handler::startTimeoutThread() {
  if(!isAlive()) {
    start();
    mThreadStarted.waitIndefinitely();
    mThreadStarted.inc();
  }
}

void CHTTP_Handler::stopTimeoutThread() {
  setAlive(false);
  resumeSelfsuspend();
  end();
}

void CHTTP_Handler::openHTTPServer() {
  if(CIPComSocketHandler::scmInvalidSocketDescriptor == smServerListeningSocket) {
    char address[] = "127.0.0.1";
    smServerListeningSocket = CIPComSocketHandler::openTCPServerConnection(address, FORTE_COM_HTTP_LISTENING_PORT);
    if(CIPComSocketHandler::scmInvalidSocketDescriptor != smServerListeningSocket) {
      getExtEvHandler<CIPComSocketHandler>().addComCallback(smServerListeningSocket, this);
      DEVLOG_INFO("[HTTP Handler] HTTP server listening on port %d\n", FORTE_COM_HTTP_LISTENING_PORT);
    } else {
      DEVLOG_ERROR("[HTTP Handler] Couldn't start HTTP server on port %d\n", FORTE_COM_HTTP_LISTENING_PORT);
    }
  }
}

void CHTTP_Handler::closeHTTPServer() {
  if(CIPComSocketHandler::scmInvalidSocketDescriptor != smServerListeningSocket) {
    removeAndCloseSocket(smServerListeningSocket);
    smServerListeningSocket = CIPComSocketHandler::scmInvalidSocketDescriptor;
  }
}

void CHTTP_Handler::removeAndCloseSocket(const CIPComSocketHandler::TSocketDescriptor paSocket) {
  //normally, when the device is being killed the CIPComSocketHandler is already dead, so calls to it must be avoided.
  if(mDeviceExecution.isExtEvHandlerValid(CIPComSocketHandler::mHandlerIdentifier)) {
    getExtEvHandler<CIPComSocketHandler>().removeComCallback(paSocket);
    getExtEvHandler<CIPComSocketHandler>().closeSocket(paSocket);
  }
}

void CHTTP_Handler::resumeSelfsuspend() {
  mSuspendSemaphore.inc();
}

void CHTTP_Handler::selfSuspend() {
  mSuspendSemaphore.waitIndefinitely();
}

void CHTTP_Handler::sendServerAnswerHelper(forte::com_infra::CHttpComLayer* paLayer, CIEC_STRING& paAnswer, bool paFromRecv) {
  if(!paFromRecv) {
    mServerMutex.lock();
  }

  for(CSinglyLinkedList<HTTPServerWaiting *>::Iterator iter = mServerLayers.begin(); iter != mServerLayers.end(); ++iter) {
    if((*iter)->mLayer == paLayer) {
      CSinglyLinkedList<CIPComSocketHandler::TSocketDescriptor>::Iterator iterSocket = (*iter)->mSockets.begin();
      if(paAnswer.length() != CIPComSocketHandler::sendDataOnTCP(*iterSocket, paAnswer.getValue(), paAnswer.length())) {
        DEVLOG_ERROR("[HTTP Handler]: Error sending back the answer %s \n", paAnswer.getValue());
      }
      removeAndCloseSocket(*iterSocket);
      (*iter)->mSockets.popFront();
      break;
    }
  }

  if(!paFromRecv) {
    mServerMutex.unlock();
  }
}

void CHTTP_Handler::forceCloseHelper(forte::com_infra::CHttpComLayer* paLayer, bool paFromRecv) {
  if(!paFromRecv) {
    mServerMutex.lock();
  }

  bool found = false;

  for(CSinglyLinkedList<HTTPServerWaiting *>::Iterator iter = mServerLayers.begin(); iter != mServerLayers.end(); ++iter) {
    if((*iter)->mLayer == paLayer) {
      if(!(*iter)->mSockets.isEmpty()) {
        CSinglyLinkedList<CIPComSocketHandler::TSocketDescriptor>::Iterator itSocket = (*iter)->mSockets.begin();
        removeAndCloseSocket(*itSocket);
        (*iter)->mSockets.popFront();
      }
      found = true;
      break;
    }
  }

  if(!paFromRecv) {
    mServerMutex.unlock();
    mClientMutex.lock();
  }

  if(!found && !mClientLayers.isEmpty()) {
    for(CSinglyLinkedList<HTTPClientWaiting *>::Iterator iter = mClientLayers.begin(); iter != mClientLayers.end(); ++iter) {
      if((*iter)->mLayer == paLayer) {
        removeAndCloseSocket((*iter)->mSocket);
      }
    }
  }

  if(!paFromRecv) {
    mClientMutex.unlock();
  }
}
