// Struct type class -*- c++ -*-

#include "snprintf.h"

#ifdef __GNUC__
# pragma implementation
#endif // __GNUC__
#include "StructType.h"
#include "StructValue.h"
#include "Constraint.h"
#include "Printer.h"

/** @file StructType.C
 * Struct data type
 */

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

class Value&
StructType::getFirstValue () const
{
  if (myConstraint)
    return *myConstraint->getFirstValue ().copy ();
  else {
    class StructValue* v = new class StructValue (*this);
    for (card_t i = 0; i < getSize (); i++)
      (*v)[i] = &(*this)[i].getFirstValue ();
    return *v;
  }
}

class Value&
StructType::getLastValue () const
{
  if (myConstraint)
    return *myConstraint->getLastValue ().copy ();
  else {
    class StructValue* v = new class StructValue (*this);
    for (card_t i = 0; i < getSize (); i++)
      (*v)[i] = &(*this)[i].getLastValue ();
    return *v;
  }
}

bool
StructType::isAssignable (const class Type& type) const
{
  if (&type == this)
    return true;

  if (type.getKind () != getKind ())
    return Type::isAssignable (type);

  return myComponents.isAssignable
    (static_cast<const class StructType&>(type).myComponents);
}

bool
StructType::isAlwaysAssignable (const class Type& type) const
{
  if (&type == this)
    return true;

  if (type.getKind () != getKind ())
    return false;

  return myComponents.isAlwaysAssignable
    (static_cast<const class StructType&>(type).myComponents);
}

bool
StructType::isConstrained (const class Value& value) const
{
  assert (value.getType ().isAssignable (*this));
  const class StructValue& v = static_cast<const class StructValue&>(value);
  assert (v.getSize () == getSize ());

  for (card_t i = getSize (); i--; )
    if (!(*this)[i].isConstrained (v[i]))
      return false;

  return Type::isConstrained (value);
}

card_t
StructType::do_getNumValues () const
{
  assert (!myConstraint);
  card_t numValues = 1;
  for (card_t i = 0; i < myComponents.getSize (); i++) {
    card_t num = myComponents[i].getNumValues ();
    if (num != CARD_T_MAX && num < CARD_T_MAX / numValues)
      numValues *= num;
    else
      return CARD_T_MAX;
  }

  return numValues;
}

card_t
StructType::convert (const class Value& value) const
{
  assert (value.getKind () == Value::vStruct);
  assert (isConstrained (value));
  assert (getNumValues () < CARD_T_MAX);

  if (myConstraint)
    return Type::convert (value);

  if (!myComponents.getSize ())
    return 0;

  card_t number = 0;
  const class StructValue& v = static_cast<const class StructValue&>(value);
  for (card_t i = myComponents.getSize (); --i; ) {
    number += myComponents[i].convert (v[i]);
    number *= myComponents[i - 1].getNumValues ();
  }
  number += myComponents[0u].convert (v[0]);

  assert (number < getNumValues ());
  return number;
}

class Value*
StructType::convert (card_t number) const
{
  assert (number < getNumValues ());
  assert (getNumValues () < CARD_T_MAX);

  if (myConstraint)
    return Type::convert (number);

  class StructValue* value = new class StructValue (*this);
  if (myComponents.getSize ()) {
    const card_t size = myComponents.getSize ();
    for (card_t i = 0; i < size; i++) {
      const card_t card = myComponents[i].getNumValues ();
      card_t num = number % card;
      number /= card;
      (*value)[i] = myComponents[i].convert (num);
    }
  }

  assert (isConstrained (*value));
  return value;
}

#ifdef EXPR_COMPILE
# include "CExpression.h"
# include <stdio.h>

void
StructType::compile (class StringBuffer& out)
{
  for (card_t i = 0; i < myComponents.getSize (); i++)
    const_cast<class Type&>(myComponents[i]).compile (out);
  Type::compile (out);
}

void
StructType::compileDefinition (class StringBuffer& out,
			       unsigned indent) const
{
  out.indent (indent), out.append ("struct {\n");
  for (card_t i = 0; i < myComponents.getSize (); i++) {
    out.indent (indent + 2), myComponents[i].appendName (out);
    out.append (" s"), out.append (i), out.append (";\n");
  }
  out.indent (indent), out.append ("}");
}

bool
StructType::compileEqual (class StringBuffer& out,
			  unsigned indent,
			  const char* left,
			  const char* right,
			  bool equal,
			  bool first,
			  bool last,
			  bool backslash) const
{
  if (!myComponents.getSize ())
    return false;

  size_t llen = strlen (left), rlen = strlen (right);
  char* l = new char[llen + 23];
  char* r = new char[rlen + 23];
  memcpy (l, left, llen);
  memcpy (r, right, rlen);

  bool gen = false, genAny = false;

  for (card_t i = myComponents.getSize (); i--; ) {
    snprintf (l + llen, 23, ".s%u", i);
    snprintf (r + rlen, 23, ".s%u", i);
    gen = myComponents[i].compileEqual (out, indent, l, r,
					equal, first, !i, backslash);
    if (gen)
      genAny = true, first = false;
  }

  delete[] l; delete[] r;

  if (gen) {
    if (!last)
      out.append (backslash
		  ? (equal ? "&&\\\n" : "||\\\n")
		  : (equal ? "&&\n" : "||\n"));
  }
  else if (genAny && last)
    out.indent (indent);

  return gen || !last;
}

void
StructType::compileCompare3 (class StringBuffer& out,
			     const char* condition,
			     const char* component) const
{
  const size_t len = strlen (component);
  char* const newcomp = new char[len + 23];
  char* const offset = newcomp + len;
  memcpy (newcomp, component, len);

  for (card_t i = myComponents.getSize (); i--; ) {
    snprintf (offset, 23, ".s%u", i);
    myComponents[i].compileCompare3 (out, condition, newcomp);
  }
  delete[] newcomp;
}

void
StructType::do_compileSuccessor (class StringBuffer& out,
				 unsigned indent,
				 const char* lvalue,
				 const char* rvalue,
				 const char* wrap) const
{
  if (!myComponents.getSize ())
    return;

  size_t llen = strlen (lvalue), rlen = strlen (rvalue);
  char* lval = new char[llen + 23];
  char* rval = new char[rlen + 23];
  memcpy (lval, lvalue, llen);
  memcpy (rval, rvalue, rlen);

  out.indent (indent);
  out.append ("do {\n");
  if (!wrap) {
    wrap = "wrap";
    out.indent (indent + 2);
    out.append ("bool_t ");
    out.append (wrap);
    out.append ("=0;\n");
  }

  for (card_t i = 0;; ) {
    snprintf (lval + llen, 23, ".s%u", i);
    snprintf (rval + rlen, 23, ".s%u", i);
    myComponents[i].compileSuccessor (out, indent + 2, lval, rval, wrap);
    if (++i < myComponents.getSize ()) {
      out.indent (indent + 2);
      out.append ("if (!");
      out.append (wrap);
      out.append (") {\n");
      if (lvalue != rvalue && strcmp (lvalue, rvalue)) {
	for (card_t j = i; j < myComponents.getSize (); j++) {
	  out.indent (indent + 4);
	  out.append (lvalue);
	  out.append (".s");
	  out.append (j);
	  out.append ("=");
	  out.append (rvalue);
	  out.append (".s");
	  out.append (j);
	  out.append (";\n");
	}
      }
      out.indent (indent + 4);
      out.append ("continue;\n");
      out.indent (indent + 2);
      out.append ("}\n");
      out.indent (indent + 2);
      out.append (wrap);
      out.append ("=0;\n");
    }
    else
      break;
  }

  out.indent (indent);
  out.append ("} while (0);\n");

  delete[] lval;
  delete[] rval;
}

void
StructType::do_compilePredecessor (class StringBuffer& out,
				   unsigned indent,
				   const char* lvalue,
				   const char* rvalue,
				   const char* wrap) const
{
  if (!myComponents.getSize ())
    return;

  size_t llen = strlen (lvalue), rlen = strlen (rvalue);
  char* lval = new char[llen + 23];
  char* rval = new char[rlen + 23];
  memcpy (lval, lvalue, llen);
  memcpy (rval, rvalue, rlen);

  out.indent (indent);
  out.append ("do {\n");
  if (!wrap) {
    wrap = "wrap";
    out.indent (indent + 2);
    out.append ("bool_t ");
    out.append (wrap);
    out.append ("=0;\n");
  }

  for (card_t i = 0;; ) {
    snprintf (lval + llen, 23, ".s%u", i);
    snprintf (rval + rlen, 23, ".s%u", i);
    myComponents[i].compilePredecessor (out, indent + 2, lval, rval, wrap);
    if (++i < myComponents.getSize ()) {
      out.indent (indent + 2);
      out.append ("if (!");
      out.append (wrap);
      out.append (") {\n");
      if (lvalue != rvalue && strcmp (lvalue, rvalue)) {
	for (card_t j = i; j < myComponents.getSize (); j++) {
	  out.indent (indent + 4);
	  out.append (lvalue);
	  out.append (".s");
	  out.append (j);
	  out.append ("=");
	  out.append (rvalue);
	  out.append (".s");
	  out.append (j);
	  out.append (";\n");
	}
      }
      out.indent (indent + 4);
      out.append ("continue;\n");
      out.indent (indent + 2);
      out.append ("}\n");
      out.indent (indent + 2);
      out.append (wrap);
      out.append ("=0;\n");
    }
    else
      break;
  }

  out.indent (indent);
  out.append ("} while (0);\n");

  delete[] lval;
  delete[] rval;
}

void
StructType::compileCast (class CExpression& cexpr,
			 unsigned indent,
			 const class Type& target,
			 const char* lvalue,
			 const char* rvalue) const
{
  assert (isAssignable (target));
  if (target.getKind () != Type::tStruct)
    Type::compileCast (cexpr, indent, target, lvalue, rvalue);
  else {
    const class StructType& st = static_cast<const class StructType&>(target);
    assert (getSize () == st.getSize ());
    size_t llen = strlen (lvalue);
    size_t rlen = strlen (rvalue);
    char* lval = new char[llen + 23];
    char* rval = new char[llen + 23];
    memcpy (lval, lvalue, llen);
    memcpy (rval, rvalue, rlen);
    for (card_t i = getSize (); i--; ) {
      snprintf (lval + llen, 23, ".s%u", i);
      snprintf (rval + rlen, 23, ".s%u", i);
      myComponents[i].compileCast (cexpr, indent, st[i], lval, rval);
    }
    delete[] lval;
    delete[] rval;
    if (const class Constraint* c = target.getConstraint ())
      c->compileCheck (cexpr, indent, lvalue);
  }
}

void
StructType::do_compileConversion (class StringBuffer& out,
				  unsigned indent,
				  const char* value,
				  const char* number,
				  bool add) const
{
  card_t i = myComponents.getSize ();
  assert (getNumValues () < CARD_T_MAX && i);
  size_t length = strlen (value);
  char* val = new char[length + 23];
  char* offset = val + length;
  memcpy (val, value, length);
  memcpy (offset, ".s0", 4);

  if (myComponents[0u].getNumValues () == getNumValues ())
    myComponents[0u].compileConversion (out, indent, val, number, add);
  else if (--i) {
    const char* num = number;
    if (add) {
      out.indent (indent), out.append ("{\n");
      out.indent (indent += 2), out.append ("card_t ");
      length = strlen (number);
      char* nbr = new char[length + 2];
      memcpy (nbr, number, length);
      memcpy (nbr + length, "_", 2);
      num = nbr;
      out.append (num), out.append (";\n");
    }

    for (bool next = false; i; next = true) {
      snprintf (offset, 23, ".s%u", i);
      myComponents[i].compileConversion (out, indent, val, num, next);
      const card_t numValues = myComponents[--i].getNumValues ();
      if (numValues != 1) {
	out.indent (indent), out.append (num), out.append ("*=");
	out.append (numValues), out.append (";\n");
      }
    }

    memcpy (offset, ".s0", 4);
    myComponents[0u].compileConversion (out, indent, val, num, true);

    if (add) {
      out.indent (indent), out.append (number), out.append ("+=");
      out.append (num), out.append (";\n");
      out.indent (indent -= 2), out.append ("}\n");
      delete[] num;
    }
  }
  else
    myComponents[0u].compileConversion (out, indent, val, number, add);

  delete[] val;
}

void
StructType::compileReverseConversion (class StringBuffer& out,
				      unsigned indent,
				      const char* number,
				      const char* value) const
{
  if (myConstraint)
    Type::compileReverseConversion (out, indent, number, value);
  else if (getNumValues () == 1)
    compileBottom (out, indent, value);
  else {
    size_t length = strlen (value);
    char* val = new char[length + 23];
    char* offset = val + length;
    memcpy (val, value, length);

    switch (myComponents.getSize ()) {
    case 0:
      assert (false);
      break;
    default:
      if (myComponents[0u].getNumValues () != getNumValues ()) {
	length = strlen (number);
	char* num = new char[length + 2];
	memcpy (num, number, length);
	memcpy (num + length, "_", 2);

	out.indent (indent), out.append ("{\n");
	out.indent (indent + 2);
	out.append ("card_t "), out.append (num), out.append (";\n");

	for (card_t i = 0; i < myComponents.getSize (); i++) {
	  snprintf (offset, 23, ".s%u", i);
	  card_t numValues = myComponents[i].getNumValues ();
	  if (numValues != 1) {
	    out.indent (indent + 2);
	    out.append (num), out.append ("="), out.append (number);
	    out.append ("%"), out.append (numValues);
	    out.append (", ");
	    out.append (number), out.append ("/=");
	    out.append (numValues);
	    out.append (";\n");
	    myComponents[i].compileReverseConversion (out, indent + 2,
						      num, val);
	  }
	  else
	    myComponents[i].compileBottom (out, indent + 2, val);
	}

	out.indent (indent), out.append ("}\n");

	delete[] num;
	break;
      }
      else {
	for (card_t i = myComponents.getSize (); --i; ) {
	  snprintf (offset, 23, ".s%u", i);
	  myComponents[i].compileBottom (out, indent, val);
	}
      }
      // fall through
    case 1:
      memcpy (offset, ".s0", 4);
      myComponents[0u].compileReverseConversion (out, indent, number, val);
    }
    delete[] val;
  }
}

void
StructType::compileEncoder (class CExpression& cexpr,
			    unsigned indent,
			    const char* func,
			    const char* value) const
{
  if (getNumValues () < CARD_T_MAX)
    Type::compileEncoder (cexpr, indent, func, value);
  else {
    size_t length = strlen (value);
    char* val = new char[length + 23];
    char* offset = val + length;
    memcpy (val, value, length);
    for (card_t i = 0; i < myComponents.getSize (); i++) {
      snprintf (offset, 23, ".s%u", i);
      myComponents[i].compileEncoder (cexpr, indent, func, val);
    }
    delete[] val;
  }
}

void
StructType::compileDecoder (class CExpression& cexpr,
			    unsigned indent,
			    const char* func,
			    const char* value) const
{
  if (getNumValues () < CARD_T_MAX)
    Type::compileDecoder (cexpr, indent, func, value);
  else {
    size_t length = strlen (value);
    char* val = new char[length + 23];
    char* offset = val + length;
    memcpy (val, value, length);
    for (card_t i = 0; i < myComponents.getSize (); i++) {
      snprintf (offset, 23, ".s%u", i);
      myComponents[i].compileDecoder (cexpr, indent, func, val);
    }
    delete[] val;
  }
}

#endif // EXPR_COMPILE

void
StructType::display (const class Printer& printer) const
{
  printer.printRaw ("struct ");
  printer.delimiter ('{')++;
  myComponents.display (printer);
  --printer.delimiter ('}');

  if (myConstraint)
    myConstraint->display (printer);
}
