 #include "listenerThread.h"

listenerThread::listenerThread(IceUtil::Mutex &_mtxCounter,
                               int _msgCounter)
{
  m_socketFd = -1;
  m_msgCounter = _msgCounter;
  // m_ptrMsgCounter = &_msgCounter;
  m_mtxCounter = &_mtxCounter;
}

listenerThread::listenerThread(const int &_socketFd,
                               IceUtil::Mutex &_mtxCounter,
                               int _msgCounter)//,
                               // IceUtil::Mutex &_mtxProp)
{
  m_socketFd = _socketFd;
  m_vecSocketFd.push_back(_socketFd);
  // m_ptrMsgCounter = &_msgCounter;
  m_msgCounter = _msgCounter;
  m_mtxCounter = &_mtxCounter;
  // m_mtxProp = &_mtxProp; // ??
}

listenerThread::listenerThread(const std::vector<int> &_vecSocketFd,
                               IceUtil::Mutex &_mtxCounter,
                               int _msgCounter)//,
                               // IceUtil::Mutex &_mtxProp)
{
  m_socketFd = _vecSocketFd[0]; // this will be deleted once listenerThread is able to
                                // listens to all sockets
  m_vecSocketFd = _vecSocketFd;
  // m_ptrMsgCounter = &_msgCounter;
  m_msgCounter = _msgCounter;
  m_mtxCounter = &_mtxCounter;
  std::cout << m_mtxCounter << "    " << &_mtxCounter << std::endl;
  // m_mtxProp = &_mtxProp; // ??
}

listenerThread::~listenerThread()
{
  // m_ptrMsgCounter = NULL;
  m_mtxCounter = NULL;
  // m_mtxProp = NULL;
  // delete m_ptrMsgCounter;
  delete m_mtxCounter;
  // delete m_mtxProp;
}

// Decreases internal and external counter by 1. Internal and external counters keep track of
// received messages
// NOTE: external counter shpuld be removed. Parent process and other threads should
// rely on the internal counter value, available through a get method.
void listenerThread::decrease_counter()
{
  IceUtil::Mutex::TryLock lock(*m_mtxCounter);
  if (!lock.acquired())
    {
      A_LOG("Lock not aquired");
      lock.acquire(); //blocks function execution until mutex is available
    }
  m_msgCounter = m_msgCounter - 1;
  std::cout << "Thrd: Message counter = " << m_msgCounter << std::endl;
}

// Increases internal counter by 1. Internal counter keeps track of
// sent messages
void listenerThread::increase_counter()
{
  IceUtil::Mutex::TryLock lock(*m_mtxCounter);
  if (!lock.acquired())
    {
      A_LOG("Lock not aquired");
      lock.acquire(); //blocks function execution until mutex is available
    }
  m_msgCounter = m_msgCounter + 1;
  std::cout << "Thrd: Message counter = " << m_msgCounter << std::endl;
}

std::string listenerThread::receive_data(const int &_socketFd)
{
  const int MAX_BUFFER_SIZE = 4096;
  int resultSize = 0;
  char socketBuffer[MAX_BUFFER_SIZE+1];
  std::string strRecv;
  std::string strResult;
  // IceUtil::Mutex::TryLock lock(*m_mtxCounter);
  // if (!lock.acquired())
  //   {
  //     A_LOG("Lock not aquired");
  //     lock.acquire(); //blocks function execution until mutex is available
  //   }
  resultSize = recv(_socketFd, socketBuffer, MAX_BUFFER_SIZE, MSG_PEEK);
  resultSize = recv(_socketFd, socketBuffer, MAX_BUFFER_SIZE, 0);
  if (resultSize == MAX_BUFFER_SIZE)
    {
      A_LOG("Max buffer size reached!");
    }
  strRecv = std::string(socketBuffer, resultSize);
  U3_LOG("Data received!");
  strResult = remove_crnl(strRecv);
  return strResult;
}

// Removes carriage returns and new line characters from string,
// in order to ease search for specific XML tag.
// Removing is performed in 2 steps using std::remove method from
// C++ std library (algorithm header)
std::string listenerThread::remove_crnl(const std::string &_strIn)
{
  U3_LOG("Removing carriage returns+new lines...")
  std::string strNew;
  std::string strFinal;
  std::string::iterator itNewEnd;
  strNew = _strIn;
  itNewEnd = std::remove(strNew.begin(), strNew.end(), '\r');
  itNewEnd = std::remove(strNew.begin(), itNewEnd, '\n');
  strFinal.assign(strNew.begin(), itNewEnd);
  // strNew.assign(_str.begin(),
  //               (std::remove(_str.begin(),
  //                            (std::remove(_str.begin(), _str.end(), '\r'), '\n'))
  //                )
  //               );
  D_LOG(strFinal);
  return strFinal;
}

void
listenerThread::run()
{
  std::queue<std::string> bufRecv;
  // std::vector<pcf::IndiProperty> vecRes;

  std::vector<pcf::IndiProperty>::iterator itRes;
  std::vector<int>::iterator it;

  std::ofstream ofStrm;
  ofStrm.open("/tmp/listenerThread-run.log", std::ofstream::out);
  int result = 0;
  int maxFd = 0;
  const int timeOut = 3600;
  // const int MAX_BUFFER_SIZE = 4096;
  // char socketBuffer[MAX_BUFFER_SIZE+1];
  std::string strRecvXml = "";
  fd_set fdReadSet;
  struct timeval tv;
  bool loop = true;

  // LOG_FILE(::fopen("/tmp/wheelsCtrl_nice.log", "w+"));
  LOG_FILE(::fopen("/tmp/listenerThread.log", "w+"));
  I_LOG("*******************")
  I_LOG("Thread running ... ");
  I_LOG("*******************")

  tv.tv_sec = timeOut;
  tv.tv_usec = timeOut * 1000;

  //pcf::IndiProperty testProperty(IndiProperty::Text);

  std::cout << "Hey! mr. thread entering do-while loop on fd "
            << m_socketFd << std::endl;
  // set the maximum socket file descriptor stored in the file descriptors vector
  // when starting the thread. It will be updated at run time if necessary
  if (m_vecSocketFd.size())
    maxFd = *std::max_element(m_vecSocketFd.begin(), m_vecSocketFd.end());
  else
    maxFd = 0;

  while (loop)
    {
      tv.tv_sec = timeOut;
      tv.tv_usec = timeOut * 1000;
      // scan socket for data 
      do {
        // NOTE: should a mutex be used on m_socketFd? No, no critical region
        FD_ZERO(&fdReadSet);
        // FD_SET(m_socketFd, &fdReadSet);
        FD_SET(m_socketFd, &fdReadSet);
        // NOTE select is not blocking
        // result = select(m_socketFd + 1, &fdReadSet, NULL, NULL, &tv);
        result = select(maxFd + 1, &fdReadSet, NULL, NULL, &tv);
      } while (result == -1 && errno == EINTR);
      // handle time out
      if (result == 0)
        {
          // time out
          std::cout << std::endl
                    << "Time Out"
                    << std::endl
                    << std::endl;
          // break;
        }
      if (result > 0)
        std::cout << "Thrd: listened fd = " << maxFd << std::endl;
        // std::cout << "Thrd: m_socketFd = " << m_socketFd << std::endl;
        {
          if (FD_ISSET(m_socketFd, &fdReadSet))
            {
              /* The socket_fd has data available to be read */
              std::cout << "The socket_fd has data available to be read" << std::endl;
              I_LOG("Thread receiving data from socket");
              // result = recv(m_socketFd, socketBuffer, MAX_BUFFER_SIZE, 0);
              strRecvXml = receive_data(m_socketFd);
              U3_LOG("Data received, parsing...");

              // parse_data returns a STL vector of IndiProperties. 
              m_vecRecvProperty = parse_data(strRecvXml, bufRecv);
              m_sigPropertyReceived(m_vecRecvProperty);
              for (itRes = m_vecRecvProperty.begin(); itRes < m_vecRecvProperty.end(); ++itRes)
                {
                  // decrease message counter
                  decrease_counter();
                }
              ofStrm << "------------- BEGIN MESG -------------" << std::endl;
              ofStrm << strRecvXml;
              ofStrm << "-------------  END MESG  -------------" << std::endl;
              // strRecvXml = std::string(socketBuffer, result);
              D_LOG("Number of characters received = " << strRecvXml.size());
              D_LOG("Message received" << strRecvXml)
              // std::cout << strRecvXml << std::endl;
              std::cout << m_msgCounter << std::endl;

//	      testProperty = pcf::Xmlparser(strRecvXml).createIndiMessage().getProperty();
              // if (*m_ptrMsgCounter == 0)
              if (m_msgCounter == 0)
                std::cout << "Thrd: Message counter is zero!!" << std::endl;
                // break;
              if (m_msgCounter < 0)
                {
                  F_LOG("Message counter < 0!!");
                  throw std::runtime_error("Message counter cannot be < 0 !");
                  break;
                }
              if (result == 0) 
                {
                  /* This means the other side closed the socket */
                  std::cout << "Connection closed on remote side" << std::endl;
                  break;
                }
              // This thread should pause and signal main thread to receive data
              // assert(pthread_mutex_unlock(&connMutex));
            }
        }
    } // end while
  ofStrm.close();
}

int* listenerThread::get_ptr_counter()
{
  // LOG_FILE(::fopen("/tmp/wheelsCtrl_nice.log", "w+"));
  IceUtil::Mutex::TryLock lock(*m_mtxCounter);
  if (!lock.acquired())
    {
      std::cout << "Lock not aquired" << std::endl;
      A_LOG("Lock not aquired");
      lock.acquire();
    }
  return &m_msgCounter;
}

void listenerThread::add_socket_fd(const int &_socketFd)
{
  if (_socketFd > 0)
    {
      if (m_socketFd < 0)
        m_socketFd = _socketFd;
      m_vecSocketFd.push_back(_socketFd);
    }
  else
    {
      throw std::invalid_argument("invalid socket file descriptor");
    }
}

void listenerThread::buffer_push(std::queue<std::string>& _buf, std::string& _str)
{
  IceUtil::Mutex::TryLock lock(m_mtxBuffer);
  if (!lock.acquired())
    {
      A_LOG("Lock not aquired");
      lock.acquire(); //blocks function execution until mutex is available
    }
  _buf.push(_str);
  // out of function scope, mutex is released.
}

std::string listenerThread::buffer_front(std::queue<std::string>& _buf)
{
  // LOG_FILE(::fopen("/tmp/wheelsCtrl_nice.log", "w+"));
  IceUtil::Mutex::TryLock lock(m_mtxBuffer);
  if (!lock.acquired())
    {
      A_LOG("Lock not aquired");
      lock.acquire(); //blocks function execution until mutex is available
    }
  return _buf.front();
}

void listenerThread::buffer_pop(std::queue<std::string>& _buf)
{
  // LOG_FILE(::fopen("/tmp/wheelsCtrl_nice.log", "w+"));
  IceUtil::Mutex::TryLock lock(m_mtxBuffer);
  if (!lock.acquired())
    {
      A_LOG("Lock not aquired");
      lock.acquire(); //blocks function execution until mutex is available
    }
  _buf.pop();
}

std::vector<pcf::IndiProperty> listenerThread::parse_data(const std::string &_str,
                                                          std::queue<std::string> &_buf)
{
  // LOG_FILE(::fopen("/tmp/wheelsCtrl_nice.log", "w+"));
  U3_LOG("Parsing...");
  std::vector<std::string> vecTagName;
  std::vector<std::string::size_type> vecPosDefOpen, vecPosDefClose;
  std::vector<std::string::size_type> vecPosSetOpen, vecPosSetClose;
  // the following vector will contain vectors of positions for
  // open and close tag of XML blocks. It will be scanned 2 by 2
  std::vector< std::vector<std::string::size_type> > vecPosOpenClose;
  std::vector<pcf::IndiProperty> vecIndiProps;

  std::vector< std::vector<std::string::size_type> >::iterator itVec, itTmp;
  std::vector<std::string::size_type>::iterator itOpen, itClose, it1, it2;
  std::vector<std::string>::iterator itTagName;

  pcf::IndiXmlParser indiXml;
  pcf::IndiMessage indiMsg;

  std::string::size_type blockLen = 0;

  std::string openTag, closeTag;
  std::string strToParse, strXmlBlock, strErr = "";

  // this vector could be gives as input
  vecTagName.push_back("setTextVector");
  vecTagName.push_back("defTextVector");
  itTagName = vecTagName.begin();

  if (_str.find('\r') != std::string::npos)
    {
      int cnt = std::count(_str.begin(), _str.end(), '\r');

      A_LOG(cnt << " \\r still present in received data!");
      throw std::runtime_error("\\r still present in received data!");
    }

  if (_buf.empty())
    {
      U3_LOG("Buffer is empty");
      strToParse = _str;
    }
  else
    {
      if (_buf.size() > 1)
        {
          // every time data is received from socket there should be
          // ONE incomplete XML block. This should be completed
          // the next time data is received. Thus, the maximum size
          // for the buffer is one. If buffer size get to be bigger,
          // something is wrong (maybe prev. element has not been popped).
          // strErr = sprintf("Reception buffer containg more then one element (%d)", _buf.size()); // wrong sprintf usage. To be corrected
          strErr = "Reception buffer containg more then one element!";
          throw std::runtime_error(strErr);
        }
      U6_LOG("Buffer has one element.");
      if (buffer_front(_buf).find('\r') != std::string::npos)
        {
          A_LOG("\\r still present in received data!");
          throw std::runtime_error("\\r present in data stored in buffer!");
        }

      strToParse = buffer_front(_buf) + _str;
      buffer_pop(_buf);
    }
  // search for XML blocks

  search_xml_block(strToParse, vecTagName[0], vecPosSetOpen, vecPosSetClose);
  search_xml_block(strToParse, vecTagName[1], vecPosDefOpen, vecPosDefClose);

  vecPosOpenClose.push_back(vecPosSetOpen);
  vecPosOpenClose.push_back(vecPosSetClose);
  vecPosOpenClose.push_back(vecPosDefOpen);
  vecPosOpenClose.push_back(vecPosDefClose);

  if ((vecPosSetOpen.size() == 1) && (vecPosSetOpen.front() == std::string::npos) &&
      (vecPosSetClose.size() == 1) && (vecPosSetClose.front() == std::string::npos))
    {
      // set block not present
      U3_LOG("No " + vecTagName[0] + " XML block received");
    }
  if ((vecPosDefOpen.size() == 1) && (vecPosDefOpen.front() == std::string::npos) &&
      (vecPosDefClose.size() == 1) && (vecPosDefClose.front() == std::string::npos))
    {
      // def block not present
      U3_LOG("No " + vecTagName[1] + " XML block received");
    }

  U3_LOG("Trasforming XML blocks into INDI Properties...");
  U3_LOG("Vector of vectors has this many lines " << vecPosOpenClose.size());
  for (itVec = vecPosOpenClose.begin(); itVec < vecPosOpenClose.end(); itVec=2+itVec)
    {
      //U3_LOG(itVec-vecPosOpenClose.begin());
      // Initializing iterator to vector of postions for close tags
      itTmp = 1 + itVec;
      //U3_LOG(itTmp-vecPosOpenClose.begin());
      if ((itTmp) < vecPosOpenClose.end())
        itClose = (itTmp)->begin();
      else
        throw std::out_of_range("");

      for (itOpen=itVec->begin(); itOpen < itVec->end(); ++itOpen)
        {
          U5_LOG("open size " << itVec->size());
          U5_LOG("itOpen conent " << *itOpen);
          if (*itOpen != std::string::npos)
            {
              if (*itClose != std::string::npos)
                {
                  // complete XML block
                  U3_LOG("found complete XML block");
                  closeTag = "</" + *itTagName + ">";
                  blockLen = *itClose - *itOpen + closeTag.length();

                  strXmlBlock = strToParse.substr(*itOpen, blockLen);
                  indiXml.parseXml(strXmlBlock, strErr);
                  if (strErr != "")
                    {
                      E_LOG(strErr);
                      E_LOG(strXmlBlock);
                    }
                  indiMsg = indiXml.createIndiMessage();
                  vecIndiProps.push_back(indiMsg.getProperty());
                }
              else
                {
                  // open XML block
                  U3_LOG("found partial (open) XML block");
                  blockLen = strToParse.length() - *itOpen;
                  strXmlBlock = strToParse.substr(*itOpen, blockLen);
                  buffer_push(_buf, strXmlBlock);
                  U3_LOG("Buffer size " << _buf.size());
                }
            }
          else
            {
              U5_LOG("Era npos");
              if (*itClose != std::string::npos)
                {
                  // closing part of XML block
                  // this case should vefiry in this if statement
                    throw std::runtime_error("open XML block detected");
                }
            }
          try
            {
              ++itClose;
            } catch (std::exception err)
            {
              std::cerr << "Exceeded close tag vector size " << err.what() << std::endl;
              E_LOG(err.what());
              throw (err);
            }
        }
      // NOTE
      // vecTagName size must be equal to vecPosOpenClose divided by 2
      try
        {
          ++itTagName;
        } catch (std::exception err)
        {
          std::cerr << "Exceeded tag name vector size " << err.what() << std::endl;
          E_LOG(err.what());
          throw (err);
        }
    }
  U3_LOG("Parsing done.");
  return vecIndiProps;
}

void listenerThread::search_xml_block(const std::string &_strIn,
                                      const std::string &_blockName,
                                      std::vector<std::string::size_type> &_vecPosOpenOut,
                                      std::vector<std::string::size_type> &_vecPosCloseOut)
{
  // LOG_FILE(::fopen("/tmp/wheelsCtrl_nice.log", "w+"));
  std::string openTag = std::string("<" + _blockName);
  std::string closeTag = std::string("</" + _blockName + ">");
  std::vector<std::string::size_type>::iterator it;
  std::string::size_type tmpOpenPos, tmpClosePos;

  U3_LOG("Searching " + _blockName + " XML blocks");
  U3_LOG("Open tag " << openTag);
  U3_LOG("Close tag " << closeTag);
  tmpOpenPos = _strIn.find(openTag);
  tmpClosePos = _strIn.find(closeTag);
  _vecPosOpenOut.push_back(tmpOpenPos);
  _vecPosCloseOut.push_back(tmpClosePos);

  // check if close tag exists before open tag
  if (tmpOpenPos != std::string::npos)
    {
      if (tmpClosePos < tmpOpenPos) // close tag before open tag
        {
          U3_LOG("found close tag before open tag");
          it = _vecPosOpenOut.begin();
          _vecPosOpenOut.insert(it, std::string::npos);
          it = _vecPosOpenOut.begin();
          // search for close tag corresponding to open tag in tmpOpenPos
          tmpClosePos = _strIn.find(closeTag, tmpOpenPos);
          _vecPosCloseOut.push_back(tmpClosePos);
        }
    }


  tmpOpenPos = _strIn.find(openTag, tmpClosePos);
  while (tmpOpenPos != std::string::npos)
    {
      U3_LOG("while loop");
      _vecPosOpenOut.push_back(tmpOpenPos);
      tmpClosePos = _strIn.find(closeTag, tmpClosePos+1);
      _vecPosCloseOut.push_back(tmpClosePos);
      if (tmpClosePos != std::string::npos)
        {
          tmpOpenPos = _strIn.find(openTag, tmpOpenPos+1);
        }
      else
        {
          break;
        }
    }
  U3_LOG("_vecPosOpenOut.size() = " << _vecPosOpenOut.size());
  U3_LOG("_vecPosCloseOut.size() = " << _vecPosCloseOut.size());
}


boost::signals2::connection listenerThread::add_signal_onReceived(const signalReceivedData::slot_type& subscriber)
{
  return m_sigPropertyReceived.connect(subscriber);
}
