#include "AdapterConfigPort.h"

#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/algorithm/string.hpp>
#include "Logger/Logger.h"
#include "Helper/Decoders.h"
#include "Helper/GeneralConfigurationParams.h"
#include "LowerTestAdapter.h"
#include "UpperTestAdapter.h"
#include "Ports/MessagesIdentifiers.h"
#include "Dispatcher/Filter.h"
#include "Dispatcher/IpFilter.h"
#include "Dispatcher/Ipv6Filter.h"
#include "Dispatcher/PortFilter.h"
#include "Dispatcher/IpFilter.h"
#include "Dispatcher/PortFilter.h"

using namespace t3devlib;
using namespace boost;
using namespace std;

AdapterConfigPort::AdapterConfigPort (PortId& portId) 
:Port(portId) {
  LowerTestAdapter::Instance().RegisterAdapterConfigPort(this);  
  Logger::Instance().LogInfo("AdapterConfigPort::AdapterConfigPort");
}

AdapterConfigPort::~AdapterConfigPort() {
  Logger::Instance().LogInfo("AdapterConfigPort::~AdapterConfigPort");
  m_connectedPorts.clear();
}

bool AdapterConfigPort::Map (const PortId& connectedPortId) {
  
    if(string(connectedPortId.GetComponentId().GetType().GetObjectName()) == MTC) {
    // MTC
    Logger::Instance().LogInfo("MTC.AdapterConfigPort mapped");
    m_mtcPortId = &connectedPortId;
  }
  else {
    m_connectedPorts.push_back(&connectedPortId);
  }
  
  return true; 
} 
 
bool AdapterConfigPort::Unmap (const PortId& connectedPortId) { 

  //TODO: unregister filter; remove from m_connectedPorts
  return true; 
}

bool AdapterConfigPort::Send (const ComponentId& from, const Bitstring& bsMsg) {

  stringstream ss;
  ss << "AdapterConfigPort::Send: " << from;
  Logger::Instance().LogInfo(ss.str());
  
  unsigned char processDone = 0x00; // Processing result code - Set to 0 on success, set to 2 otherwise.
  const char *response = 0;
  
  if(string(from.GetType().GetObjectName()) == MTC) {
    // This is the Global adapter port.
    ss.str("");
    ss << "##### bsMsg(HEXA value): " << bsMsg;
    //Logger::Instance().LogDebug(ss.str());
    // Retrieve and convert parameters.
    Logger::Instance().LogInfo("AdapterConfigPort::Send: Gathering and converting parameters");
    // Check the message type.
    string frame = bsMsg.GetValueHexa();
    int offset = 0;
    int messageType = static_cast<int>(Decoders::Instance().DecodeByte(frame, &offset));
    ss.str("");
    ss << "AdapterConfigPort::Send: messageType=" << messageType << " - " << offset;
    Logger::Instance().LogDebug(ss.str());
    if (messageType == MessagesIdentifiers::GetIdx(MessagesIdentifiers::GeneralConfigurationReq)) // GlobalConfigureReq
    {
      processDone = ProcessGeneralConfigurationReq(frame, offset);
      response = MessagesIdentifiers::GeneralConfigurationRsp;
    }
    else if (messageType == MessagesIdentifiers::GetIdx(MessagesIdentifiers::StartTrafficCaptureReq)) // StartCaptureReq
    {
      processDone = ProcessStartCaptureReq(frame, offset);
      response = MessagesIdentifiers::StartTrafficCaptureRsp;
    }
    else if (messageType == MessagesIdentifiers::GetIdx(MessagesIdentifiers::StopTrafficCaptureReq)) // StopCaptureReq
    {
      processDone = ProcessStopCaptureReq(frame, offset);
      response = MessagesIdentifiers::StopTrafficCaptureRsp;
    }
    else
    {
      // Unknown message.
      cerr << "AdapterConfigPort::Send: Unknown message" << endl;
      processDone = 0x01; // e_error
    }
    
    if (processDone != 0x00) // e_success
    {
      Logger::Instance().LogError("AdapterConfigPort::Send: processDone flags set");
      ProcessResponse(response, processDone);
    }
  }
  else // not MTC
  {
    // TODO: Support of Interface configuration port
    string frame = bsMsg.GetValueHexa();
    int offset = 0;
    int messageType = static_cast<int>(Decoders::Instance().DecodeByte(frame, &offset));
    ss.str("");
    ss << "AdapterConfigPort::Send: Processing component-specific configuration request for messageType=" << messageType << " - " << MessagesIdentifiers::GetMessage(messageType);
    Logger::Instance().LogDebug(ss.str());
  if (messageType == MessagesIdentifiers::GetIdx(MessagesIdentifiers::SetFilterReq))
    {
      processDone = ProcessSetFilterReq(frame, offset, from);
    ProcessResponse(MessagesIdentifiers::SetFilterRsp, processDone, &GetConnectedPort(&from));
    }
    else
    {
      // Unknown message.
      //Logger::Instance().LogError("AdapterConfigPort::Send: Unknown message");
      processDone = 0x01; // e_unsuccess
    }
  }
  
  return true;
}

const t3devlib::PortId& AdapterConfigPort::GetConnectedPort(const ComponentId *pComponentId) {
  std::list<const PortId *>::iterator it;
  for(it = m_connectedPorts.begin(); it != m_connectedPorts.end(); it++) {
    if(&((*it)->GetComponentId()) == pComponentId) {
      return **it;
    }
  }
}

const t3devlib::ComponentId* AdapterConfigPort::Filter() {
  // FOR DEBUG ONLY: round robin
  const PortId * pPortId = m_connectedPorts.front();
  m_connectedPorts.pop_front();
  m_connectedPorts.push_back(pPortId);
  return &(pPortId->GetComponentId());
}

unsigned char AdapterConfigPort::ProcessGeneralConfigurationReq(const string & frame, int offset) {
  
  unsigned char processDone = 0x00;
  
  stringstream ss;
  int length = -1;
  boost::shared_ptr<GeneralConfigurationParams> configParams(new GeneralConfigurationParams);
  try
  {
    // CaptureTraffic IP address.
    string captureProcessIpAddress = Decoders::Instance().DecodeLengthPlusString(frame, &offset, &length);
    /*ss.str("");
    ss << "AdapterConfigPort::Send: captureProcessIpAddress=" << captureProcessIpAddress << " - " << offset  << " - " << length;
    Logger::Instance().LogDebug(ss.str());*/
    
    // CaptureTraffic port number.
    int captureProcessPort = Decoders::Instance().DecodeInteger(frame, &offset);
    /*ss.str("");
    ss << "AdapterConfigPort::Send: captureProcessPort=" << captureProcessPort << " - " << offset;
    Logger::Instance().LogDebug(ss.str());*/
    cout << "AdapterConfigPort::Send: captureProcessPort=" << captureProcessPort << " - " << offset << endl;
    
    // Capture mode.
    // 1. Retrieve the union record to use.
    unsigned char mode = Decoders::Instance().DecodeByte(frame, &offset);
    /*ss.str("");
    ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: mode=" << static_cast<int>(mode) << " - " << offset;
    Logger::Instance().LogDebug(ss.str());*/
    cout  << "AdapterConfigPort::ProcessGeneralConfigurationReq: mode=" << static_cast<int>(mode) << " - " << offset << endl;
    boost::shared_ptr<GeneralConfigurationParams> configParams(new GeneralConfigurationParams);
    if (mode == 0x00) // LiveCapture
    {
      // List of Ethernet interfaces.
      int ifacesNum = Decoders::Instance().DecodeParamsNum(frame, &offset);
      /*ss.str("");
      ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: ifacesNum=" << ifacesNum << " - " << offset;
      //Logger::Instance().LogDebug(ss.str());*/
      clog << "AdapterConfigPort::ProcessGeneralConfigurationReq: ifacesNum=" << ifacesNum << " - " << offset << endl;
      vector<string> ifacesList;
      for (int i = 0; i < ifacesNum; i++)
      {
        string iface = Decoders::Instance().DecodeLengthPlusString(frame, &offset, &length);
        /*ss.str("");
        ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: iface=" << iface << " - " << offset << " - " << length;
        //Logger::Instance().LogDebug(ss.str());*/
        clog << "AdapterConfigPort::ProcessGeneralConfigurationReq: iface= " << iface << " - " << offset << " - " << length << endl;
        ifacesList.push_back(iface);
      }
      
      // Record mode.
      unsigned char record = Decoders::Instance().DecodeEnumerated(frame, &offset);
      /*ss.str("");
      ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: record=" << static_cast<int>(record) << " - " << offset;
      //Logger::Instance().LogDebug(ss.str());*/
      
      // Get number of IP interfaces for EUTs
      int parmsNum = Decoders::Instance().DecodeParamsNum(frame, &offset);
      /*ss.str("");
      ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: EUTs parms number=" << parmsNum << " - " << offset << " - " << length;
      //Logger::Instance().LogDebug(ss.str());*/
      vector<shared_ptr<EutIPInterface> > eutIpInterfaceList;
      for (int i = 0; i < parmsNum; i++)
      {
        EutIPInterface *eut = new EutIPInterface(
          Decoders::Instance().DecodeLengthPlusString(frame, &offset, &length), // EUT name
          Decoders::Instance().DecodeLengthPlusString(frame, &offset, &length), // EUT IP address
          Decoders::Instance().DecodeInteger(frame, &offset) ); // EUT port number
        /*ss.str("");
        ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: EUT=" << eut->Eut() << " - " << offset << " - " << length;
        //Logger::Instance().LogDebug(ss.str());*/
        clog << "AdapterConfigPort::ProcessGeneralConfigurationReq: EUT=" << eut->Eut() << " - " << offset << " - " << length << endl;
        /*ss.str("");
        ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: Add EUT item: " << eut->Eut() << " - " << eut->EutAddress() << " - " << eut->EutPort();
        //Logger::Instance().LogDebug(ss.str());*/
        eutIpInterfaceList.push_back(shared_ptr<EutIPInterface>(eut));
      }
      
      // Create the GeneralConfigurationParams instance.
      configParams.get()->Initialize(
          captureProcessIpAddress, 
          captureProcessPort, 
          ifacesList,
          mode,
          record,
          eutIpInterfaceList);
    }
    else // OfflineCapture
    {
      // Time stamp offset.
      int seconds = Decoders::Instance().DecodeUInt32(frame, &offset);
      int microseconds = Decoders::Instance().DecodeUInt32(frame, &offset);
      /*ss.str("");
      ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: pcapTimeStamp=" << seconds << " - " << microseconds << " - "<< offset;
      Logger::Instance().LogDebug(ss.str());*/
      clog << "AdapterConfigPort::ProcessGeneralConfigurationReq: pcapTimeStamp=" << seconds << " - " << microseconds << " - "<< offset << endl;
      
      // Sessions path if mode is offline
      string sessionsPath = Decoders::Instance().DecodeLengthPlusString(frame, &offset, &length);
      /*ss.str("");
      ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: sessionsPath=" << sessionsPath << " - " << offset << " - " << length;
      Logger::Instance().LogDebug(ss.str());*/
      clog  << "AdapterConfigPort::ProcessGeneralConfigurationReq: sessionsPath=" << sessionsPath << " - " << offset << " - " << length << endl;
      string fileToPlay = "";
      if (sessionsPath.length() != 0)
      {
        // Build the file name
        ss.str("");
        ss << sessionsPath << "/" << GetTestcaseId().GetObjectName() << ".pcap";
        fileToPlay = string(ss.str());
        replace_first(fileToPlay, "TC_", "TD_");
      }
      /*ss.str("");
      ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: fileToPlay=" << fileToPlay << " - " << offset << " - " << length;
      Logger::Instance().LogDebug(ss.str());*/
      clog << "AdapterConfigPort::ProcessGeneralConfigurationReq: fileToPlay=" << fileToPlay << " - " << offset << " - " << length << endl;
      
      // List of files to merge
      int parmsNum = Decoders::Instance().DecodeParamsNum(frame, &offset);
      /*ss.str("");
      ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: parmsNum=" << parmsNum;
      //Logger::Instance().LogDebug(ss.str());*/
      clog << "AdapterConfigPort::ProcessGeneralConfigurationReq: parmsNum=" << parmsNum << " - " << offset << " - " << length << endl;
      vector<string> mergeFileList;
      for (int i = 0; i < parmsNum; i++)
      {
        string str = Decoders::Instance().DecodeLengthPlusString(frame, &offset, &length);
        /*ss.str("");
        ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: str=" << str << " - " << offset << " - " << length;
        //Logger::Instance().LogDebug(ss.str());*/
        clog << "AdapterConfigPort::ProcessGeneralConfigurationReq: str=" << str << " - " << offset << " - " << length << endl;
        mergeFileList.push_back(str);
      }
      
      // Pcap file to play if mode is offline
      string mergeFilePath = Decoders::Instance().DecodeLengthPlusString(frame, &offset, &length);
      /*ss.str("");
      ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: mergeFilePath=" << mergeFilePath;
      //Logger::Instance().LogDebug(ss.str());*/
      clog << "AdapterConfigPort::ProcessGeneralConfigurationReq: mergeFilePath=" << mergeFilePath << " - " << offset << " - " << length << endl;
      
      // PCAP merge tool
      string pcapMergeTool = Decoders::Instance().DecodeLengthPlusString(frame, &offset, &length);
      /*ss.str("");
      ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: pcapMergeTool=" << pcapMergeTool;
      //Logger::Instance().LogDebug(ss.str());*/
      clog << "AdapterConfigPort::ProcessGeneralConfigurationReq: pcapMergeTool=" << pcapMergeTool << " - " << offset << " - " << length << endl;

      vector<shared_ptr<EutIPInterface> > eutIpInterfaceList;
      
      // Create the GeneralConfigurationParams instance.
      configParams.get()->Initialize(
          captureProcessIpAddress, 
          captureProcessPort, 
          mode,
          seconds,
          microseconds,
          pcapMergeTool,
          fileToPlay,
          mergeFileList,
          mergeFilePath);
    }
    
    // All parameters are gathered, initialize the UpperTestAdapter
    /*ss.str("");
    ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: Call UpperTestAdapter::Initialize" ;
    //Logger::Instance().LogDebug(ss.str());*/
    clog << "AdapterConfigPort::ProcessGeneralConfigurationReq: Call UpperTestAdapter::Initialize" << endl;
    if (!Singleton<UpperTestAdapter::UpperTestAdapter>::Instance().Initialize(configParams))
    {
      cerr << "AdapterConfigPort::ProcessGeneralConfigurationReq: UpperTestAdapter initialization failed" << endl;
      processDone = 0x01; // e_error
    }
    // And start TrafficCapture initialization (MergePcapFilesRequest & OpenDeviceRequest message exhanged between Adapter and TrafficCapture).
    /*ss.str("");
    ss << "AdapterConfigPort::ProcessGeneralConfigurationReq: Call ConnectToCaptureServer";
    //Logger::Instance().LogDebug(ss.str());*/
    clog << "AdapterConfigPort::ProcessGeneralConfigurationReq: Call ConnectToCaptureServer" << endl;
    LowerTestAdapter::Instance().ConnectToCaptureServer(configParams);
    // Free pointer.
    configParams.reset();
  }
  catch (std::exception &e)
    {
      cerr << "AdapterConfigPort::ProcessGeneralConfigurationReq: " << e.what() << endl;
      processDone = 0x01; // e_error
    }
  
  return processDone;
}

unsigned char AdapterConfigPort::ProcessStartCaptureReq(const string & frame, int offset) {
  
  unsigned char processDone = 0x00; // e_success
  
  int length = -1;
  
  try
  {
    clog << "AdapterConfigPort::ProcessStartCaptureReq: Call StartCapture" << endl;
    LowerTestAdapter::Instance().StartCapture();
  }
  catch (std::exception &e)
    {
      cerr << "AdapterConfigPort::ProcessStartCaptureReq: " << e.what() << endl;
      processDone = 0x01; // e_error
    }
  
  return processDone;
}

unsigned char AdapterConfigPort::ProcessStopCaptureReq(const string & frame, int offset) {
  
  unsigned char processDone = 0x00;
  
  int length = -1;
  
  try
  {
    clog << "AdapterConfigPort::ProcessStopCaptureReq: Call StopCaptureReq" << endl;
    LowerTestAdapter::Instance().StopCapture();
  }
  catch (std::exception &e)
    {
      cerr << "AdapterConfigPort::ProcessStopCaptureReq: " << e.what() << endl;
      processDone = 0x02; // e_error
    }
  
  return processDone;
}

unsigned char AdapterConfigPort::ProcessSetFilterReq(const string & frame, int offset, const ComponentId& from) {
  
  unsigned char processDone = 0x00;
  int length = -1;

  try
  {
    FilterSet fs;
    Logger::Instance().LogDebug("AdapterConfigPort::SetFilterReq: Call RegisterFilter");
  // Discard ProtocolFilter (not used at the moment for filtering)
  Decoders::Instance().DecodeByte(frame, &offset);
  // InterfaceInfoList: get number of elements
    int nIfaceCount = Decoders::Instance().DecodeByte(frame, &offset);
  for (int i = 0; processDone == 0x00 && i < nIfaceCount; ++i)
  {
    // InterfaceInfo; union item id
    unsigned char mode = Decoders::Instance().DecodeByte(frame, &offset);
    if (mode == 0x00) // IpInterfaceInfoList
    {
      int nItemCount = Decoders::Instance().DecodeByte(frame, &offset);
      for (int j = 0; processDone == 0x00 && j < nItemCount; j++)
      {
        // domainName (not used)
        Decoders::Instance().DecodeLengthPlusString(frame, &offset, &length);
        std::string sAddr = Decoders::Instance().DecodeLengthPlusString(frame, &offset, &length);
        if (sAddr.find(':', 0) != string::npos) { // TODO: Q&D test
	        Ipv6Filter ipFilter;
	        if (!ipFilter.SetAddress(sAddr))
	        {
	          Logger::Instance().LogError("AdapterConfigPort::ProcessSetFilterReq: Unknown IPv6 interface info format");
	          processDone = 0x01;
	          break;
	        }
	        int nPortCount = Decoders::Instance().DecodeParamsNum(frame, &offset);
	        std::clog << "AdapterConfigPort::ProcessSetFilterReq: # ports: " << nPortCount << std::endl;
	        for (int k = 0; processDone == 0x00 && k < nPortCount; k++)
	        {
	          int nPort = Decoders::Instance().DecodeInteger(frame, &offset);
	          PortFilter portFilter;
	          portFilter.SetPortNumber(static_cast<unsigned short>(nPort));
	          ipFilter.AddNextLayerFilter(portFilter);
	        }
	        fs.AddFilter(ipFilter);
      	} else {
	        IpFilter ipFilter;
	        if (!ipFilter.SetAddress(sAddr))
	        {
	          Logger::Instance().LogError("AdapterConfigPort::ProcessSetFilterReq: Unknown interface info format");
	          processDone = 0x01;
	          break;
	        }
	        int nPortCount = Decoders::Instance().DecodeParamsNum(frame, &offset);
	        std::clog << "AdapterConfigPort::ProcessSetFilterReq: # ports: " << nPortCount << std::endl;
	        for (int k = 0; processDone == 0x00 && k < nPortCount; k++)
	        {
	          int nPort = Decoders::Instance().DecodeInteger(frame, &offset);
	          PortFilter portFilter;
	          portFilter.SetPortNumber(static_cast<unsigned short>(nPort));
	          ipFilter.AddNextLayerFilter(portFilter);
	        }
	        fs.AddFilter(ipFilter);
	      }
      }
    }
    else
    {
      Logger::Instance().LogWarning("AdapterConfigPort::SetFilterReq: Unknown interface info format");
      processDone = 0x01;
    }
  }
    LowerTestAdapter::Instance().RegisterFilter(&from, fs);

  }
  catch (std::exception &e)
    {
      cerr << "AdapterConfigPort::SetFilterReq: " << e.what() << endl;
      processDone = 0x01; // e_error
    }
  
  clog << "<<< AdapterConfigPort::SetFilterReq: " << processDone << endl;
  return processDone;
}

bool AdapterConfigPort::ProcessResponse (const std::string& message, unsigned char result, const PortId *to)
{
  stringstream ss;
  ss << "AdapterConfigPort::ProcessResponse: " << message << " - " << static_cast<int>(result);
  Logger::Instance().LogInfo(ss.str());

  // Prepare the response.
  Bitstring buffer;
  // Get message index
  unsigned char idx = MessagesIdentifiers::GetIdx(message);
  if (idx == 0xff) {
    std::string errmsg ("unsupported response message value '");
    errmsg += message;
    errmsg += '\n';
    Logger::Instance().LogError(errmsg);
    throw errmsg;
  }
  // Set message index
  Unsigned messageType(8); // 1 octet length
  ss.str("");
  ss << "AdapterConfigPort::ProcessResponse: messageType=" << static_cast<int>(idx);
  Logger::Instance().LogDebug(ss.str());
  messageType.SetValue(idx);
  buffer.Append(messageType);
  // Set status
  Unsigned responseBuffer(8, result); // 1 octet length
  buffer.Append(responseBuffer);
  ss.str("");
  ss << "AdapterConfigPort::ProcessResponse: responseBuffer=" << responseBuffer;
  Logger::Instance().LogDebug(ss.str());
  // Set reason
  Unsigned reasonLength(2 * 8, 0); // omitted - 2 octets length
  buffer.Append(reasonLength);
  // Send the response.
  ss.str("");
  ss << "AdapterConfigPort::ProcessResponse: buffer=" << buffer;
  Logger::Instance().LogDebug(ss.str());

  if(!to) {
    to = m_mtcPortId;
  }

  EnqueueMsg (*to, buffer);

  return true;
}
