// Pretty-printing utility -*- c++ -*-

#ifdef __GNUC__
# pragma implementation
#endif // __GNUC__
#include "Printer.h"
#include "util.h"
#include <assert.h>
#include <string.h>
#include <ctype.h>

/** @file Printer.C
 * Pretty-printing utility (output to character strings and file streams)
 */

/* Copyright © 1999-2002 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. */

void
Printer::finish () const
{
  assert (!myNesting);
  if (myColumn) {
    if (!myOutput)
      fputc ('\n', myStream);
    myColumn = 0;
  }
}

void
Printer::linebreak () const
{
  if (myOutput) return;
  fputc ('\n', myStream);
  unsigned i = myNesting;
  for (myColumn = myBOL && myStream == stderr ? (*myBOL) () : 0; i--;
       myColumn++)
    fputc (' ', myStream);
}

const class Printer&
Printer::delimiter (char character) const
{
  if (myOutput)
    myOutput->append (character, 1);
  else {
    linebreak (1);
    fputc (character, myStream);
  }
  myColumn++;
  return *this;
}

bool
Printer::isPrintable (const char* name)
{
  if (!*name) return false;
  for (unsigned i = 0; name[i]; i++)
    if (!((name[i] >= 'a' && name[i] <= 'z') ||
	  (name[i] >= 'A' && name[i] <= 'Z') ||
	  (name[i] >= '0' && name[i] <= '9') ||
	  name[i] == '_'))
      return false;
  return true;
}

void
Printer::printRaw (const char* string) const
{
  unsigned i;
  for (i = 0; string[i]; i++);
  if (myOutput)
    myOutput->append (string);
  else {
    linebreak (i);
    fputs (string, myStream);
  }
  myColumn += i;
}

void
Printer::printQuoted (const char* string) const
{
  print (string, strlen (string));
}

void
Printer::print (bool value) const
{
  printRaw (value ? "true" : "false");
}

void
Printer::printState (card_t state) const
{
  printNumber ("@", state);
}

void
Printer::printComponent (card_t comp) const
{
  printNumber ("@@", comp);
}

void
Printer::printNumber (const char* prefix, card_t number) const
{
  assert (!!prefix);
  if (myOutput) {
    myOutput->append (prefix);
    myOutput->append (number);
    return;
  }
  size_t l = strlen (prefix);
  switch (myRadix) {
  case Octal:
    linebreak (l + (number ? log8 (number) + 1 : 1));
    myColumn += fprintf (myStream, number ? "%s%o" : "%s0%o", prefix, number);
  case Hexadecimal:
    linebreak (l + (number ? log16 (number) + 2 : 3));
    myColumn += fprintf (myStream, "%s0x%X", prefix, number);
    break;
  case Decimal:
    linebreak (l + (number ? log8 (number) : 1));
    myColumn += fprintf (myStream, "%s%u", prefix, number);
    break;
  }
}

void
Printer::print (card_t number) const
{
  printNumber ("", number);
}

void
Printer::print (int_t number) const
{
  if (myOutput) {
    myOutput->append (number);
    return;
  }
  switch (myRadix) {
  case Octal:
    linebreak (number ? log8 (card_t (number)) + 1 : 1);
    myColumn += fprintf (myStream, number ? "%o" : "0%o", number);
  case Hexadecimal:
    linebreak (number ? log16 (card_t (number)) + 2 : 3);
    myColumn += fprintf (myStream, "0x%X", number);
    break;
  case Decimal:
    linebreak (number ? log8 (card_t (number < 0 ? -number : number)) : 1);
    myColumn += fprintf (myStream, "%d", number);
    break;
  }
}

void
Printer::print (const char* name) const
{
  bool printable = true;
  unsigned i;
  for (i = 0; name[i]; i++)
    if (!((name[i] >= 'a' && name[i] <= 'z') ||
	  (name[i] >= 'A' && name[i] <= 'Z') ||
	  (name[i] >= '0' && name[i] <= '9') ||
	  name[i] == '_'))
      printable = false;
  if (!i || !((name[0] >= 'a' && name[0] <= 'z') ||
	      (name[0] >= 'A' && name[0] <= 'Z') ||
	      name[0] == '_'))
    printable = false;
  if (printable) {
    if (myOutput)
      myOutput->append (name);
    else {
      linebreak (i);
      fputs (name, myStream);
    }
    myColumn += i;
  }
  else
    print (name, i);
}

void
Printer::print (const char* string, unsigned length) const
{
  if (myOutput)
    myOutput->append ('"', 1);
  else
    *myBuffer.create (1) = '"';
  class StringBuffer& buf = myOutput ? *myOutput : myBuffer;

  unsigned j = buf.getLength ();
  for (unsigned i = 0; i < length; i++) {
    unsigned char c = static_cast<unsigned char>(string[i]);
    switch (c) {
    case '\f':
      memcpy (buf.extend (2) + j, "\\f", 2), j += 2;
      break;
    case '\n':
      memcpy (buf.extend (2) + j, "\\n", 2), j += 2;
      break;
    case '\r':
      memcpy (buf.extend (2) + j, "\\r", 2), j += 2;
      break;
    case '\t':
      memcpy (buf.extend (2) + j, "\\t", 2), j += 2;
      break;
    case '\v':
      memcpy (buf.extend (2) + j, "\\v", 2), j += 2;
      break;
    case '\b':
      memcpy (buf.extend (2) + j, "\\b", 2), j += 2;
      break;
    case '\a':
      memcpy (buf.extend (2) + j, "\\a", 2), j += 2;
      break;
    case 0:
      memcpy (buf.extend (2) + j, "\\0", 2), j += 2;
      break;
    case '\\':
      memcpy (buf.extend (2) + j, "\\\\", 2), j += 2;
      break;
    case '"':
      memcpy (buf.extend (2) + j, "\\\"", 2), j += 2;
      break;
    default:
      if (iscntrl (c)) {
	unsigned l = log8 (c) + 1;
	if (l < 4 && i < length &&
	    string[i + 1] >= '0' && string[i + 1] <= '7')
	  sprintf (buf.extend (4) + j, "\\%03o", c), j += 4;
	else
	  sprintf (buf.extend (l) + j, "\\%o", c), j += l;
      }
      else
	buf.extend (1)[j++] = c;
      break;
    }
  }

  memcpy (&buf.extend (1)[j++], "\"", 2);
  if (!myOutput) {
    linebreak (j);
    fputs (buf.getString (), myStream);
  }
  myColumn += j;
}

void
Printer::print (char_t c) const
{
  if (myOutput)
    myOutput->append ('\'', 1);
  else
    *myBuffer.create (1) = '\'';
  class StringBuffer& buf = myOutput ? *myOutput : myBuffer;

  unsigned j = buf.getLength ();
  switch (c) {
  case '\f':
    memcpy (buf.extend (2) + j, "\\f", 2), j += 2;
    break;
  case '\n':
    memcpy (buf.extend (2) + j, "\\n", 2), j += 2;
    break;
  case '\r':
    memcpy (buf.extend (2) + j, "\\r", 2), j += 2;
    break;
  case '\t':
    memcpy (buf.extend (2) + j, "\\t", 2), j += 2;
    break;
  case '\v':
    memcpy (buf.extend (2) + j, "\\v", 2), j += 2;
    break;
  case '\b':
    memcpy (buf.extend (2) + j, "\\b", 2), j += 2;
    break;
  case '\a':
    memcpy (buf.extend (2) + j, "\\a", 2), j += 2;
    break;
  case 0:
    memcpy (buf.extend (2) + j, "\\0", 2), j += 2;
    break;
  case '\\':
    memcpy (buf.extend (2) + j, "\\\\", 2), j += 2;
    break;
  case '\'':
    memcpy (buf.extend (2) + j, "\\\'", 2), j += 2;
    break;
  default:
    if (iscntrl (c)) {
      unsigned l = log8 (c) + 1;
      sprintf (buf.extend (l) + j, "\\%o", c), j += l;
    }
    else
      buf.extend (1)[j++] = c;
    break;
  }
  memcpy (&buf.extend (1)[j++], "'", 2);
  if (!myOutput) {
    linebreak (j);
    fputs (buf.getString (), myStream);
  }
  myColumn += j;
}
