//<copyright>
//
// Copyright (c) 1994,95
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
//
//</copyright>

//<file>
//
// Name:        imgserver.C
//
// Purpose:     server for texture images
//
// Created:     16 Mar 94   Gaisbauer Mansuet Juergen
//
// Changed:      3 Nov 95   Michael Pichler
//
//
//</file>



#include "imgserver.h"
#include "material.h"

#include <vrml/QvSFString.h>
#include <vrml/QvWWWInline.h>

#ifdef PMAX
#include <ctype.h>  /* problems with include order */
#endif

#ifdef SUN4
#ifndef __GNUC__
#include <CC/sysent.h>
#endif
#endif /* SUN4*/

#include <IV-look/kit.h>
#include <InterViews/style.h>
#include <Dispatch/dispatcher.h>
#include <OS/string.h>

#include <hyperg/hyperg/verbose.h>

#include <hyperg/viewer/vwstuff.h>
#include <hyperg/hyperg/object.h>
#include <hyperg/utils/environ.h>
#include <hyperg/hyperg/hgheader.h>

#include <hyperg/WWW/HTParse.h>

#include <errno.h>
#include <string.h>
#include <stdio.h>

#ifdef PMAX

#include <CC/sysent.h>
extern "C" {
     char* strerror(int en);
}

#define	R_OK	4	/* Test for "Read Permission */
#define	W_OK	2	/* Test for "Write Permission */
#define	X_OK	1	/* Test for "Execute" (Search) Permission */
#define	F_OK	0
#else

#include <unistd.h>

#endif /* PMAX */

#include <iomanip.h>
#include <strstream.h>


long ImageServer::refno_ = 1000000000L;



class ImageRequest
{
  public:
    ImageRequest (const RString& msg, Material* mat)
    : msg_ (msg)
    { mat_ = mat;
      sent_ = 0;
    }

    const RString& message ()  { return msg_; }
    Material* material ()  { return mat_; }

  public:
    int sent_;                          // request sent to database

  private:
    RString msg_;
    Material* mat_;
};


class InlineURLRequest
{
  public:
    InlineURLRequest (const RString& msg, QvWWWInline* node)
    : msg_ (msg)
    { node_ = node;
      sent_ = 0;
    }

    const RString& message ()  { return msg_; }
    QvWWWInline* WWWInlineNode ()  { return node_; }

  public:
    int sent_;                          // request sent to database

  private:
    RString msg_;
    QvWWWInline* node_;
};



// --- ImageReader ------------------------------------------------------------

char* ImageReader::rd_buf_ = new char [HgHeader::maxsize];

unsigned int ImageReader::img_reader_no_ = 0;


ImageReader::ImageReader (int fd, ImageServer* server)
{
  pipe_port_ = fd;
  server_ = server;
  header_state_ = request;

  char tempname [64];
  filename_ = server_->getPath ();
  sprintf (tempname, "/%d.%d.hs", (int) getpid (), img_reader_no_++);
  filename_ += tempname;

  DEBUGNL ("ImageReader on file " << filename_);

  ostr_.open (filename_);
}


ImageReader::~ImageReader ()
{
  if (pipe_port_ != -1)
  {
    // if the file is still open, then remove the file because it might
    // be truncated
    DEBUGNL ("~ImageReader: pipeport != -1");
    Dispatcher::instance ().unlink (pipe_port_);  // close connection
    ::close (pipe_port_);

    ostr_.close ();
  }

  DEBUG ("~ImageReader " << pipe_port_ << ": ");

  DEBUGNL ("removing file " << filename_);
  // cerr << "TEST: temporary file not beeing removed!!!" << endl;
  ::unlink (filename_);  // delete file

//xx     server_->removeReader(this);
}

int ImageReader::inputReady (int fd)
{
  int rv = ::read (fd, rd_buf_, HgHeader::maxsize);

  pipe_port_ = fd;
  DEBUG ("IR::inputReady [" << rv << "]");
  switch (rv)
  {
    case 0:  // EOF all read
      ::close(fd);
      pipe_port_ = -1;
      ostr_.close();
      DEBUGNL ("ImageReader::inputReady: end header state " << header_state_);
      Dispatcher::instance().unlink(fd);
      // image complete - process it
      server_->removeReader (this);  // will delete this
    return -1; // to remove the handler from the dispatcher

    case -1: // error
      cerr << "IR:inputReady : read error " << errno << endl;
#if defined(SUN4) && !defined(__GNUC__)
      cerr << sys_errlist[errno] << endl;
#else
      cerr << strerror(errno) << endl;
#endif
      Dispatcher::instance().unlink(fd);
      server_->removeReader (this);  // will delete this
    return -1; // to remove the handler from the dispatcher

    default:
      if (header_state_ == request)
      {
        HgHeader header;

        int used_len = header.read (rd_buf_, HgHeader::maxsize);

        if (used_len != -1)  // image with header
        {
          header_state_ = header_ok;
#if !defined(SUN4) && !defined(HPUX9) && !defined(SGI) && !defined(PMAX)
          ostrstream str1;
          str1 << "0x" << setfill('0');
          str1 << setw(sizeof(long) * 2);
          str1 << hex << header.refno() << ends;
          char* refno_str = str1.str();
#else
          char refno_str[20];
          sprintf(refno_str, "0x%0*x", sizeof(long) * 2, header.refno()); 
#endif
          DEBUGNL ("ImageReader::inputReady HEADER " << refno_str << " " <<  header_state_);

// should the request be removed immediately or here (to be able to abort it)?
//           ImgReq* req = server_->removeImgReq(header.refno());
//           DEBUGNL ("ImageReader::inputReady after removeImgReq " << req);
//           delete req;

#if !defined(SUN4) && !defined(HPUX9) && !defined(SGI) && !defined(PMAX)
          delete[] refno_str;
#endif
          ostr_.write (rd_buf_ + used_len, rv - used_len);
        }
        else  // no header
        {
          header_state_ = no_header;
          ostr_.write(rd_buf_, rv);
        }
      }
      else  // header read, continue reading data
      {
        ostr_.write(rd_buf_, rv);
      }
      return 0; // everything done
  }
} // inputReady


// --- ImageServer ------------------------------------------------------------

declareIOCallback(ImageServer)
implementIOCallback(ImageServer)


ImageServer::ImageServer (
  HgViewerManager* manager,
  ImageServerCallback* callback
) 
: RpcService (0)
{
  Style* style = WidgetKit::instance ()->style ();

  imgreader_ = 0;
  manager_ = manager;
  req_without_connect_ = 0;
  pendingimgrequest_ = 0;
  pendingurlrequest_ = 0;
  requesttextures_ = 0;

  String str;
  if (style->find_attribute ("tmpDir", str) && str.length ())
  { path_ = str.string ();  // RString.operator =
    // under UNIX it does not matter when path ends with '/' (path//file is ok.)
    // (otherwhise cut off a trailing '/' here)
  }
  else
    path_ = "/tmp";

  DEBUGNL ("scene viewer: temporary data stored in directory " << path_);

  callback_ = callback;
}


ImageServer::~ImageServer ()
{
  DEBUGNL ("~ImageServer");
  clearAllRequests ();
  DEBUGNL ("~ImageServer finished");
}


void ImageServer::clearAllRequests ()
{
  req_without_connect_ = 0;

  DEBUGNL ("ImageServer::clearAllRequests");

  delete pendingimgrequest_;
  delete pendingurlrequest_;
  pendingimgrequest_ = 0;
  pendingurlrequest_ = 0;

  for (ImageRequest* ireq = (ImageRequest*) imagerequests_.first ();  ireq;
       ireq = (ImageRequest*) imagerequests_.next ())
  { delete ireq;
  }
  imagerequests_.clear ();  // clear list of image requests

  for (InlineURLRequest* urlreq = (InlineURLRequest*) urlrequests_.first ();  urlreq;
       urlreq = (InlineURLRequest*) urlrequests_.next ())
  { delete urlreq;
  }
  urlrequests_.clear ();  // clear list of URL requests

  // only one reader class (just responsible for reading data onto temporary file)
  delete imgreader_;
  imgreader_ = 0;

  DEBUGNL ("ImageServer::clearAllRequests finished");
}


void ImageServer::cancelRequest (const char*)  // refno_str
{
  // as there is only one request active at any time, refno is not needed
  DEBUGNL ("ImageServer (scene viewer): cancel current request");

  // would need a callback to abstract functionality of ImageServer
  // feedback will be given in handleRequest (either next one or finished)

  if (pendingimgrequest_)
  {
    cerr << "Scene Viewer: error in texture image request -\n"
            "  texture name: " << pendingimgrequest_->material ()->texture () << endl;
    if (!pendingimgrequest_->sent_)
      req_without_connect_--;
    delete pendingimgrequest_;
    pendingimgrequest_ = 0;
    handleRequests (0);  // contine handling requests
  }
  else if (pendingurlrequest_)
  {
    cerr << "Scene Viewer: error on request of inline VRML -\n"
            "  URL: " << pendingurlrequest_->WWWInlineNode ()->name.value.getString () << endl;
    if (!pendingurlrequest_->sent_)
      req_without_connect_--;
    delete pendingurlrequest_;
    pendingurlrequest_ = 0;
    handleRequests (0);  // contine handling requests
  }

} // cancelRequest

     
// appendRequest for texture image

void ImageServer::appendRequest (
  const RString& doc_id,
  const Object& anchor_obj, const RString& dest,
  Material* mat
)
{
  DEBUGNL ("ImageServer::appendRequest: " << doc_id << " to " << dest);

//long refno = anchor_obj.ID ().id ();

  RString msg ("DocumentID=");
  msg += doc_id + "\n";
  char vb [16];
  sprintf (vb, "%d", RpcService::_port);
  msg += anchor_obj;
  msg += RString ("PipePort=") + RString (vb) + "\n"; 
  msg += RString ("PipeHost=") + Environ::hostName () + "\n";

//DEBUGNL("ImageServer::appendImgReq: " << (const char*) msg);
  ImageRequest* ireq = new ImageRequest (msg, mat);

  imagerequests_.put (ireq);
  req_without_connect_++;

  DEBUGNL ("ImageServer::appendRequest finished.");
} // appendRequest



// parseURL
// and build a Hyper-G object string with common fields
// for anchors and inlines

void ImageServer::parseURL (const char* url, const char* docurl, RString& msg)
{
  ObjectID id (refno_--);  // next unused refno

  msg =
    "DocumentID=0x00000000\n"
    "ObjectID=";
  msg += id.IDString () + "\n";
  msg +=
    "Type=Document\n"
    "DocumentType=Remote\n"
    "RemoteType=Remote\n";

  char* str;
  str = HTParse (url, docurl, PARSE_ACCESS);  // protocol (usually WWW)
  msg += "Protocol=" + RString (str) + "\n";
  free (str);

  str = HTParse (url, docurl, PARSE_HOST);  // host name (poss. host:port)
  char* colpos = strchr (str, ':');
  if (colpos)
  { *colpos = '\0';
    msg += "Host=" + RString (str) + "\n";
    msg += "Port=" + RString (colpos + 1) + "\n";
  }
  else
  { msg += "Host=" + RString (str) + "\n";
    msg += "Port=80\n";  // default: port 80
  }
  free (str);

  str = HTParse (url, docurl, PARSE_PATH);
  msg += "Path=";
  // path sould begin with '/'
  if (*str != '/')
    msg += "/";
  msg += RString (str) + "\n";
  free (str);

  str = HTParse (url, docurl, PARSE_ALL);
  msg += "Title=en:" + RString (str) + "\n";
  free (str);

} // parseURL


// append request for inline URL (VRML scene)
// these requests are handled immediately (unless another one is already pending)
// compare with HTMLDocBuffer::composeAnchorMsg of text viewer

void ImageServer::requestInlineURL (QvWWWInline* node, const char* url, const char* docurl)
{
  // TODO: write message into status line (cleared when finished in handleRequests)
  DEBUGNL ("requesting inline URL (VRML scene) " << url << " for document " << docurl);

  RString msg;
  parseURL (url, docurl, msg);  // common fields

  msg += "WWWType=Scene\n";  // inline data must be scenes

  // for inline WWW anchor additionally:
  msg += "LinkType=inline\n";

  // ensure the data being piped to this viewer
  char vb [16];
  sprintf (vb, "%d", RpcService::_port);

  msg += RString ("PipePort=") + RString (vb) + "\n"; 
  msg += RString ("PipeHost=") + Environ::hostName () + "\n";

  DEBUGNL("ImageServer::appendURLReq: " << (const char*) msg);
  InlineURLRequest* ureq = new InlineURLRequest (msg, node);

  urlrequests_.put (ureq);
  req_without_connect_++;

  handleRequests (0);  // begin handling inline request (if not already pending)
} // requestInlineURL


// handle requests
// handle next request of queue, inline URLs are prefered, texture images
// are only loaded when texturesalso flag is set to true

void ImageServer::handleRequests (int texturesalso)
{
  // handle first request (initiate transfer)
  // when it is finished it will call this procedure again to handle the next one

  requesttextures_ |= texturesalso;

  // handle requests one by one
  if (pendingimgrequest_ || pendingurlrequest_)
  {
    DEBUGNL ("ImageServer::handleRequests: another request currently pending");
    return;
  }

  InlineURLRequest* urlrequest = (InlineURLRequest*) urlrequests_.first ();
  if (urlrequest)
  {
    DEBUGNL ("ImageServer: handling VRML-URL request");
    if (callback_)
      callback_->requestURL (urlrequest->WWWInlineNode ());
    manager_->followLink (urlrequest->message (), nil);
    urlrequest->sent_ = 1;
    pendingurlrequest_ = urlrequest;
    urlrequests_.removeFirst ();
    DEBUGNL ("ImageServer: returned from followLink (VRML-URL)");

    return;
  }

  // delay texture request until first draw with textures
  if (!requesttextures_)
  { if (!urlrequest && callback_)
      callback_->requestFinished ();
    return;
  }

  ImageRequest* imgrequest = (ImageRequest*) imagerequests_.first ();
  if (imgrequest)
  {
    DEBUGNL ("ImageServer: handling texture request");
    if (callback_)
      callback_->requestImage (imgrequest->material ());
    manager_->followLink (imgrequest->message (), nil);  // last paramter is ignored
    imgrequest->sent_ = 1;
    pendingimgrequest_ = imgrequest;
    imagerequests_.removeFirst ();
    DEBUGNL ("ImageServer: returned from followLink (texture)");
  }
  else
  {
    DEBUGNL ("ImageServer: no more inline/texture requests to handle");
    if (callback_)
      callback_->requestFinished ();
  }
} // handleRequests


void ImageServer::createReader (int fd)
{
  DEBUGNL ("ImageServer::createReader " << fd);

  if (req_without_connect_)
  {
    DEBUGNL ("  request without connect: creating ImageReader");
    req_without_connect_--;

    ImageReader* ireader = new ImageReader (fd, this);
    imgreader_ = ireader;

    Dispatcher::instance ().link (fd, Dispatcher::ReadMask, ireader);
  }
  else
  { DEBUGNL ("  no open requests");
    ::close(fd);
  }

  DEBUGNL ("ImageServer::createReader finished");
}


void ImageServer::removeReader (ImageReader* reader)
{
  DEBUGNL ("ImageServer::removeReader (reader finished)");
  const char* filename = reader->filename_;

  int stop = 0;

  if (pendingurlrequest_)
  { stop = callback_ && callback_->readInlineURL (pendingurlrequest_->WWWInlineNode (), filename);
  }
  else if (pendingimgrequest_)
  { stop = callback_ && callback_->readImage (pendingimgrequest_->material (), filename);
  }
  else
  { DEBUGNL ("ImageServer: internal error: cannot relate inline data to any request");
  }

  delete reader;  // clean up reader (removes file)
  imgreader_ = 0;

  // clean up request
  delete pendingimgrequest_;
  pendingimgrequest_ = 0;
  delete pendingurlrequest_;
  pendingurlrequest_ = 0;

  if (stop)
  { DEBUGNL ("ImageServer: readImage requested stop");
    clearAllRequests ();
  }
  else
  { DEBUGNL ("ImageServer: continue after readImage");
    handleRequests (0);
  }
  // possibly need timer to react on stop button

} // removeReader
