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

#include "assert.h"

#include <hyperg/utils/environ.h>

#include <Dispatch/dispatcher.h>

#include <fstream.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>



#define VERBOSE
#include "verbose.h"




// --------------------------------------------------------------------
const int DcSender :: def_maxread = 1024 ;
const long DcSender :: def_timeout = 60 ;

DcSender :: DcSender (int port, int maxread, long timeout) 
: port_(nil),
  portno_(port),
  sockno_(-1),
  max_read_(maxread),
  timeout_(timeout),
  timer_set_(false),
  data_(new char[maxread]),
  state_(NOTYETRUNNING),
  unlink_on_output_ready_(false),
  first_time_(true) {}

DcSender :: DcSender (const HgHeader& header, int port, int maxread, long timeout) 
: port_(nil),
  portno_(port),
  sockno_(-1),
  max_read_(maxread),
  timeout_(timeout),
  timer_set_(false),
  data_(new char[maxread]),
  state_(NOTYETRUNNING),
  unlink_on_output_ready_(false),
  hdr_(header),
  first_time_(true) {}

DcSender :: ~DcSender() {
   delete_port_() ;
   delete_socket_() ;
   stop_timer_() ;
   delete[] data_ ;
}

void DcSender :: set_header_(const HgHeader& h) {
   hgassert (state_==NOTYETRUNNING, "DcSender::header(): cannot set header when running") ;
   hdr_ = h ;
}

int DcSender :: port() const {
   return port_ ?  port_->port() :  -1 ;
}

boolean DcSender :: ok() const {
   return state_==NOTYETRUNNING || state_==RUNNING || state_==FINISHED ;
}

int DcSender :: inputReady (int fd) {
   DEBUGNL ("DcSender::inputReady()") ;
   if (sockno_ < 0) {
      // this must be my port
      hgassert (port_&&port_->fd()==fd, 
                "DcSender::inputReady(): port_ nil or wrong file descriptor on accept") ;
      
      stop_timer_() ;

      if (! port_->accept (sockno_)) {
         DEBUGNL ("DcSender::inputReady(): could not accept()") ;
         sockno_ = -1 ; // be sure to reset it to "invalid" (necessary? changed by accept?)
         state_ = NOACCEPT ;
      }
      else {
         // set the connection nonblocking
         int flags = ::fcntl (sockno_, F_GETFL, 0) ;
         if (flags < 0) {
            perror ("DcSender::inputReady(): fcntl(F_GETFL)") ;
            abort() ;
         }
         flags |= O_NONBLOCK ;
         if (::fcntl (sockno_, F_SETFL, flags) < 0) {
            perror ("DcSender::inputReady(): fcntl(F_SETFL)") ;
            abort() ;
         }

         Dispatcher::instance().link (sockno_, Dispatcher::WriteMask, this) ;
         Dispatcher::instance().link (sockno_, Dispatcher::ReadMask, this) ;

         start_timer_() ;

         state_ = RUNNING ;
      }

      delete_port_() ;
      return -1 ; // the Dispatcher will unlink me from my port fd
   }
   else {
      // it is input on my dc connection
      hgassert (sockno_>=0, 
                "DcSender::inputReady(): not yet accepted or wrong file descriptor on read") ;

      // check to see if the connection is closed 
      char c ;
      int nread = ::read (sockno_, &c, 1) ;
      if (nread <= 0)
         state_ = CLOSED ;
      else { // data input on my connection
         state_ = FATAL ;
      }

      stop_timer_() ;
      delete_socket_() ;
      
      return -1 ;
   }
}

int DcSender :: outputReady (int) {
   DEBUGNL ("DcSender::outputReady()") ;
   hgassert (state_==RUNNING, "DcSender::outputReady(): invalid state_") ;
   hgassert (sockno_>=0, "DcSender::outputReady(): not yet accepted or wrong file descriptor") ;

   if (first_time_) {
      first_time_ = false ;
      if (hdr_) {
         RString s ;
         hdr_.write (s) ;
         int nwritten = ::write (sockno_, s.string(), s.length()) ;

         if (nwritten <= 0) { // ++++ what do I do with (nwritten==0)? can that happen?
            state_ = CLOSED ; // ++++ (closed?)
            stop_timer_() ;
            delete_socket_() ;
            return -1 ;
         }
         if (nwritten < s.length()) {
            rest_ = RString (s.string() + nwritten, s.length() - nwritten) ;
         }
         reset_timer_() ;
         return 0 ;
      }
   }

   if (rest_.length()) {
      DEBUGNL ("DcSender::outputReady(): writing rest: "<<rest_.length()) ;
      int nwritten = ::write (sockno_, rest_.string(), rest_.length()) ;
      if (nwritten <= 0) {
         state_ = CLOSED ;
         stop_timer_() ;
         delete_socket_() ;
         return -1 ;
      }
      if (nwritten < rest_.length()) {
         DEBUGNL ("DcSender::outputReady(): left rest writing rest: "<<(rest_.length()-nwritten)) ;
         rest_ = RString (rest_.string() + nwritten, rest_.length() - nwritten) ;
      }
      else {
         rest_ = RString() ;
      }
      reset_timer_() ;
      return 0 ;
   }

   // try read new data
   int nread = read_() ;
      
   if (nread < 0) {
      // read error
      DEBUGNL ("DcSender::outputReady(): NOREAD") ;
      state_ = NOREAD ;
      stop_timer_() ;
      delete_socket_() ;
      return -1 ;
   }
   if (nread == 0) {
      // eof
      DEBUGNL ("DcSender::outputReady(): eof") ;
      state_ = FINISHED ;
      stop_timer_() ;
      delete_socket_() ;
      return -1 ;
   }

   // ok
   int nwritten = ::write (sockno_, data_, nread) ;
   if (nwritten <= 0) {
      DEBUGNL ("DcSender::outputReady(): error writing data just read") ;
      state_ = CLOSED ;
      stop_timer_() ;
      delete_socket_() ;
      return -1 ;
   }
   if (nwritten < nread) {
      DEBUGNL ("DcSender::outputReady(): rest: "<<(nread-nwritten)) ;
      rest_ = RString (data_ + nwritten, nread - nwritten) ;
   }
   reset_timer_() ;
   return 0 ;
}

void DcSender :: timerExpired (long, long) {
   DEBUGNL ("DcSender::timerExpired()") ;
   state_ = TIMEDOUT ;
   if (port_ && port_->fd()>=0) 
      Dispatcher::instance().unlink (port_->fd()) ;
   if (sockno_ >= 0)
      Dispatcher::instance().unlink (sockno_) ;
   delete_port_() ;
   delete_socket_() ;
}

boolean DcSender :: listen_() {
   DEBUGNL ("DcSender::listen_()") ;

   if (state_ != NOTYETRUNNING) {
      DEBUGNL ("DcSender::listen_(): state_ != NOTYETRUNNING") ;
      return false ;
   }

   port_ = new rpcbuf ;

   if (! port_->listen (portno_)) {
      DEBUGNL ("DcSender::DcSender(): listen failed") ;
      state_ = NOPORT ;
      delete_port_() ;
      return false ;
   }
   else {
      DEBUGNL ("Sender::listen_(): link...") ;
      start_timer_() ;
      Dispatcher::instance().link (port_->fd(), Dispatcher::ReadMask, this) ;
      DEBUGNL ("Sender::listen_(): done") ;
   }

   return true ;
}

void DcSender :: delete_port_() {
   DEBUGNL ("DcSender::delete_port_()") ;
   if (! port_)
      return ;
   delete port_ ;
   port_ = nil ;
}
void DcSender :: delete_socket_() {
   if (sockno_ < 0)
      return ;
   ::close (sockno_) ;
   sockno_ = -1 ;
}

void DcSender :: start_timer_() {
   DEBUGNL ("DcSender::start_timer_()") ;
   if (timeout_ > 0) {
      DEBUGNL ("DcSender::start_timer_(): starting timer") ;
      Dispatcher::instance().startTimer (timeout_, 0, this) ;
      timer_set_ = true ;
   }
}
void DcSender :: stop_timer_() {
   DEBUGNL ("DcSender::stop_timer_()") ;
   if (timer_set_) {
      Dispatcher::instance().stopTimer (this) ;
      timer_set_ = false ;
   }
}
void DcSender :: reset_timer_() {
   stop_timer_() ;
   start_timer_() ;
}

// --------------------------------------------------------------------
DcFileSender :: DcFileSender (int port, 
                              int maxread, 
                              long timeout)
: DcSender (port, maxread, timeout),
  file_(nil),
  read_yet_(false),
  want_header_(false) {}

DcFileSender :: DcFileSender (const HgHeader& h, 
                              int port, 
                              int maxread, 
                              long timeout)
: DcSender (h, port, maxread, timeout),
  file_(nil),
  read_yet_(false),
  want_header_(true) {}

DcFileSender :: DcFileSender (const char* filename, 
                              int port, 
                              int maxread, 
                              long timeout)
: DcSender (port, maxread, timeout),
  file_(nil),
  read_yet_(false),
  want_header_(false) {
     init (filename) ;
}

DcFileSender :: DcFileSender (const HgHeader& h, 
                              const char* filename,
                              int port, 
                              int maxread, 
                              long timeout)
: DcSender (h, port, maxread, timeout),
  file_(nil),
  read_yet_(false),
  want_header_(true) {
     init (filename) ;
}

DcFileSender :: DcFileSender (ifstream* file, 
                              int port, 
                              int maxread, 
                              long timeout)
: DcSender (port, maxread, timeout),
  file_(nil),
  read_yet_(false),
  want_header_(false) {
     init (file) ;
}

DcFileSender :: DcFileSender (const HgHeader& h, 
                              ifstream* file, 
                              int port, 
                              int maxread, 
                              long timeout)
: DcSender (h, port, maxread, timeout),
  file_(nil),
  read_yet_(false),
  want_header_(true) {
     init (file) ;
}

DcFileSender :: ~DcFileSender() {
   if (delete_)
      delete file_ ;
}

int DcFileSender :: read_() {
   hgassert (file_, "DcFileSender::read_(): nothing to read from") ;
   if (!read_yet_  &&  !file_->good())
      // file_ is bad (is there a better way to check this ?)   ++++
      return -1 ;

   read_yet_ = true ;

   if (file_->eof() || file_->fail())
      return 0 ;

   file_->read (data_, max_read_) ;
   
   if (file_->good() || file_->eof() || file_->fail())
      return file_->gcount() ;

   DEBUGNL ("DcFileSender::read_(): bad file_") ;
   return -1 ;
}

boolean DcFileSender :: init (const char* filename) {
   hgassert (!file_, "DcFileSender::init (const char*): file_ already set") ;
   
   file_ = new ifstream (filename) ;
   delete_ = true ;

   if (! *file_) {
      set_state_(DcSender::NOOPEN) ;
      delete file_ ;
      file_ = nil ;
      return false ;
   }

   if (want_header_) {
      // set current header's size if not initialized
      if (header().size() == HgHeader::notinit) {
         HgHeader h (DcSender::header()) ;
         file_->seekg (0, ios::end) ;
         DcSender::set_header_(h.size (int (file_->tellg()))) ;
         file_->seekg (0) ;
      }
   }
   
   return DcSender::listen_() ;
}

boolean DcFileSender :: init (ifstream* f) {
   hgassert (!file_, "DcFileSender::init (ifstream*): file_ already set") ;
   hgassert (f, "DcFileSender::init (ifstream*): file not ok") ;

   file_ = f ;
   delete_ = false ;

   if (want_header_) {
      // set current header's size if not initialized
      if (header().size() == HgHeader::notinit) {
         HgHeader h (DcSender::header()) ;
         streampos pos = file_->tellg() ;
         file_->seekg (0, ios::end) ;
         DcSender::set_header_(h.size (int (file_->tellg() - pos))) ;
         file_->seekg (pos) ;
      }
   }
   
   return DcSender::listen_() ;
}


// --------------------------------------------------------------------
DcStringSender :: DcStringSender (const char* str, int length, 
                                  int port, 
                                  int maxread, 
                                  long timeout)
: DcSender (port, maxread, timeout),
  str_(str),
  length_(length<0? ::strlen (str): length),
  pos_(str) {
     listen_() ;
}

DcStringSender :: DcStringSender (const HgHeader& h, 
                                  const char* str, int length, 
                                  int port, 
                                  int maxread, 
                                  long timeout)
: DcSender (port, maxread, timeout),
  str_(str),
  pos_(str) {
     HgHeader mh (h) ;

     if (mh.size() == HgHeader::notinit) {
        if (length < 0) {
           // assume str be null terminated
           length_ = ::strlen (str) ;
        }
        else {
           length_ = length ;
        }
        mh.size (length_) ;
     }
     else {
        if (length < 0) {
           length_ = mh.size() ;
        }
        else {
           // ++++ fail state (NOOPEN?) instead ???
           hgassert (mh.size()==length, 
                     "DcStringSender::DcStringSender(): h.size() and length not equal") ;
           length_ = length ;
        }
     }

     set_header_(mh) ;
     listen_() ;
}

DcStringSender :: DcStringSender (const RString& rstr, 
                                  int port, 
                                  int maxread, 
                                  long timeout)
: DcSender (port, maxread, timeout),
  rstr_(rstr), // remember the RString (so that str_ survives until the end)
  str_(rstr.string()),
  length_(rstr.length()),
  pos_(rstr.string()) {
     listen_() ;
}

DcStringSender :: DcStringSender (const HgHeader& h, 
                                  const RString& rstr,
                                  int port, 
                                  int maxread, 
                                  long timeout)
: DcSender (port, maxread, timeout),
  rstr_(rstr), // remember the RString (so that str_ survives until the end)
  str_(rstr.string()),
  pos_(rstr.string()) {
     HgHeader mh (h) ;

     length_ = rstr.length() ;
     if (mh.size() == HgHeader::notinit) {
        mh.size (length_) ;
     }
     else {
        hgassert (mh.size()==rstr.length(), 
                  "DcStringSender::DcStringSender(const HgHeader&,const RString&): "
                  "sizes dont match") ;
     }

     set_header_(mh) ;
     listen_() ;
}

DcStringSender :: ~DcStringSender() {}

int DcStringSender :: read_() {
   const char* run ;
   if (length_ < 0) { // ++++ do I need this anymore ?? length_ is always set.
      for (run=pos_; run-pos_<max_read_ && *run++;) ;
      int nread = run - pos_ ;
      ::memcpy (data_, pos_, nread) ;
      pos_ = run ;
      return nread ;
   }
   else {
      for (run=pos_; run-pos_<max_read_ && run-str_<length_; run++) ;
      int nread = run - pos_ ;
      ::memcpy (data_, pos_, nread) ;
      pos_ = run ;
      return nread ;
   }
}

