/* 
 * Written by Jae An (jae_young_an@yahoo.com) 
 *
 * adapted for SEMS by Stefan Sayer stefan.sayer at iptego.com
 */ 
#include "XmlRpcServer.h" 
#include "XmlRpcServerConnection.h" 
#include "XmlRpcServerMethod.h" 
#include "XmlRpcSocket.h" 
#include "XmlRpcUtil.h" 
#include "XmlRpcException.h" 
#include "MultithreadXmlRpcServer.h" 
#include "AmUtils.h"
#include "AmEventDispatcher.h"
#include "log.h"
 
#include "unistd.h"
#include <errno.h>

using namespace XmlRpc; 
WorkerThread::WorkerThread(MultithreadXmlRpcServer* chief) 
  : running(true), runcond(false), chief(chief) {
} 
 
// call this method before calling run 
void WorkerThread::addXmlRpcSource(XmlRpcSource* source,unsigned eventMask) 
{ 
  dispatcher.addSource(source,eventMask); 
  wakeup();
}  

void WorkerThread::wakeup() {
  runcond.set(true);
}

void WorkerThread::run() 
{ 
  running.set(true);

  string eventqueue_name = "MT_XMLRPC_SERVER_" + long2str((unsigned long)pthread_self());

  // register us as SIP event receiver for MOD_NAME
  AmEventDispatcher::instance()->addEventQueue(eventqueue_name, this);

  chief->reportBack(this);

  while (running.get()) {
    runcond.wait_for();
    dispatcher.work(-1.0); 
  
    dispatcher.clear(); // close socket and others ...  
    runcond.set(false);

    /* tell chief we can work again */
    chief->reportBack(this);
  }

  AmEventDispatcher::instance()->delEventQueue(eventqueue_name);
  DBG("WorkerThread stopped.\n");
} 

void WorkerThread::postEvent(AmEvent* ev) {

  if (ev->event_id == E_SYSTEM) {
    AmSystemEvent* sys_ev = dynamic_cast<AmSystemEvent*>(ev);
    if (sys_ev) {
      if (sys_ev->sys_event == AmSystemEvent::ServerShutdown) {
	DBG("XMLRPC worker thread received system Event: ServerShutdown, stopping\n");
	running.set(false);
	runcond.set(true);
      }
      return;
    }
  }
  WARN("unknown event received\n");
}

void WorkerThread::on_stop() {
}

MultithreadXmlRpcServer::MultithreadXmlRpcServer()
  : XmlRpcServer(), have_waiting(false)   {
}

// Wait until all the worker threads are done. 
MultithreadXmlRpcServer::~MultithreadXmlRpcServer() 
{ 
  for (std::vector<WorkerThread*>::iterator it=
	 workers.begin(); it != workers.end();it++) {
    (*it)->stop();
    (*it)->join();
    delete *it;
  }
} 

// Accept a client connection request and create a connection to 
// handle method calls from the client. 
void MultithreadXmlRpcServer::acceptConnection() 
{ 
  int s = XmlRpcSocket::accept(this->getfd()); 
  if (s < 0) 
    { 
      if (errno != EAGAIN && errno != EWOULDBLOCK) {
	ERROR("MultithreadXmlRpcServer::acceptConnection: Could not accept connection (%s).",
			XmlRpcSocket::getErrorMsg().c_str()); 

	if ((errno == EMFILE) || (errno == ENFILE)) {
	  // would cause fast loop because we don't get the connection from the listening socket
	  usleep(500000);
	}
      }
      
    }
  else if ( ! XmlRpcSocket::setNonBlocking(s))
    {
      XmlRpcSocket::close(s);
      ERROR("XmlRpcServer::acceptConnection: Could not set socket "
	    "to non-blocking input mode (%s).\n", 
	    XmlRpcSocket::getErrorMsg().c_str());
    }
  else // Notify the dispatcher to listen for input on this source when we are in work() 
    { 
      WorkerThread* thr = NULL;
      while (!thr) {
	if (!have_waiting.get()) 
	    have_waiting.wait_for();
	thr = getIdleThread();
      }
      thr->addXmlRpcSource(this->createConnection(s), XmlRpcDispatch::ReadableEvent); 
 
      //       // Uh oh.. all threads are busy...  
      //       // Just close the connection so that the rejected client would try again. 
      //       XmlRpcSocket::close(s); 
      //       XmlRpcUtil::error("MultithreadXmlRpcServer::acceptConnection: All threads are busy. Rejected a client"); 
    } 
}

void MultithreadXmlRpcServer::reportBack(WorkerThread* thr) {
  waiting_mut.lock();
  waiting.push(thr);
  have_waiting.set(true);
  waiting_mut.unlock();
}

WorkerThread* MultithreadXmlRpcServer::getIdleThread() {
  WorkerThread* res = NULL;
  waiting_mut.lock();
  if (!waiting.empty()) {
    res = waiting.front();
    waiting.pop();
  }
  have_waiting.set(!waiting.empty());
  waiting_mut.unlock();
  return res;
}

void MultithreadXmlRpcServer::createThreads(unsigned int n) {
  for (unsigned int i=0;i<n;i++) {
    WorkerThread* thr = new WorkerThread(this);
    workers.push_back(thr);
    thr->start();
  }
}
