/*
 * extendes sockets
 */
#include <unistd.h>
#include <vdr/plugin.h>
#include "sock.h"

static int clientno = 0;

/*
 * cVdrmonSocket
 */
cVdrmanagerSocket::cVdrmanagerSocket()
{
  sock = -1;
}

cVdrmanagerSocket::~cVdrmanagerSocket()
{
  Close();
}

void cVdrmanagerSocket::Close()
{
  if (socket >= 0)
  {
    close(sock);
    sock = -1;
  }
}

int cVdrmanagerSocket::GetSocket()
{
  return sock;
}

bool cVdrmanagerSocket::MakeDontBlock()
{
  // make it non-blocking:
  int oldflags = fcntl(sock, F_GETFL, 0);
  if (oldflags < 0) {
    LOG_ERROR;
    return false;
  }
  oldflags |= O_NONBLOCK;
  if (fcntl(sock, F_SETFL, oldflags) < 0) {
    LOG_ERROR;
    return false;
  }

  return true;
}

const char * cVdrmanagerSocket::GetPassword() {
  return password;
}

/*
 * cVdrmonServerSocket
 */
cVdrmanagerServerSocket::cVdrmanagerServerSocket() : cVdrmanagerSocket()
{
}

cVdrmanagerServerSocket::~cVdrmanagerServerSocket()
{
}

bool cVdrmanagerServerSocket::Create(int port, const char * password)
{
  // save password
  this->password = password;

  // create socket
  sock = socket(PF_INET, SOCK_STREAM, 0);
  if (sock < 0) {
     LOG_ERROR;
     return false;
  }

  // allow it to always reuse the same port:
  int ReUseAddr = 1;
  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr, sizeof(ReUseAddr));

  // bind to address
  struct sockaddr_in name;
  name.sin_family = AF_INET;
  name.sin_port = htons(port);
  name.sin_addr.s_addr = htonl(INADDR_ANY);
  if (bind(sock, (struct sockaddr *)&name, sizeof(name)) < 0) {
     LOG_ERROR;
     Close();
     return false;
  }

  // make it non-blocking:
  if (!MakeDontBlock())
  {
    Close();
    return false;
  }

  // listen to the socket:
  if (listen(sock, 100) < 0) {
    LOG_ERROR;
    Close();
    return false;
  }

  return true;
}

cVdrmanagerClientSocket * cVdrmanagerServerSocket::Accept()
{
  cVdrmanagerClientSocket * newsocket = NULL;

  // accept the connection
  struct sockaddr_in clientname;
  uint size = sizeof(clientname);
  int newsock = accept(sock, (struct sockaddr *)&clientname, &size);
  if (newsock > 0)
  {
    // create client socket
    newsocket = new cVdrmanagerClientSocket(password);
    if (!newsocket->Attach(newsock))
    {
      delete newsocket;
      return NULL;
    }

    bool accepted = SVDRPhosts.Acceptable(clientname.sin_addr.s_addr);
    if (!accepted)
    {
      newsocket->PutLine(string("NACC Access denied.\n"));
      newsocket->Flush();
      delete newsocket;
      newsocket = NULL;
    }
    dsyslog("[vdrmon] connect from %s, port %hd - %s",
        inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port),
        accepted ? "accepted" : "DENIED");
  }
  else if (errno != EINTR && errno != EAGAIN)
    LOG_ERROR;

  return newsocket;
}

/*
 * cVdrmonClientSocket
 */
cVdrmanagerClientSocket::cVdrmanagerClientSocket(const char * password)
{
  readbuf = writebuf = "";
  disconnected = false;
  client = ++clientno;
  this->password = password;
  login = false;
}

cVdrmanagerClientSocket::~cVdrmanagerClientSocket()
{
}

bool cVdrmanagerClientSocket::IsLineComplete()
{
  // check a for complete line
  string::size_type pos = readbuf.find("\r", 0);
  if (pos == string::npos)
    pos = readbuf.find("\n");
  return pos != string::npos;
}

bool cVdrmanagerClientSocket::GetLine(string& line)
{
  // check the line
  string::size_type pos = readbuf.find("\r", 0);
  if (pos == string::npos)
    pos = readbuf.find("\n", 0);
  if (pos == string::npos)
    return false;

  // extract the line ...
  line = readbuf.substr(0, pos);

  // handle \r\n
  if (readbuf[pos] == '\r' && readbuf.length() > pos && readbuf[pos+1] == '\n')
    pos++;

  // ... and move the remainder
  readbuf = readbuf.substr(pos+1);

  return true;
}

bool cVdrmanagerClientSocket::Read()
{
  if (Disconnected())
    return false;

  int rc;
  bool len = 0;
  char buf[2001];
  while ((rc = read(sock, buf, sizeof(buf)-1)) > 0)
  {
    buf[rc] = 0;
    readbuf += buf;
    len += rc;
  }

  if (rc < 0 && errno != EAGAIN)
  {
    LOG_ERROR;
    return false;
  }
  else if (rc == 0)
  {
    disconnected = true;
  }

  return len > 0;
}

bool cVdrmanagerClientSocket::Disconnected()
{
  return disconnected;
}

void cVdrmanagerClientSocket::Disconnect()
{
  disconnected = true;
}

bool cVdrmanagerClientSocket::PutLine(string line)
{
  // add line to write buffer
  writebuf += line;

  // data present?
  if (writebuf.length() > 0)
  {
    // write so many bytes as possible
    int rc = write(sock, writebuf.c_str(), writebuf.length());
    if (rc < 0 && errno != EAGAIN)
    {
      LOG_ERROR;
      return false;
    }

    // move the remainder
    if (rc > 0)
      writebuf = writebuf.substr(rc, writebuf.length()-rc);
  }

  return true;
}

bool cVdrmanagerClientSocket::Flush()
{
  string empty = "";
  return PutLine(empty);
}

bool cVdrmanagerClientSocket::Attach(int fd)
{
  sock = fd;
  return MakeDontBlock();
}

int cVdrmanagerClientSocket::GetClientId()
{
  return client;
}

bool cVdrmanagerClientSocket::WritePending()
{
  return writebuf.length() > 0;
}

bool cVdrmanagerClientSocket::IsLoggedIn() {
  return login || !password || !*password;
}

void cVdrmanagerClientSocket::SetLoggedIn() {
  login = true;
}