/*
 * select
 */

#include <sys/select.h>
#include <vdr/plugin.h>
#include <vdr/timers.h>
#include "sock.h"
#include "select.h"
#include "handler.h"
#include "helpers.h"

struct node
{
  cVdrmonClientSocket * socket;
  node * next;
};

cSelect::cSelect()
{
  readeventpipe = NULL;
  writeeventpipe = NULL;
  serversocket = NULL;
  clientsockets = NULL;
  clientsocketcount = 0;
  handler = new cHandler;
  pollfds = NULL;
  stopped = false;
  nexttimer = 0;
  shutdown = 0;
}

cSelect::~cSelect()
{
  if (readeventpipe)
    delete readeventpipe;

  if (writeeventpipe)
    delete writeeventpipe;

  if (serversocket)
    delete serversocket;

  while(clientsockets)
  {
    node * next = clientsockets->next;
    delete clientsockets->socket;
    delete clientsockets;
    clientsockets = next;
  }

  if (handler)
    delete handler;

  if (pollfds)
    delete pollfds;
}

void cSelect::SetEventPipe(int pipe[2])
{
  // remember both sides of the pipe
  readeventpipe = new cVdrmonClientSocket();
  readeventpipe->Attach(pipe[0]);
  writeeventpipe = new cVdrmonClientSocket();
  writeeventpipe->Attach(pipe[1]);
}

void cSelect::SetServerSocket(cVdrmonServerSocket * sock)
{
  serversocket = sock;
}

void cSelect::AddClientSocket(cVdrmonClientSocket * sock)
{
  // remember socket
  node * newnode = new node;
  newnode->next = clientsockets;
  newnode->socket = sock;
  clientsockets = newnode;
  clientsocketcount++;
}

void cSelect::RemoveClientSocket(cVdrmonClientSocket * sock)
{
  node * curnode = clientsockets;
  node * lastnode = NULL;
  while(curnode)
  {
    if (curnode->socket == sock)
    {
      // unlink node
      if (lastnode)
        lastnode->next = curnode->next;
      else
        clientsockets = curnode->next;

      // free socket and node
      delete curnode->socket;
      delete curnode;

      clientsocketcount--;
      break;
    }
    lastnode = curnode;
    curnode = curnode->next;
  }

  if (clientsockets)
  {
    curnode = clientsockets;
    while (curnode)
    {
      curnode = curnode->next;
    }
  }
}

cVdrmonClientSocket * cSelect::GetClientSocket(int sock)
{
  node * curnode = clientsockets;
  while (curnode)
  {
    if (curnode->socket->GetSocket() == sock)
      return curnode->socket;
    curnode = curnode->next;
  }

  return NULL;
}

bool cSelect::Action()
{
  for(;!stopped;)
  {
    if (!Poll())
      return false;
  }

  return true;
}

void cSelect::CreatePollfds()
{
  // construct pollfd array
  // we need one pollfd for the eventpipe,
  // the serversocket and each clientsocket
  pollfds = new struct pollfd[clientsocketcount+2];
  pollfds[0].fd = readeventpipe->GetSocket();
  pollfds[0].events = POLLIN;

  pollfds[1].fd = serversocket->GetSocket();
  pollfds[1].events = POLLIN;

  node * curnode = clientsockets;
  int i = 2;
  while (curnode)
  {
    pollfds[i].fd = curnode->socket->GetSocket();
    pollfds[i].events = POLLIN;
    if (curnode->socket->WritePending())
      pollfds[i].events |= POLLOUT;
    pollfds[i++].revents = 0;
    curnode = curnode->next;
  }
}

bool cSelect::Poll()
{
  // poll for events
  CreatePollfds();
  int rc = poll(pollfds, clientsocketcount+2, 1000);
  if (rc < 0)
  {
    LOG_ERROR;
    delete pollfds;
    return false;
  }

  // next timer changed?
  time_t starttime = 0;
  time_t stoptime = 0;
  string text = cHelpers::GetNextRecordings(starttime);
  if (starttime != nexttimer)
  {
    // save start time and notify all clients
    nexttimer = starttime;
    NotifyClients(text);
  }
  text = cHelpers::GetShutdownTime(stoptime);
  if (stoptime != shutdown)
  {
    shutdown = stoptime;
    NotifyClients(text);
  }

  // just timeout? then no more checks
  if (rc == 0)
    return true;

  // vdr event?
  if (pollfds[0].revents & POLLIN)
  {
    // get answer string (possibly multiple lines)
    string msg;
    if (handler->HandleVdrEvent(readeventpipe, msg))
    {
      // forward event to all clients
      NotifyClients(msg);
    }
  }


  // client requests or outstanding writes
  for(int i = 2; i < clientsocketcount+2; i++)
  {
    cVdrmonClientSocket * sock = GetClientSocket(pollfds[i].fd);
    if (sock)
    {
      if (pollfds[i].revents & (POLLIN|POLLHUP))
      {
        // client request
        handler->HandleClientRequest(sock);

        // disconnect?
        if (sock->Disconnected())
        {
          RemoveClientSocket(sock);
        }
      }
      else if (pollfds[i].revents & POLLOUT)
      {
        // possibly outstanding writes
        sock->Flush();
      }
    }
  }

  // new client?
  if (pollfds[1].revents & POLLIN)
  {
    // get client socket
    cVdrmonClientSocket * sock = serversocket->Accept();
    if (sock)
    {
      // Add client socket
      AddClientSocket(sock);
      // Send current data
      handler->HandleNewClient(sock);
    }
  }

  delete pollfds;

  return true;
}

void cSelect::DispatchVdrEvent(string event)
{
  writeeventpipe->PutLine(event);
}

void cSelect::NotifyClients(string event)
{
  node * curnode = clientsockets;
  while (curnode)
  {
    curnode->socket->PutLine(event);
    curnode = curnode->next;
  }
}
