/*
  Copied from PLIB into SimGear
  
     PLIB - A Suite of Portable Game Libraries
     Copyright (C) 1998,2002  Steve Baker
 
     This library is free software; you can redistribute it and/or
     modify it under the terms of the GNU Library General Public
     License as published by the Free Software Foundation; either
     version 2 of the License, or (at your option) any later version.
 
     This library is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     Library General Public License for more details.
 
     You should have received a copy of the GNU Library General Public
     License along with this library; if not, write to the Free Software
     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 
     For further information visit http://plib.sourceforge.net

     $Id: netChannel.cxx 1906 2004-03-22 19:44:50Z sjbaker $
*/

// TODO:
// have all socket-related functions assert that the socket has not
// been closed.  [a read event may close it, and a write event may try
// to write or something...]
// Maybe assert valid handle, too?

#include "sg_netChannel.hxx"

#include <memory>
#include <cassert>
#include <cstring>

#include <simgear/debug/logstream.hxx>

namespace simgear  {

static NetChannel* channels = 0 ;

NetChannel::NetChannel ()
{
  closed = true ;
  connected = false ;
  accepting = false ;
  write_blocked = false ;
  should_delete = false ;

  next_channel = channels ;
  channels = this ;
}
  
NetChannel::~NetChannel ()
{
  close();

  NetChannel* prev = NULL ;

  for ( NetChannel* ch = channels; ch != NULL;
                    ch = ch -> next_channel )
  {
    if (ch == this)
    {
      ch = ch -> next_channel ;
      if ( prev != NULL )
        prev -> next_channel = ch ;
      else
        channels = ch ;
      next_channel = 0 ;
      break;
    }
    prev = ch ;
  }
}
  
void
NetChannel::setHandle (int handle, bool is_connected)
{
  close () ;
  Socket::setHandle ( handle ) ;
  connected = is_connected ;
  //if ( connected ) this->handleConnect();
  closed = false ;
}

bool
NetChannel::open (void)
{
  close();
  if (Socket::open(true)) {
    closed = false ;
    setBlocking ( false ) ;
    return true ;
  }
  return false ;
}

int
NetChannel::listen ( int backlog )
{
  accepting = true ;
  return Socket::listen ( backlog ) ;
}

int
NetChannel::connect ( const char* host, int port )
{
  int result = Socket::connect ( host, port ) ;
  if (result == 0) {
    connected = true ;
    //this->handleConnect();
    return 0;
  } else if (isNonBlockingError ()) {
    return 0;
  } else {
    // some other error condition
    this->handleError (result);
    close();
    return -1;
  }
}

int
NetChannel::send (const void * buffer, int size, int flags)
{
  int result = Socket::send (buffer, size, flags);
  
  if (result == (int)size) {
    // everything was sent
    write_blocked = false ;
    return result;
  } else if (result >= 0) {
    // not all of it was sent, but no error
    write_blocked = true ;
    return result;
  } else if (isNonBlockingError ()) {
    write_blocked = true ;
    return 0;
  } else {
    this->handleError (result);
    close();
    return -1;
  }
  
}

int
NetChannel::recv (void * buffer, int size, int flags)
{
  int result = Socket::recv (buffer, size, flags);
  
  if (result > 0) {
    return result;
  } else if (result == 0) {
    close();
    return 0;
  } else if (isNonBlockingError ()) {
    return 0;
  } else {
    this->handleError (result);
    close();
    return -1;
  }
}

void
NetChannel::close (void)
{
  if ( !closed )
  {
    this->handleClose();
  
    closed = true ;
    connected = false ;
    accepting = false ;
    write_blocked = false ;
  }

  Socket::close () ;
}

void
NetChannel::handleReadEvent (void)
{
  if (accepting) {
    if (!connected) {
      connected = true ;
      //this->handleConnect();
    }
    this->handleAccept();
  } else if (!connected) {
    connected = true ;
    //this->handleConnect();
    this->handleRead();
  } else {
    this->handleRead();
  }
}

void
NetChannel::handleWriteEvent (void)
{
  if (!connected) {
    connected = true ;
    //this->handleConnect();
  }
  write_blocked = false ;
  this->handleWrite();
}

bool
NetChannel::poll (unsigned int timeout)
{
  if (!channels)
    return false ;
  
  enum { MAX_SOCKETS = 256 } ;
  Socket* reads [ MAX_SOCKETS+1 ] ;
  Socket* writes [ MAX_SOCKETS+1 ] ;
  Socket* deletes [ MAX_SOCKETS+1 ] ;
  int nreads = 0 ;
  int nwrites = 0 ;
  int ndeletes = 0 ;
  int nopen = 0 ;
  NetChannel* ch;
  for (  ch = channels; ch != NULL; ch = ch -> next_channel )
  {
    if ( ch -> should_delete )
    {
      assert(ndeletes<MAX_SOCKETS);
      deletes[ndeletes++] = ch ;
    }
    else if ( ! ch -> closed )
    {
      nopen++ ;
      if (ch -> readable()) {
        assert(nreads<MAX_SOCKETS);
        reads[nreads++] = ch ;
      }
      if (ch -> writable()) {
        assert(nwrites<MAX_SOCKETS);
        writes[nwrites++] = ch ;
      }
    }
  }
  reads[nreads] = NULL ;
  writes[nwrites] = NULL ;
  deletes[ndeletes] = NULL ;

  int i ;
  for ( i=0; deletes[i]; i++ )
  {
    ch = (NetChannel*)deletes[i];
    delete ch ;
  }

  if (!nopen)
    return false ;
  if (!nreads && !nwrites)
    return true ; //hmmm- should we shutdown?

  Socket::select (reads, writes, timeout) ;

  for ( i=0; reads[i]; i++ )
  {
    ch = (NetChannel*)reads[i];
    if ( ! ch -> closed )
      ch -> handleReadEvent();
  }

  for ( i=0; writes[i]; i++ )
  {
    ch = (NetChannel*)writes[i];
    if ( ! ch -> closed )
      ch -> handleWriteEvent();
  }

  return true ;
}

void
NetChannel::loop (unsigned int timeout)
{
  while ( poll (timeout) ) ;
}


void NetChannel::handleRead (void) {
  SG_LOG(SG_IO, SG_WARN, "Network:" << getHandle() << ": unhandled read");
}

void NetChannel::handleWrite (void) {
  SG_LOG(SG_IO, SG_WARN, "Network:" << getHandle() << ": unhandled write");
}

void NetChannel::handleAccept (void) {
  SG_LOG(SG_IO, SG_WARN, "Network:" << getHandle() << ": unhandled accept");
}

void NetChannel::handleError (int error) {
  SG_LOG(SG_IO, SG_WARN,"Network:" << getHandle() << ": errno: " << strerror(errno) <<"(" << errno << ")");
}

} // of namespace simgear
