// This may look like C code, but it is really -*- C++ -*-
// 
// <copyright> 
//  
//  Copyright (c) 1994
//  Institute for Information Processing and Computer Supported New Media (IICM), 
//  Graz University of Technology, Austria. 
//  
// </copyright> 
// 
// 
// <file> 
// 
// Name:        connect.C
// 
// Purpose:     
// 
// Created:     6 Jul 95   Joerg Faschingbauer
// 
// Modified:    
// 
// Description: 
// 
// 
// </file> 
#include "connect.h"

#include "iohandler.h"
#include "dispatcher.h"

#include <hyperg/utils/assert.h>
#include <hyperg/utils/hgunistd.h>
#include <hyperg/utils/list.h>
#include <hyperg/utils/new.h>
#include <hyperg/utils/smartptr.h>
#include <hyperg/utils/verbose.h>

// both for perror() on the various platforms
#include <stdio.h>
#include <errno.h>

#include <fcntl.h>

#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

// --------------------------------------------------------------------
class ConnectRequest : public DLListNode, public IOHandler {
public:
   ConnectRequest (Connectable* callback, const ConnectArgs&, Connecter*) ;
   ~ConnectRequest() ;

   boolean ok() const { return socket_>=0; }
   boolean operator !() const { return !ok(); }
   operator void*() const { return (void*)ok(); }

   Connectable* callback() { return callback_; }
   const ConnectArgs& args() const { return args_; }

   // IOHandler part: connection established/refused
   virtual int outputReady (int) ;
   // IOHandler part: select() returned with *my* timeout expired
   virtual void timerExpired (long sec, long usec) ;
   
private:
   Connectable* callback_ ;
   ConnectArgs args_ ;
   Connecter* connecter_ ;

   int socket_ ;

private:
   ConnectRequest (const ConnectRequest&) {} 
   ConnectRequest& operator = (const ConnectRequest&) { return *this; }
} ;
DLListdeclare (ConnectRequests, ConnectRequest)

ConnectRequest :: ConnectRequest (Connectable* callback, const ConnectArgs& args, Connecter* c)
: callback_(callback),
  args_(args),
  connecter_(c) {
     hgassert (callback_, "ConnectRequest::ConnectRequest(): no-one to call back") ;
     hgassert (args.address(), "ConnectRequest::ConnectRequest(): no or invalid address") ;

     if ((socket_ = ::socket (AF_INET, SOCK_STREAM, 0)) < 0) {
        ::perror ("ConnectRequest::ConnectRequest(): ::socket()") ;
        return ;
     }
   
     // >>> set nonblocking
     if (::set_blocking (socket_, false) < 0) {
        socket_ = -1 ;
        return ;
     }        
     // <<<

     // initiate the connect request
     if (::connect (socket_, (sockaddr*)&args_.address(), sizeof (INETAddress)) < 0) {
#ifdef WIN32
       int iError;
       if ((iError=WSAGetLastError()) != WSAEWOULDBLOCK) {
         cerr << "ConnectRequest::ConnectRequest(): ::connect() code:" << iError<<endl;
#else
       if (::errno != EINPROGRESS) {
           ::perror ("ConnectRequest::ConnectRequest(): ::connect()") ;
#endif
           HGSOCKCLOSE (socket_) ;
           socket_ = -1 ;
           return ;
        }
     }

     // watch ...
     Dispatcher::instance().link (socket_, Dispatcher::WriteMask, this) ;
     if (args_.timeout() > 0)
        Dispatcher::instance().startTimer (args_.timeout(), 0, this) ;
}

ConnectRequest :: ~ConnectRequest() {
   if (socket_ >= 0) {
      // this is only the case if I am removed when I have not yet completed
      Dispatcher::instance().unlink (socket_) ;
      if (args_.timeout() > 0)
         Dispatcher::instance().stopTimer (this) ;
      ::close (socket_) ;
   }
}

int ConnectRequest :: outputReady (int fd) {
   DEBUGNL ("ConnectRequest::outputReady()") ;
   hgassert (fd==socket_, "ConnectRequest::outputReady(): not my file number") ;

   // !!: first do a correct cleanup of myself (by having my
   // connecter_ remove me). then issue the callback. this is because
   // the one who issued me (me, the connect request) might decide to
   // try again here if I failed.

   Connectable* callback = callback_ ;
   ConnectArgs a = args_ ;

   socket_ = -1 ;
   connecter_->rem_requ_(this) ; 
   // ATTENTION: this is invalid now (deleted by the above call).
   // have to work with fd, a and callback from now on.

   Dispatcher::instance().unlink (fd) ;
   if (a.timeout() > 0)
      Dispatcher::instance().stopTimer (this) ;

// this paranoia sucks under NT 4.0
#ifndef WIN32
   // check if I am connected (is there a better way?++++)
   sockaddr_in that ;
   int thatlen = sizeof (that) ;
   if (::getpeername (fd, (sockaddr*)&that, &thatlen) < 0) {
      if (! (::errno==ENOTCONN || ::errno==ECONNREFUSED))
         ::perror ("ConnectRequest::outputReady(): ::getpeername()") ;
      callback->connectError (a) ;
      ::close (fd) ;
   }
   else {
#endif
      // connected. make a class INETSocket out of my socket_.
      INETSocketPtr sp (HGNEW (INETSocket (fd, true))) ;
      callback->connected (a, sp) ;
#ifndef WIN32
   }
#endif

   return 0 ;
}

void ConnectRequest :: timerExpired (long, long) {
   DEBUGNL ("ConnectRequest::timerExpired()") ;

   Dispatcher::instance().unlink (socket_) ;
   ::close (socket_) ;
   socket_ = -1 ;

   // see the comments in outputReady() for the below code.
   Connectable* callback = callback_ ;
   ConnectArgs a = args_ ;

   connecter_->rem_requ_(this) ;

   callback->connectError (a) ;
}




// --------------------------------------------------------------------
Connecter* Connecter :: instance_ = nil ; // not necessary, but...

Connecter :: Connecter() 
: requests_(HGNEW (ConnectRequests)) {}

Connecter& Connecter :: instance() {
   return instance_ ?  *instance_ :  *(instance_ = HGNEW (Connecter)) ;
}

boolean Connecter :: beginRequest (Connectable* callback, const ConnectArgs& args) {
   hgassert (args.address().ok(), "Connecter::beginRequest(): invalid address") ;
   hgassert (callback, "Connecter::beginRequest(): no callback specified") ;

   ConnectRequest* r = HGNEW (ConnectRequest (callback, args, this)) ;

   if (! r->ok()) {
      // according to the request class: ::connect() failed immediately
      HGDELETE (r) ;
      return false ;
   }

   requests_->addTail (r) ;
   return true ;
}

boolean Connecter :: endRequest (const Connectable* c, long refno) {
   int nremoved = 0 ;

   ConnectRequest* curr = requests_->getFirst() ;
   while (curr) {
      ConnectRequest* next = requests_->getNext (curr) ;

      if (curr->callback() == c  &&  (refno < 0 || refno == curr->args().refno())) {
         requests_->remove (curr) ;
         HGDELETE (curr) ;
         nremoved ++ ;
      }

      curr = next ;
   }

   return nremoved > 0 ;
}

void Connecter :: rem_requ_(const ConnectRequest* r) {
   for (ConnectRequest* run = requests_->getFirst() ; run ; run = requests_->getNext(run))
      if (run == r) {
         requests_->remove (run) ;
         HGDELETE (run) ;
         return ;
      }
}
