//<copyright>
//
// Copyright (c) 1995,96,97
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
//
// This file is part of VRweb.
//
// VRweb is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.
//
// VRweb 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with VRweb; see the file LICENCE. If not, write to the
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.
//
// Note that the GNU General Public License does not permit incorporating
// the Software into proprietary or commercial programs. Such usage
// requires a separate license from IICM.
//
//</copyright>

//<file>
//
// Name:        urlserver.C
//
// Purpose:     server for fetching texture images and inline scenes from the Web
//
// Created:     29 Nov 1995   Michael Pichler
//
// Changed:     23 May 1997   Michael Pichler
//
// $Id: urlserver.C,v 1.10 1997/05/26 17:32:34 mpichler Exp $
//
//</file>


#include "urlserver.h"
#include "httpreader.h"

#include "scenewin.h"
#include "stranslate.h"
#include "gecontext.h"

#include <hyperg/widgets/progrind.h>
#include <hyperg/widgets/cursors.h>

#include <hyperg/WWW/HTParse.h>
#include <hyperg/utils/str.h>
#include <hyperg/hyperg/message.h>

#include <InterViews/action.h>

#undef DEBUG
/* also defined by HTParse.h */
#include <hyperg/utils/verbose.h>

#include <iostream>
#include <fstream>
#include <unistd.h>  /* unlink */


declareActionCallback (URLServer)
implementActionCallback (URLServer)



int URLServer::senduseragent_ = 1;  // send user agent on HTTP requests


/***** URLRequest *****/


class URLRequest
{
  public:
    URLRequest (
      const char* url, const char* docurl,
      QvWWWInline* in_line, QvTexture2* texture
    )
    {
      url_ = url;
      docurl_ = docurl;
      inline_ = in_line;
      texture_ = texture;
    }

    RString url_;
    RString docurl_;
    QvWWWInline* inline_;
    QvTexture2* texture_;
};  // URLReqeust



/***** URLReader *****/


class URLReader: public HTTPReader
{
  public:
    URLReader (URLRequest* req, URLServer* server,  // both non-nil
      const char* useragent);  // may be null
    ~URLReader ();

    // HTTPReader
    void connect ();
    void success ();
    void failure ();
    void headerComplete ();
    void documentData ();

  private:
    URLRequest* request_;
    URLServer* server_;
    std::ofstream ostr_;
    RString filename_;
};  // URLReader


// URLReader

URLReader::URLReader (URLRequest* req, URLServer* server, const char* useragent)
: HTTPReader (req->url_, req->docurl_, useragent)
{
  DEBUGNL ("URLReader[" << (void*) this << "] created");
  request_ = req;  // URLRequest now managed by this URLReader
  server_ = server;
  // temp file ostr_ created when header complete
  // creator responsible for error handling

  if (!error ())
    connect ();  // blocking for time of connecting

} // URLReader


URLReader::~URLReader ()
{
  DEBUGNL ("~URLReader[" << (void*) this << "]: this request finished. going on to next one.");
  delete request_;

  if (filename_.length ())  // delete temporary file
  { DEBUGNL ("removing temp file " << filename_);
    ::unlink (filename_);
  }

  server_->readerFinished ();

  DEBUGNL ("~URLReader finished.");
}


void URLReader::connect ()
{
  SceneWindow* scene = server_->scene ();
  ProgressIndicator* progrind = scene->progrind ();
  GEContext* gecon = scene->gecontext ();
  RString msg;

  if (progrind)
  { msg = STranslate::str (STranslate::ProgressCONTACTINGHOST);
    msg += host ();
    progrind->workingMessage (msg.string ());
    progrind->repairWindow ();  // show connect message
  }
  gecon->pushCursor (HgCursors::instance ()->hourglass ());

  HTTPReader::connect ();

  if (progrind)
  { msg = STranslate::str (STranslate::ProgressDATATRANSFER);
    msg += fullURL ();
    progrind->workingMessage (msg.string ());
  }
  gecon->popCursor ();
} // connect


void URLReader::success ()
{
  ostr_.flush ();  // write data to disk
  DEBUGNL ("URLReader[" << (void*) this << "]::success");

// TODO: register filename_ in cache and do not delete it in destructor

  DEBUGNL ("data arrived on file " << filename_);

  if (request_->inline_)  // read VRML inline data (also cares for decompression)
  {
    server_->scene ()->currentURL (fullURL ());
    // std::cerr << "new parent URL set to " << fullURL () << std::endl;

    if (server_->scene ()->readInlineVRMLFile (request_->inline_, filename_.string ()))
      server_->scene ()->redraw ();
    else
      std::cerr << "error on reading data from request " << request_->url_ << std::endl;
  }

  if (request_->texture_)  // read texture image
  {
    if (server_->scene ()->readTextureFile (request_->texture_, filename_.string ()))
      server_->scene ()->redraw ();
    else
      std::cerr << "error on reading data from request " << request_->url_ << std::endl;
  }

  HTTPReader::success ();  // cleanup (delete this)
}


void URLReader::failure ()
{
  DEBUGNL ("URLReader[" << (void*) this << "]::failure");
  std::cerr << "error occured on fetching URL " << request_->url_ << std::endl;
  // pop up error dialog?
  HTTPReader::failure ();  // cleanup (delete this)
}


void URLReader::headerComplete ()
{
  DEBUGNL ("URLReader[" << (void*) this << "]::headerComplete. error code: " << error ());

  if (error ())  // no longer called on redirect
    return;

  server_->scene ()->makeTempFileName (filename_, "vrinl");
  if (!filename_.length ())
  { HgMessage::error ("could not create temporary file name");
    failure ();
  }

  DEBUGNL ("temporary file for output: " << filename_);
  ostr_.open (filename_.string ());  // open tempfile for output

  if (ostr_)
    setOS (&ostr_);
  else
  { RString msg = "cannot create temporary file " + filename_ + " for writing";
    HgMessage::error (msg.string ());
    filename_ = "";  // not created, not to be deleted
    failure ();
  }

  ProgressIndicator* progrind = server_->scene ()->progrind ();
  unsigned long total = bytesTotal ();

  if (total > 0 && progrind)
  { // tell total expected size in progress indicator
    char buf [64];
    sprintf (buf, " [%lu %s]", total, "bytes");

    RString msg = STranslate::str (STranslate::ProgressDATATRANSFER);
    msg += fullURL ();  // see connect
    msg += buf;

    progrind->workingMessage (msg.string ());
  }

} // headerComplete


void URLReader::documentData ()
{
  ProgressIndicator* progrind = server_->scene ()->progrind ();
  unsigned long data = bytesRead ();
  unsigned long total = bytesTotal ();

  if (total > 0 && progrind)
    progrind->setProgress ((float) data / total);  // range check done by progress indicator
  // TODO: should have some pseudo-progress when total size not known

  DEBUGNL (data << " bytes of total " << total << " read.");
} // documentData



/***** URLServer *****/


URLServer::URLServer (SceneWindow* scene)
{
  scene_ = scene;
  reqtextures_ = 0;
  activereader_ = 0;
}


URLServer::~URLServer ()
{
  DEBUGNL ("~URLServer");

  clearAllRequests ();

  DEBUGNL ("~URLServer finished");
}


void URLServer::clearAllRequests ()
{
  DEBUGNL ("URLServer::clearAllRequests");
  // called when scene is cleared and on stop button

  URLRequest* req;
  for (req = (URLRequest*) inlinerequests_.first ();  req;
       req = (URLRequest*) inlinerequests_.next ())
  { delete req;
  }
  inlinerequests_.clear ();  // clear list of VRML inline requests

  for (req = (URLRequest*) texturerequests_.first ();  req;
       req = (URLRequest*) texturerequests_.next ())
  { delete req;
  }
  texturerequests_.clear ();  // clear list of VRML inline requests

  // on stop: all further requests have been removed above
  delete activereader_;
  activereader_ = 0;

  reqtextures_ = 0;

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


// appendRequest
// put a request for a VRML inline;
// handleRequests must be called when requests should be carried out

void URLServer::appendRequest (
  QvWWWInline* nodeI,
  QvTexture2* nodeT,
  const char* url,
  const char* docurl
)
{
  DEBUGNL ("appending request for VRML inline/texture");

  URLRequest* req = new URLRequest (url, docurl, nodeI, nodeT);
  // texture requests may be carried out later
  if (nodeT)
    texturerequests_.put (req);
  else
    inlinerequests_.put (req);

} // appendInlineRequest



// handleRequests
// handles the next request unless another one is pending
// texturesalso determines whether texture requests are handled too
// when a request is finished, the procedure will be called again
// until all requests are handled

void URLServer::handleRequests (int texturesalso)
{
  DEBUGNL ("URLServer::handleRequests");
  ProgressIndicator* progrind = scene_->progrind ();

  reqtextures_ |= texturesalso;  // is reset in clearAllRequests

  // only one at a time (may handle multiple connections)
  if (activereader_)
  { DEBUGNL ("URLServer::handleRequests: active reader handling another request");
    return;
  }

  URLRequest* req;
  if (progrind)
  {
    progrind->stopAction (new ActionCallback(URLServer) (this, &URLServer::clearAllRequests));
    progrind->workingMessage ("fetching inlines/textures");  // overwritten before connect
    progrind->workingState ();
  }

  while (req = (URLRequest*) inlinerequests_.first ())
  { // handle next inline request (on succeess exit loop)
    DEBUGNL ("URLServer: handling VRML inline request; creating URLReader");
    inlinerequests_.removeFirst ();  // deleted by reader

    if (handleRequest (req))
      return;
  }

  if (!reqtextures_)
  { DEBUGNL ("URLServer::handleRequests: no more inlines; textures not required");
    if (progrind)
    { progrind->readyState ();
      progrind->stopAction (nil);
    }
    return;
  }

  while (req = (URLRequest*) texturerequests_.first ())
  { // handle next texture request (if one succeeds exit loop)
    DEBUGNL ("URLServer: handling texture request; creating URLReader");
    texturerequests_.removeFirst ();  // deleted by reader

    if (handleRequest (req))
      return;
  }

  if (progrind)
  { progrind->readyState ();
    progrind->stopAction (nil);
  }
  DEBUGNL ("URLServer::handleRequests: nothing more to handle");

} // handleRequests


// handleRequest
// create reader for this request
// returns success flag

int URLServer::handleRequest (URLRequest* req)
{
  const char* useragent = 0;
  if (senduseragent_)
    useragent = STranslate::str (STranslate::AboutVERSIONhttp);
  URLReader* reader = new URLReader (req, this, useragent);

  activereader_ = reader;  // now active

  if (reader->error ())  // invalid URL or no connection
  {
    DEBUGNL ("URLServer: error encountered on creating URLReader; will try next one");

    // log message into error dialog
    RString errmsg = "error on connecting to: ";
    errmsg += reader->fullURL ();
    scene_->errorMessage (errmsg);

    delete reader;  // forget this request, try next one
    activereader_ = 0;
    return 0;  // error
  }

  DEBUGNL ("URLServer: URLReader successfully created; exiting handleRequests");
  return 1;  // handleRequests finished for now
  // progress indicator: working, stop possible

} // handleRequest


// current reader finished its work
// handle next request

void URLServer::readerFinished ()
{
  // currently only one pending request at each time
  // called by ~URLReader; request already deleted by reader

  activereader_ = 0;

  handleRequests (0);

} // readerFinished
