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

//<file>
//
// Name:        hg3dvw.C
//
// Purpose:     implementation of class Hg3dViewer
//
// Created:      9 Jul 92   Keith Andrews, IICM (template)
//
// Changed:     16 Jan 96   Michael Pichler
//
// $Id: hg3dvw.C,v 1.9 1996/01/30 12:31:44 mpichler Exp $
//
// Description
// -----------
//
// Implementation of the abstract 3D scene viewer Hg3dViewer
// (independent of look-and-feel).
// Responsible for loading a 3D scene and managing the source anchors.
//
//
//</file>


#include "hg3dvw.h"

#include "scenewin.h"
#include "camera.h"
#include "geomobj.h"
#include "srcanch.h"
#include "material.h"
#include "stranslate.h"

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

#include <InterViews/window.h>

//#include <Dispatch/rpcstream.h>
#include <Dispatch/dispatcher.h>

#include <hyperg/utils/str.h>
#include <hyperg/hyperg/object.h>
#include <hyperg/hyperg/message.h>
#include <hyperg/hyperg/verbose.h>

#include <string.h>
#include <iostream.h>
#include <unistd.h>

#ifdef PMAX
// close prototype only contained in sysent.h, conflicts with other headers
extern "C" { int close (int); }
#endif


typedef long objid_type;




implementIOCallback(Hg3dViewer)


//
// class HyperGScene
//
// Manages the 3D scene and communication
// from the viewer to the viewer manager:
// * activation of links
// * definition of source and destination anchor
//


class HyperGScene: public SceneWindow
{
  public:
    HyperGScene (Session*, HgViewerManager*, Hg3dViewer*, const char* wintitle);

    // Scene3D
    void clear ();
    void deleteSourceAnchor (const SourceAnchor*);
    int followLink (const GeometricObject*, const SourceAnchor*);
    int followLink (const QvWWWAnchor*);
    void setSourceAnchor ();
    void setDestinationAnchor ();
    void setDefDestAnchor ();

    void requestInlineURL (QvWWWInline* node, const char* url, const char* docurl)
    { hg3dviewer_->requestInlineURL (node, url, docurl); }

    const char* mostRecentURL () const
    { return hg3dviewer_->docURL ();
    }

    void requestTextures ()  { hg3dviewer_->requestTextures (); }
    // the HyperGScene does not load textures demanded in the file,
    // but instead loads textures provided via "texture links"

    // Scene3D (see also viewer/vwstuff.h)
    void back ();
    void forward ();
    void history ();
    void hold ();
    void docinfo (int);
    void anchorInfo (const SourceAnchor*);

    // SceneWindow
    void beginFeedback ();
    void endFeedback ();
    void endFeedback (long, long);

  private:
    HgViewerManager* manager_;
    Hg3dViewer* hg3dviewer_;
};


HyperGScene::HyperGScene (Session* session, HgViewerManager* manager, Hg3dViewer* hg3dviewer, const char* wintitle)
: SceneWindow (session, wintitle)
{
  manager_ = manager;
  hg3dviewer_ = hg3dviewer;
}


void HyperGScene::clear ()
{
  DEBUGNL ("HyperGScene::clear: clearing pending requests and scene data");
  hg3dviewer_->clearAllRequests ();
  SceneWindow::clear ();
}


void HyperGScene::deleteSourceAnchor (const SourceAnchor* anchor)
{
  if (!anchor)
    return;

  long id = anchor->id ();
  ObjectID ID (id);

  RString link = "ObjectID=" + ID.IDString () + "\n"
               + "DocumentID=" + hg3dviewer_->objIdStr () + "\n";
  DEBUGNL ("hg3d: delete link <" << link << ">.");

  manager_->deleteLink (link, hg3dviewer_);
}


int HyperGScene::followLink (const GeometricObject*, const SourceAnchor* anchor)
{
  if (!anchor)  // assertion
    return 0;

  long id = anchor->id ();

  ObjectID ID (id);

  RString link ("ObjectID=" + ID.IDString ());
  DEBUGNL ("scene viewer: follow link to <" << link << ">.");
  manager_->followLink (link, hg3dviewer_);

  return 0;  // I am not responsible for redraw
}


int HyperGScene::followLink (const QvWWWAnchor* anchor)
{
  // will have to differ here between anchors with ID (Hyper-G) and URL (WWW)

  if (!anchor)  // assertion
    return 0;

  RString absurl;
  anchorURL (anchor, absurl);  // add point map if necessary

  RString msg;
  hg3dviewer_->parseURL (absurl, anchor->parentURL_.getString (), msg);

  msg += "WWWType=text\n";  // text viewer will redirect the document appropriately

  DEBUGNL ("scene viewer: follow link to <" << msg << ">.");
  manager_->followLink (msg, hg3dviewer_);

  return 0;  // I am not responsible for redraw
}


void HyperGScene::beginFeedback ()
{
  SceneWindow::beginFeedback ();
  Dispatcher::instance ().startTimer (0, (long int) feedbacktime (), feedbackhandler ());
  // no need for stopTimer
}


void HyperGScene::endFeedback ()
{ // end of feedback via IO-callback
}

void HyperGScene::endFeedback (long, long)
{
  SceneWindow::endFeedback ();
}


void HyperGScene::setSourceAnchor ()
{
  // specify the source anchor:
  //   ObjectId=<ID of current scene>\n
  //   Position=Object <ObjectNumber>[/<GroupName>]\n
  // could also use <ObjectName> instead of "Object <ObjectNumber>"

  const GeometricObject* selobj = selectedObj ();
  const char* selgroup = selectedGroup ();

  if (selobj)
  {
    char onum [16];
    sprintf (onum, "%d", selobj->getobj_num ());

    RString srcanch (hg3dviewer_->docIdStr () + "Position=Object " + onum);
    if (selgroup)
      srcanch = srcanch + RString ("/") + selgroup;
    srcanch += "\n";

    DEBUGNL ("hg3d: defining source anchor <" << srcanch << ">.");
    manager_->defineSourceAnchor (srcanch, hg3dviewer_);
  }
  else
  {
    DEBUGNL ("hg3d: cannot define a source anchor without selection");
  }
} // setSourceAnchor


void HyperGScene::setDestinationAnchor ()
{
  // specify the destination anchor:
  // "ObjectId=<ID of current scene>\nPosition=<destination position>\n"
  Camera* cam = getCamera ();

  if (cam)
  {
    point3D pos, look;
    cam->getposition (pos);
    cam->getlookat (look);
    char ps [256];
    sprintf (ps, "(%f, %f, %f) (%f, %f, %f)\n", pos.x, pos.y, pos.z, look.x, look.y, look.z);

    RString dstanch (hg3dviewer_->docIdStr () + "Position=" + ps);
    DEBUGNL ("hg3d: defining destination anchor <" << dstanch << ">.");
    manager_->defineDestAnchor (dstanch, hg3dviewer_);
  }
  // cannot define a destination anchor without camera
}


void HyperGScene::setDefDestAnchor ()
{
  // specify the default destination anchor:
  // "ObjectId=<ID of current scene>\n"
  RString dstanch (hg3dviewer_->docIdStr ());
  DEBUGNL ("hg3d: defining default destination anchor <" << dstanch << ">.");
  manager_->defineDestAnchor (dstanch, hg3dviewer_);
}


void HyperGScene::back ()
{
  manager_->back (hg3dviewer_);
}

void HyperGScene::forward ()
{
  manager_->forward (hg3dviewer_);
}

void HyperGScene::history ()
{
  manager_->history (hg3dviewer_);
}

void HyperGScene::hold ()
{
  DEBUGNL ("scene: hold document '" << hg3dviewer_->docIdStr () << "'.");
  manager_->hold (hg3dviewer_->docIdStr (), hg3dviewer_);
}


void HyperGScene::docinfo (int item)
{
  const RString& docobj = hg3dviewer_->docObj ();

  switch (item)
  {
    case info_references:
      manager_->showReferences (docobj, hg3dviewer_);
    break;
    case info_annotations:
      manager_->showAnnotations (docobj, hg3dviewer_);
    break;
    case info_parents:
      manager_->showParents (docobj, hg3dviewer_);
    break;
    case info_attributes:
      manager_->showAttributes (docobj, hg3dviewer_);
    break;
    case info_textures:
      //manager_->showTextures (docobj, hg3dviewer_);
    break;
    default:
      cerr << "HyperGScene::docinfo: index " << item << " out of range (internal error)." << endl;
  }
}


void HyperGScene::anchorInfo (const SourceAnchor* anchor)
{
  if (!anchor)
    return;

  manager_->showAttributes (anchor->object (), hg3dviewer_);
}



//
// class Hg3dViewer
//
// Used to display 3d documents, stored in sdf format.
//


Hg3dViewer::Hg3dViewer (HgViewerManager* manager, Session* session) 
: HgViewer (manager),
  RpcService (0),
  read_callback_ (this, &Hg3dViewer::readInput, nil, nil),
  imgserver_ (manager, this)
{
  manager_ = manager;
  terminated_ = 0;
  pipe_port_ = -1;
  to_close_ = -1;

  // open communication
  if (!_service)  // RpcScervice
  {
    HgMessage::fatalError ("could not open port for piping document.", 1);  // exit
  }

  scene_ = new HyperGScene (session, manager, this, STranslate::str (STranslate::HARMONY3DVIEWER));

  Window* appwin = scene_->appwin ();
  appwin->map ();  // map application window
}


Hg3dViewer::~Hg3dViewer ()
{
  if (!terminated_)
    terminate ();
}


int Hg3dViewer::port () const           // tell document port (to viewer manager)
{
  return RpcService::_port;
  // RpcService::_port is the port for initiating the connection
  // pipe_port_ is the port for the data
}



void Hg3dViewer::load (                 // load document
  const char* doc,                      //   document
  const char* anchors,                  //   source anchors
  char*                                 //   feedback for viewer manager (unused)
)
{
  DEBUGNL ("hg3d: load document <" << doc << ">.");

  if (anchors)
  { DEBUGNL ("  3D source anchors <" << anchors << ">.");
  }
  else
  { DEBUGNL ("  without 3D source anchors.");
  }

  if (!doc || !*doc)
  { HgMessage::message ("note: no document specified for load.");
    return;
  }

  // maintain document id string (needed for terminate and anchor specification)
  Object dobj = Object (doc);
  docobj_ = dobj;
  objidstr_ = dobj.ID ().IDString ();  // id string of current object (document)
  docidstr_ = "ObjectID=" + objidstr_ + "\n";

  // URL: prot://host:port<path>, path has a leading '/'
  // (only used for non-Hyper-G objects)
  RString prot = dobj.protocol ();
  if (prot.length ())
    docurl_ = dobj.protocol () + ":/""/" + dobj.host () + ":" + dobj.port () + dobj.path ();
  else
    docurl_ = "";

  DEBUGNL ("hg3d: loading scene with object ID <" << docidstr_ << ">.");

  updateTitle ();

  // if "Function=reload" the current document should be reused, readInput
  // will not be called, and we are in same situation as when reading completed

  DEBUGNL ("hg3d: pipe port for load: " << pipe_port_);

  srcanchstr_ = anchors;  // store the anchors

  if (docobj_.field ("Function=") == "reload")
  {
    DEBUGNL ("hg3d: reloading for source anchor redefinition");

    readingCompleted ();  // already have document
  }

} // load



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

  // close old port - not here, because data may still arrive
  if (pipe_port_ != -1)
    to_close_ = pipe_port_;
  else
    to_close_ = -1;

  // open new port
  pipe_port_ = fd;
  Dispatcher::instance ().link (pipe_port_, Dispatcher::ReadMask, &read_callback_);

} // createReader



void Hg3dViewer::storeanchor (Object& anobj)     // store an anchor object
{
  // extract fields from anchor object

  ObjectID ID;
  
  if (!anobj.ID (ID))
  { HgMessage::message ("warning: source anchor without object ID.");
    return;
  }
  objid_type objid = ID.id ();  // Object ID

  // State currently unused ("seen" will be evaluated in future)

  RString posRstr;  // anobj.field ("Position=");

  if (!anobj.position (posRstr))
  { HgMessage::message ("warning: source anchor without field 'Position='.");
    return;
  }

  const char* posstr = posRstr;

  // valid position specifications, where NN is an object number (decimal):
  //
  // a) "true" source anchors
  //   Object NN
  //   Object NN/groupname
  //   objectname
  //   objectname/groupname
  //
  // b) texture anchors (LinkType "texture")
  //   materialname
  //
  // c) anchors to be ignored (used for annotations)
  //   Position=invisible

  if (!strcmp (posstr, "invisible"))  // 19950523
  {
    DEBUGNL ("hg3d: invisible source anchor (" << posstr << ") encountered (ignored).");
    return;
  }

  if (anobj.field ("LinkType=") == "texture")  // texture anchor
  {
    RString dest = anobj.field ("Dest=");
    if (!dest.length ())
    { HgMessage::message ("warning: texture anchor without field 'Dest='.");
      return;
    }
    DEBUGNL ("hg3d: texture anchor to destination " << dest);

    objid_type destid = ObjectID (dest).id ();

    Material* mat = scene_->findMaterial (posstr);

    if (mat)
    { DEBUGNL ("hg3d: texture anchor for " << mat->name () << " with destination id " << destid);
      imgserver_.appendRequest (objIdStr (), anobj, dest, mat);
    }
    else
      HgMessage::message ("warning: texture anchor with invalid position (material name)");

    return;
  }

  // else ordinary source anchor

  const char* posslash = strchr (posstr, '/');
  GeometricObject* obj;

  int objnum;  // number of 3D object (*not* ID, see SDF file)
  if (sscanf (posstr, "Object %d", &objnum) == 1)  // object number given
  { obj = scene_->findObject (objnum);
  }
  else  // object name expected
  {
    if (posslash)
    { RString objname (posstr, posslash - posstr);
      obj = scene_->findObject (objname);
    }
    else
      obj = scene_->findObject (posstr);
  }

  if (!obj)
  { DEBUGNL ("hg3d: no object corresponds to anchor position '" << posstr << "' not found.");
    HgMessage::message ("warning: wrong object name in source anchor position or invalid format.");
    return;
  }

  // else object number

  if (posslash)  // group anchor
  {
    posslash++;  // group name
    DEBUGNL ("hg3d: group " << posslash << " of object " << obj->name () << " gets id " << objid);
    scene_->defineAnchor (obj, objid, anobj, posslash);
  }
  else  // ordinary object anchor
  {
    DEBUGNL ("hg3d: object " << obj->name () << " gets id " << objid);
    scene_->defineAnchor (obj, objid, anobj, 0);  // clicking objnum activates anchor objid
  }

} // storeanchor


// getPos: tell current position
// it will used on "back" in history as destination anchor
// with a field "Function=NoMark" to turn off highlighting of destination anchor

void Hg3dViewer::getPos (RString& position)
{
  Camera* cam = scene_->getCamera ();

  if (cam)
  {
    point3D pos, look;
    cam->getposition (pos);
    cam->getlookat (look);
    char ps [256];
    sprintf (ps, "(%f, %f, %f) (%f, %f, %f)\n", pos.x, pos.y, pos.z, look.x, look.y, look.z);

    position = "Position=";
    position += ps;
    DEBUGNL ("hg3d: reporting current position <" << position << ">.");
  }
} // getPos


int Hg3dViewer::readImage (Material* mat, const char* filename)
{
  scene_->loadTextureFile (mat, filename);
  return 0;  // have to return 1 when reading should be stopped
}


int Hg3dViewer::readInlineURL (QvWWWInline* node, const char* filename)
{
  // TODO: uncompress inline data!
  if (scene_->readInlineVRML (node, filename))
    scene_->redraw ();

  return 0;  // 0 to continue, 1 to stop
}


void Hg3dViewer::requestImage (const Material* mat)
{
  char buf [256];
  sprintf (buf, "%s %.200s", STranslate::str (STranslate::ProgressREQUESTTEXTURE), mat->name ());
  scene_->statusMessage (buf);
}


void Hg3dViewer::requestURL (const QvWWWInline* node)
{
  char buf [256];
  sprintf (buf, "%s %.200s", STranslate::str (STranslate::ProgressREQUESTINLINEVRML), node->name.value.getString ());
  scene_->statusMessage (buf);
}


void Hg3dViewer::requestFinished ()
{
  scene_->showTitle ();  // switch back to title
}


void Hg3dViewer::cancelRequest (const char* refno_str)
{
  DEBUGNL ("Hg3dViewer::cancelRequest (" << refno_str << ")");
  imgserver_.cancelRequest (refno_str);
}


void Hg3dViewer::terminate ()           // terminate viewer
{
  if (terminated_)
  { HgMessage::message ("possibly strange: terminate was called twice.");
    return;
  }

  DEBUGNL ("hg3d: terminating.");

  if (pipe_port_ != -1)
  { ::close (pipe_port_);
    pipe_port_ = -1;
  }
  if (to_close_ != -1)
  { ::close (to_close_);
    to_close_ = -1;
  }

  RpcService::stopListening ();

  terminated_ = 1;
  delete scene_;
  scene_ = 0;

  manager_->viewerTerminated (docidstr_, this);
  DEBUGNL ("hg3d: termination reported to viewer manager.");
}



// storeSourceAnchors
// stores source anchors, possible after load () and readInput () arrived

void Hg3dViewer::storeSourceAnchors ()
{
  DEBUGNL ("Hg3dViewer::storeSourceAnchors");

  // requests of imageserver already cleared in HyperGScene::clear

  // clear "default" anchors of scene (possibly contained in SDF file)
  scene_->clearAnchors ();

  const char* anchors = srcanchstr_;

  if (anchors && *anchors)  // store new source anchors
  {
    char* anchor_str = new char [strlen (anchors) + 1];
    strcpy (anchor_str, anchors);  // working copy for parsing

    char* aptr;  char* eptr;

    // all anchors are terminated with empty lines ("\n\n")

    for (aptr = anchor_str;  (eptr = strstr (aptr, "\n\n")) != nil;  aptr = eptr+1)
    {
      *++eptr = '\0';  // eptr discards second \n
      // aptr now contains an anchor definition

      Object anobj (aptr);  // current anchor
      storeanchor (anobj);

    } // for all anchors

    delete anchor_str;

  } // if anchors

  GeometricObject* selobj = scene_->selectedObj ();
  if (selobj)
  { // anchors on selected object possibly (likely) changed on reload
    selobj->checkAnchorSelection ();
    scene_->selectionChanged ();
  }

} // storeSourceAnchors



// readInput
// read data
// currently the whole sdf-file is read at once --
// later it will be possible to read only pieces at once
// TODO: write input piece by piece onto temporary file, progress feedback

int Hg3dViewer::readInput (int fd)
{
//     Dispatcher::instance ().unlink (pipe_port_);
//     ::close (pipe_port_);
  if (fd == to_close_)  // fd to close
  { ::close (to_close_);
    to_close_ = -1;
    return -1;  // to remove the handler from the dispatcher
  }

  DEBUGNL ("hg3d: readInput from file descriptor " << fd);

  FILE* file = fdopen (fd, "r");

  if (!file)
  {
    HgMessage::fatalError ("could not read from document port.", 3);  // exit
  }

  int retval = scene_->readSceneFILE (file);  // clear old data and read new scene
  fclose (file);  // 19950831

  if (retval)
  { // if readscene detects an error it should be reported to the viewer manager here
    HgMessage::error ("error in scene description file.");
  }
  else
  { DEBUGNL ("hg3d: read input from fd " << fd << " - OK.");
  }

  // Dispatcher::instance ().unlink (fd);  // should not be necessary
  readingCompleted ();
  pipe_port_ = -1;
  return -1;  // to remove the handler from the dispatcher

} // readInput
