// Visualization interface to Graphviz dotty(1) -*- c++ -*-

#ifdef __CYGWIN__
# undef __STRICT_ANSI__
#endif // __CYGWIN__

#ifdef __GNUC__
# pragma implementation
# ifdef __sgi
#  define _POSIX_C_SOURCE 199309L
# endif // __sgi
#endif // __GNUC__
#include "Dotty.h"

#ifdef __alpha
# include </usr/include/stdio.h> // fdopen(3)
#endif // __alpha

#include <stdlib.h> // exit(2)
#include <string.h>
#include <unistd.h>
#include <set>

#include "Graph.h"
#include "ComponentGraph.h"
#include "GraphReporter.h"
#include "Printer.h"
#include "GlobalMarking.h"
#include "Net.h"
#include "Place.h"
#include "Transition.h"
#include "Arc.h"
#include "Valuation.h"
#include "VariableDefinition.h"
#include "Marking.h"

/** @file Dotty.C
 * Visualization interface to Graphviz dotty(1)
 */

/* Copyright © 2001-2003 Marko Mäkelä (msmakela@tcs.hut.fi).

   This file is part of MARIA, a reachability analyzer and model checker
   for high-level Petri nets.

   MARIA 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.

   MARIA 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.

   The GNU General Public License is often shipped with GNU software, and
   is generally kept in a file called COPYING or LICENSE.  If you do not
   have a copy of the license, write to the Free Software Foundation,
   59 Temple Place, Suite 330, Boston, MA 02111 USA. */

#ifndef unix
# if defined __unix||defined __unix__||defined __NetBSD__||defined __APPLE__
#  define unix
# endif
#endif

#ifndef __WIN32
/** Visualization process identifier */
volatile pid_t vispid;
/** Visualization command stream */
FILE* volatile visf;
/** Visualization command file descriptor */
volatile int visfd;
#endif // unix
/** Visualization feedback stream */
static FILE* graphf;

class Dotty Dotty::theDotty;

Dotty::Dotty () :
  myOut (0)
{
}

Dotty::~Dotty ()
{
#ifdef __WIN32
  if (graphf) fclose (graphf), graphf = 0;
#else // __WIN32
  stopVisual ();
  clearVisual ();
#endif // __WIN32
}

#ifndef __WIN32
static const char* visualprg = 0;

void
Dotty::useVisual (const char* name)
{
  visualprg = name;
}
#endif // !__WIN32

class Dotty*
Dotty::startVisual ()
{
#ifdef __WIN32
  if (graphf)
    return &theDotty;
  theDotty.clearVisual ();
  if (!(graphf = fopen ("maria-vis.out", "w"))) {
    perror ("maria-vis.out");
    return 0;
  }
#else // __WIN32
  if (visfd)
    return &theDotty;
  theDotty.clearVisual ();
  assert (!visf && !graphf && !vispid && !theDotty.myOut);
  if (!visualprg) {
    fputs ("no visualization command\n", stderr);
    return 0;
  }
  /** pipes for the graph output and the command input */
  int pipe_graph[2], pipe_command[2];
  if (pipe (pipe_graph)) {
    perror ("pipe");
    return 0;
  }
  if (pipe (pipe_command)) {
    perror ("pipe");
    close (pipe_graph[0]), close (pipe_graph[1]);
    return 0;
  }
  pid_t pid = fork ();
  if (!pid) {
    /* child process */
    dup2 (pipe_graph[0], STDIN_FILENO);
    dup2 (pipe_command[1], STDOUT_FILENO);
    close (pipe_graph[0]), close (pipe_graph[1]);
    close (pipe_command[0]), close (pipe_command[1]);
    setsid ();
    execlp (visualprg, visualprg, 0);
    perror (visualprg);
    exit (0);
  }
  else if (pid < 0) {
    perror ("fork");
    close (pipe_graph[0]), close (pipe_graph[1]);
    close (pipe_command[0]), close (pipe_command[1]);
    return 0;
  }
  close (pipe_graph[0]), close (pipe_command[1]);

  if (!(visf = fdopen (pipe_command[0], "r")) ||
      !(graphf = fdopen (pipe_graph[1], "w"))) {
    perror ("fdopen");
    if (visf)
      fclose (visf);
    else
      close (visfd);
    close (pipe_graph[1]);
    return 0;
  }

  visfd = pipe_command[0];
  vispid = pid;
#endif // __WIN32
  theDotty.myOut = new class Printer;
  theDotty.myOut->setWidth (0);
  theDotty.myOut->setOutput (graphf);
  return &theDotty;
}

#ifndef __WIN32
void
Dotty::stopVisual ()
{
  if (!vispid)
    return;
  assert (visf && graphf && visfd && theDotty.myOut);
  vispid = 0;
  fclose (visf);
  visf = 0;
  visfd = 0;
}
#endif // !__WIN32

void
Dotty::clearVisual ()
{
  if (!graphf && !myOut)
    return;
  assert (graphf && myOut);
  fclose (graphf);
  graphf = 0;
  delete myOut;
  myOut = 0;
}

/** Backslashify a string
 * @param buf		the string buffer
 * @param record	flag: escape characters for records
 */
static void
backslashify (class StringBuffer& buf,
	      bool record)
{
  /** The characters that must be escaped */
  static const char esc1[] = "\\\"<>{|}", esc2[] = "\\\"";
  unsigned length = buf.getLength ();
  /** Number of required escape characters */
  unsigned numEscapes = 0;
  unsigned i = length;
  if (record) {
    for (const char* s = buf.getString (); i--; s++)
      if (memchr (esc1, *s, sizeof esc1))
	numEscapes++;
  }
  else {
    for (const char* s = buf.getString (); i--; s++)
      if (memchr (esc2, *s, sizeof esc2))
	numEscapes++;
  }
  if (!numEscapes)
    return;
  char* str = buf.extend (numEscapes);
  memmove (str + numEscapes, str, length);
  length += numEscapes;
  for (i = 0; i < length; i++) {
    register char c = str[i + numEscapes];
    if (record
	? memchr (esc1, c, sizeof esc1)
	: memchr (esc2, c, sizeof esc2)) {
      numEscapes--;
      str[i++] = '\\';
    }
    str[i] = c;
  }
}

/** Display the graph prologue
 * @param net		the net
 * @param adding	flag: add to existing graph
 */
static void
displayPrologue (const class Net* net,
		 bool adding)
{
  unsigned depth = 0;
  const class Net* n;
  if (net)
    for (depth = 0, n = net; (n = n->getParent ()); depth++);
  fputs (adding ? "add" : "new", graphf);
  fputs ("();\n"
	 "digraph \"/", graphf);
  if (depth) {
    unsigned* path = new unsigned[depth];
    unsigned i = depth;
    for (n = net, i = depth; i--; n = n->getParent ()) {
      const class Net* parent = n->getParent ();
      for (unsigned child = parent->getNumChildren (); child--; ) {
	if (&parent->getChild (child) == n) {
	  path[i] = child;
	  goto found;
	}
      }
      assert (false);
    found:
      continue;
    }
    for (i = 0;; ) {
      fprintf (graphf, "%u", path[i]);
      if (++i < depth)
	putc ('/', graphf);
      else
	break;
    }
    delete[] path;
  }
  fputs ("\"{\n"
	 "nodesep=.05;\n"
	 "node[shape=record];\n", graphf);
}

/** Display the graph epilogue */
static void
displayEpilogue ()
{
  fputs ("}\n", graphf);
  fflush (graphf);
}

/** Display a marking
 * @param printer	the output stream
 * @param m		the marking to be displayed
 * @param record	flag: escape characters for records
 */
static void
displayMarking (const class Printer& printer,
		const class GlobalMarking& m,
		bool record)
{
  /** output buffer needed for backslashifying special characters */
  class StringBuffer out;
  class Printer pr;
  pr.setOutput (&out);
  unsigned i;
  for (i = 0; i < m.getSize (); i++) {
    if (m[i].empty () || m[i].getPlace ()->isSuppressed ())
      continue;
    pr.print (m[i].getPlace ()->getName ());
    backslashify (out, record);
    out.append (':', 1);
    printer.printRaw (out.getString ());
    out.create (0);
    bool comma = false;
    for (PlaceMarking::const_iterator t = m[i].begin ();
	 t != m[i].end (); t++) {
      if (!t->second)
	continue;
      if (comma)
	printer.delimiter (',');
      comma = true;
      if (t->second > 1)
	printer.print (t->second), printer.delimiter ('#');
      t->first->display (pr);
      backslashify (out, record);
      printer.printRaw (out.getString ());
      out.create (0);
    }
    printer.printRaw ("\\l");
  }
}

void
Dotty::dump (const class Net& net,
	     bool adding) const
{
  fputs (adding ? "add" : "new", graphf);
  fputs ("();\n"
	 "digraph g{\n"
	 "nodesep=.05;\n", graphf);

  unsigned i;
  class StringBuffer out;
  class Printer pr;
  pr.setOutput (&out);

  for (i = net.getNumPlaces (); i--; ) {
    if (!net.getPlace (i))
      continue;
    pr.print (net.getPlace (i)->getName ());
    backslashify (out, false);
    fprintf (graphf, "%u[label=\"", i);
    fwrite (out.getString (), out.getLength (), 1, graphf);
    out.create (0);
    fputs ("\\n", graphf);
    net.getPlace (i)->getType ().display (pr);
    backslashify (out, false);
    fwrite (out.getString (), out.getLength (), 1, graphf);
    out.create (0);
    fputs ("\"];\n", graphf);
  }
  fputs ("node[shape=box];\n", graphf);
  for (i = net.getNumTransitions (); i--; ) {
    const class Transition& tr = net.getTransition (i);
    pr.print (tr.getName ());
    backslashify (out, false);
    fprintf (graphf, "t%u[label=\"", i);
    fwrite (out.getString (), out.getLength (), 1, graphf);
    out.create (0);
    for (Transition::GateList::const_iterator g = tr.beginG ();
	 g != tr.endG (); g++) {
      fputs ("\\n", graphf);
      (*g)->display (pr);
      backslashify (out, false);
      fwrite (out.getString (), out.getLength (), 1, graphf);
      out.create (0);
    }
    fputs ("\"];\n", graphf);
    unsigned j;
    for (j = tr.getNumInputs (); j--; ) {
      const class Arc& arc = tr.getInput (j);
      fprintf (graphf, "%u->t%u[label=\"", arc.getPlace ().getIndex (), i);
      arc.getExpr ().display (pr);
      backslashify (out, false);
      fwrite (out.getString (), out.getLength (), 1, graphf);
      out.create (0);
      fputs ("\"];\n", graphf);
    }
    for (j = tr.getNumOutputs (); j--; ) {
      const class Arc& arc = tr.getOutput (j);
      fprintf (graphf, "t%u->%u[label=\"", i, arc.getPlace ().getIndex ());
      arc.getExpr ().display (pr);
      backslashify (out, false);
      fwrite (out.getString (), out.getLength (), 1, graphf);
      out.create (0);
      fputs ("\"];\n", graphf);
    }
  }
  fputs ("}\n", graphf);
  fflush (graphf);
}

/** Display a state
 * @param printer	the output stream
 * @param reporter	the reachability graph interface
 * @param state		index number of the state
 */
static void
displayState (const class Printer& printer,
	      const class GraphReporter& reporter,
	      card_t state)
{
  const class Graph& graph = reporter.getGraph ();
  const class ComponentGraph* cgraph = reporter.getComponents ();
  class GlobalMarking* m = graph.fetchState (state);
  card_t* succ = graph.getSuccessors (state);
  assert (!succ || *succ);
  card_t numSucc = succ ? *succ : 0;
  bool isProcessed = graph.isProcessed (state);
  delete[] succ;
  printer.printRaw ("\"");
  printer.printState (state);
  printer.printRaw ("\"[");
  if (graph.isErroneous (state))
    printer.printRaw (isProcessed
		      ? "style=filled,color=coral,fontcolor=yellow,"
		      : "style=filled,color=firebrick,fontcolor=yellow,");
  else if (!numSucc)
    printer.printRaw (isProcessed
		      ? "style=filled,color=black,fontcolor=white,"
		      : "style=filled,color=slategray,fontcolor=white,");
  printer.printRaw ("label=\"{");
  const_cast<class GraphReporter&>(reporter).printState (*m, state, printer);
  if (isProcessed) {
    printer.delimiter ('|');
    printer.print (numSucc);
    if (card_t numPred = graph.getNumPredecessors (state)) {
      printer.delimiter ('(');
      printer.print (numPred);
      printer.delimiter (')');
    }
  }
  if (cgraph) {
    card_t comp = cgraph->getComponent (state);
    if (comp != CARD_T_MAX) {
      printer.printRaw ("|");
      printer.printComponent (comp);
    }
  }
  printer.printRaw ("}|");
  ::displayMarking (printer, *m, true);
  printer.printRaw ("\"];");
  printer.finish ();
  delete m;
}

void
Dotty::show (const class GraphReporter& reporter,
	     card_t state, bool adding) const
{
  ::displayPrologue (&reporter.net, adding);
  ::displayState (*myOut, reporter, state);
  ::displayEpilogue ();
}

/** Display an edge label in the reachability graph
 * @param printer	the output stream
 * @param transition	the transition
 * @param valuation	the valuation
 */
static void
displayEdgeLabel (const class Printer& printer,
		  const class Transition& transition,
		  const class Valuation& valuation)
{
  class StringBuffer out;
  class Printer pr;
  pr.setOutput (&out);
  pr.print (transition.getName ());
  ::backslashify (out, false);
  out.append ("\\n");
  printer.printRaw (out.getString ());
  out.create (0);
  for (Valuation::const_iterator i = valuation.begin ();
       i != valuation.end (); i++) {
    pr.print (i->first->getName ());
    pr.delimiter (':');
    i->second->display (pr);
    backslashify (out, false);
    out.append ("\\l");
    printer.printRaw (out.getString ());
    out.create (0);
  }
}

/** Display an edge in the reachability graph
 * @param printer	the output stream
 * @param transition	the transition
 * @param valuation	the valuation
 * @param source	the source state
 * @param dest		the destination state
 * @param id		index number of the edge
 */
static void
displayEdge (const class Printer& printer,
	     const class Transition& transition,
	     const class Valuation& valuation,
	     card_t source,
	     card_t dest,
	     unsigned id)
{
  assert (valuation.isOK ());
  printer.printRaw ("\"");
  printer.printState (source);
  printer.printRaw ("\"->\"");
  printer.printState (dest);
  printer.printRaw ("\"[id=");
  printer.print (id);
  printer.printRaw (",label=\"");
  displayEdgeLabel (printer, transition, valuation);
  printer.printRaw ("\"];");
  printer.finish ();
}

void
Dotty::succ (class GraphReporter& reporter,
	     card_t state, bool seq, bool adding) const
{
  ::displayPrologue (&reporter.net, adding);
  const class Graph& graph = reporter.getGraph ();
  /** states that have been visited (for loop detection when seq==true) */
  std::set<card_t> visited;
 next:
  reporter.getSuccessors (state).clear ();
  word_t* data;
  card_t* s = graph.getSuccessors (state, &data);
  assert (!s || *s);
  ::displayState (*myOut, reporter, state);
  if (s) {
    class BitUnpacker buf (data);
    for (card_t i = 1; i <= *s; i++) {
      const class Transition* transition;
      class Valuation v;
      reporter.net.decode (buf, transition, v, reporter.isFlattened ());
      ::displayEdge (*myOut, *transition, v, state, s[i], i);
      ::displayState (*myOut, reporter, s[i]);
    }
    delete[] data;
    if (seq && *s == 1 && visited.insert (state = s[1]).second) {
      delete[] s;
      goto next;
    }
    delete[] s;
  }
  ::displayEpilogue ();
}

void
Dotty::pred (const class GraphReporter& reporter,
	     card_t state, bool seq, bool adding) const
{
  ::displayPrologue (&reporter.net, adding);
  ::displayState (*myOut, reporter, state);
  const class Graph& graph = reporter.getGraph ();
  /** states that have been visited (for loop detection when seq==true) */
  std::set<card_t> visited;
 prev:
  Graph::fpos_t pos = graph.getPredecessors (state);
  card_t prev = CARD_T_MAX;
  for (card_t oldsource = CARD_T_MAX; pos != FPOS_NONE; ) {
    card_t source = graph.getPredecessor (pos);
    if (oldsource == source)
      continue; /* several arcs from the same state */
    ::displayState (*myOut, reporter, oldsource = source);
    word_t* data;
    card_t* s = graph.getSuccessors (source, &data);
    assert (s && *s);
    if (s) {
      class BitUnpacker buf (data);
      for (card_t i = 1; i <= *s; i++) {
	const class Transition* transition;
	class Valuation v;
	graph.getNet ().decode (buf, transition, v, reporter.isFlattened ());
	if (s[i] != state)
	  continue;
	::displayEdge (*myOut, *transition, v, source, state, i);
	if (prev == CARD_T_MAX)
	  prev = source;
	else
	  seq = false;
      }
      delete[] data;
      delete[] s;
    }
  }

  if (seq && prev != CARD_T_MAX && visited.insert (state = prev).second)
    goto prev;

  ::displayEpilogue ();
}

/** Display a strongly connected component in a component graph
 * @param printer	the output stream
 * @param cgraph	the component graph
 * @param comp		the component number
 */
static void
displayComponent (const class Printer& printer,
		  const class ComponentGraph& cgraph,
		  card_t comp)
{
  card_t* s = cgraph.getStates (comp);
  assert (s && *s);
  card_t numPred = cgraph.getNumPred (comp);
  card_t numSucc = cgraph.getNumSucc (comp);
  printer.printRaw ("\"");
  printer.printComponent (comp);
  printer.printRaw ("\"[label=\"");
  printer.printComponent (comp);
  printer.printNumber ("|", *s);
  printer.printNumber ("|", numSucc);
  printer.printNumber ("(", numPred);
  printer.printRaw (")\"]");
  printer.finish ();
  delete[] s;
}

/** Display an edge in the strongly connected component graph
 * @param printer	the output stream
 * @param source	the source component
 * @param dest		the destination component
 */
static void
displayComponentEdge (const class Printer& printer,
		      card_t source, card_t dest)
{
  printer.printRaw ("\"");
  printer.printComponent (source);
  printer.printRaw ("\"->\"");
  printer.printComponent (dest);
  printer.printRaw ("\"");
  printer.finish ();
}

void
Dotty::components (const class ComponentGraph& cgraph,
		   bool adding) const
{
  ::displayPrologue (0, adding);
  for (card_t comp = cgraph.size (); comp--; ) {
    ::displayComponent (*myOut, cgraph, comp);
    if (card_t* s = cgraph.getSucc (comp)) {
      assert (*s > 0);
      for (card_t i = *s; i; i--)
	::displayComponentEdge (*myOut, comp, s[i]);
      delete[] s;
    }
  }
  ::displayEpilogue ();
}

void
Dotty::component (const class GraphReporter& reporter,
		  card_t comp,
		  const class Expression* cond,
		  bool adding) const
{
  const class Graph& graph = reporter.getGraph ();
  const class ComponentGraph& cgraph = *reporter.getComponents ();
  card_t* states = cgraph.getStates (comp);
  assert (states && *states);
  ::displayPrologue (&reporter.net, adding);
  card_t state;
  if (cond)
    for (state = *states; state; state--)
      if (!graph.eval (states[state], cond))
	memmove (states + state, states + state + 1,
		 (*states - state) * sizeof *states),
	  (*states)--;
  for (state = *states; state; state--) {
    ::displayState (*myOut, reporter, states[state]);
    word_t* data;
    if (card_t* s = graph.getSuccessors (states[state], &data)) {
      class BitUnpacker buf (data);
      for (card_t i = 1; i <= *s; i++) {
	const class Transition* transition;
	class Valuation v;
	graph.getNet ().decode (buf, transition, v, reporter.isFlattened ());
	register card_t dest = s[i];
	for (card_t* d = states + *states; d > states; d--)
	  if (dest == *d)
	    goto inComponent;
	continue;
      inComponent:
	::displayEdge (*myOut, *transition, v, states[state], dest, i);
      }
      delete[] data;
      delete[] s;
    }
  }
  delete[] states;
  ::displayEpilogue ();
}

void
Dotty::dumpgraph (const class GraphReporter& reporter,
		  bool adding) const
{
  ::displayPrologue (&reporter.net, adding);
  const class Graph& graph = reporter.getGraph ();
  for (card_t state = 0; state < graph.getNumStates (); state++) {
    ::displayState (*myOut, reporter, state);
    word_t* data;
    if (card_t* s = graph.getSuccessors (state, &data)) {
      class BitUnpacker buf (data);
      for (card_t i = 1; i <= *s; i++) {
	const class Transition* transition;
	class Valuation v;
	graph.getNet ().decode (buf, transition, v, reporter.isFlattened ());
	::displayEdge (*myOut, *transition, v, state, s[i], i);
      }
      delete[] data;
      delete[] s;
    }
  }
  ::displayEpilogue ();
}

void
Dotty::comp_succ (const class ComponentGraph& cgraph,
		  card_t comp, bool adding) const
{
  ::displayPrologue (0, adding);
  ::displayComponent (*myOut, cgraph, comp);
  if (card_t* s = cgraph.getSucc (comp)) {
    assert (*s > 0);
    for (card_t i = *s; i; i--) {
      ::displayComponent (*myOut, cgraph, s[i]);
      ::displayComponentEdge (*myOut, comp, s[i]);
    }
    delete[] s;
  }
  ::displayEpilogue ();
}

void
Dotty::comp_pred (const class ComponentGraph& cgraph,
		  card_t comp, bool adding) const
{
  ::displayPrologue (0, adding);
  ::displayComponent (*myOut, cgraph, comp);
  if (card_t* s = cgraph.getPred (comp)) {
    assert (*s > 0);
    for (card_t i = *s; i; i--) {
      ::displayComponent (*myOut, cgraph, s[i]);
      ::displayComponentEdge (*myOut, s[i], comp);
    }
    delete[] s;
  }
  ::displayEpilogue ();
}

void
Dotty::show (const class GraphReporter& reporter,
	     const card_t* path, bool adding, bool reverse) const
{
  assert (!!path);
  ::displayPrologue (&reporter.net, adding);
  const class Graph& graph = reporter.getGraph ();
  for (const card_t* p = path + *path; p > path; p--) {
    ::displayState (*myOut, reporter, *p);
    if (const card_t* next = reverse
	? (p < path + *path ? p + 1 : 0)
	: (p > path + 1 ? p - 1 : 0)) {
      word_t* data;
      card_t* s = graph.getSuccessors (*p, &data);
      assert (!s || *s);
      if (s) {
	class BitUnpacker buf (data);
	for (card_t i = 1; i <= *s; i++) {
	  const class Transition* transition;
	  class Valuation v;
	  graph.getNet ().decode (buf, transition, v, reporter.isFlattened ());
	  if (s[i] != *next)
	    continue;
	  ::displayEdge (*myOut, *transition, v, *p, *next, i);
	}
	delete[] data;
	delete[] s;
      }
    }
  }
  ::displayEpilogue ();
}

void
Dotty::displayPrologue (bool adding) const
{
  if (!graphf
#ifndef __WIN32
      || !vispid
#endif // !__WIN32
      )
    return;
  fputs (adding ? "add" : "new", graphf);
  fputs ("();\n"
	 "digraph g{\n"
	 "nodesep=.05;\n"
	 "node[shape=box];\n", graphf);
}

void
Dotty::displayEpilogue () const
{
  if (!graphf
#ifndef __WIN32
      || !vispid
#endif // !__WIN32
      )
    return;
  fputs ("}\n", graphf);
  fflush (graphf);
}

void
Dotty::displayMarking (const class GlobalMarking& m) const
{
  if (!graphf
#ifndef __WIN32
      || !vispid
#endif // !__WIN32
      )
    return;
  fputc ('"', graphf);
  ::displayMarking (*myOut, m, false);
  fputs ("\"\n", graphf);
}

void
Dotty::displayEdge (const class Transition& transition,
		    const class Valuation& valuation,
		    const class GlobalMarking& m) const
{
  if (!graphf
#ifndef __WIN32
      || !vispid
#endif // !__WIN32
      )
    return;
  fputs ("->\"", graphf);
  ::displayMarking (*myOut, m, false);
  fputs ("\"\n[label=\"", graphf);
  ::displayEdgeLabel (*myOut, transition, valuation);
  fputs ("\"]\n\"", graphf);
  ::displayMarking (*myOut, m, false);
  fputs ("\"\n", graphf);
}
