package ilog.language.design.types;

/**
 * @version     Last modified on Sat Sep 21 04:15:40 2002 by hak
 * @author      <a href="mailto:hak@ilog.fr">Hassan A&iuml;t-Kaci</a>
 * @copyright   &copy; 2002 <a href="http://www.ilog.fr/">ILOG, S.A.</a>
 */

import java.util.HashMap;
import java.util.ArrayList;

import ilog.language.tools.Misc;
import ilog.language.design.kernel.TupleFieldName;

/**
 * This is the type of tuples whose components are identified by field names. It derives
 * from <a href="./TupleType.html"><tt>TupleType</tt></a>, and in addition keeps an
 * array of <a href="../kernel/TupleFieldName.html"><tt>TupleFieldName</tt></a>s
 * recording the names. The reason for using <tt>TupleFieldName</tt>s rather than
 * <tt>String</tt>s is so that the array of field names may be sorted lexicographically
 * for the purpose of normalizing all named tuples and named tuple types, while
 * recording their original indices for displaying components in their "original"
 * order. Note that this notion is rather fuzzy as it corresponds to the ordering of
 * components found by the constructor (which is meant to be used by a parser) and can
 * only be that of a specific written named tuple (type) occurrence. Thus, this order
 * may only be <i>one</i> among many if distinct occurrences of this type, or tuples of
 * this type, are written using differring orders of components. Nevertheless, for
 * consistently written tuples, the components will be displayed in the expected order.
 */

public class NamedTupleType extends TupleType
{
  private TupleFieldName[] _fields;
  private int[] _index;

  public NamedTupleType (Type[] components, TupleFieldName[] fields)
    {
      _components = components;
      _fields = fields;
    }

  public NamedTupleType (ArrayList components, ArrayList names)
    {
      // NB: this assumes that both lists are non-empty, and have the same size.

      _fields = new TupleFieldName[names.size()];
      for (int i=_fields.length; i-->0;)
        _fields[i] = new TupleFieldName((String)names.get(i),i);
      Misc.sort(_fields);

      _components = new Type[components.size()];
      for (int i=_components.length; i-->0;)
        _components[i] = (Type)components.get(_fields[i].index());
    }

  public final TupleFieldName[] fields ()
    {
      return _fields;
    }

  public final int[] index ()
    {
      if (_index == null)
        {
          _index = new int[dimension()];

          for (int i=_index.length; i-->0;)
            _index[_fields[i].index()] = i;
        }

      return _index;      
    }

  public final String fieldSet ()
    {
      StringBuffer buf = new StringBuffer("{");

      for (int i=0; i<dimension(); i++)
        buf.append(_fields[index()[i]]).append(i==dimension()-1?"":", ");

      return buf.append("}").toString();
    }

  public final int position (String name)
    {
      for (int i=0; i<_fields.length; i++)
        if (_fields[i].isEqualTo(name))
          return i+1;

      return 0;
    }

  public final byte kind ()
    {
      return NAMED_TUPLE;
    }

  public final void unify (Type type, TypeChecker typeChecker) throws FailedUnificationException
    {
      if ((type = type.value()) == this)
        return;

      switch (type.kind())
        {
        case PARAMETER:
          type.unify(this,typeChecker);
          return;

        case NAMED_TUPLE:
          NamedTupleType other = (NamedTupleType)type;

          if (other.dimension() != dimension())
            typeChecker.error(new TypeClashException(this,other));

          for (int i=dimension(); i-->0;)
            {
              if (!_fields[i].equals(other.fields()[i]))
                typeChecker.error(new TypeClashException(this,other));

              component(i).unify(other.components()[i],typeChecker);
            }
          return;

        default:
          typeChecker.error(new TypeClashException(this,type));
        }
    }

  public boolean unify (Type type)
    {
      if ((type = type.findValue()) == this)
        return true;

      switch (type.kind())
        {
        case PARAMETER:
          ((TypeParameter)type).bind(this);
          return true;

        case NAMED_TUPLE:
          NamedTupleType other = (NamedTupleType)type;
          int position = dimension();
          boolean ok = (position == other.dimension());

          while (ok && position-->0)
            ok &= _fields[position].equals(other.fields()[position])
               && _components[position].findValue().unify(other.components()[position]);

          return ok;
        }

      return false;
    }

  public final Type copy (HashMap parameters)
    {
      if (dimension() == 0)
        return this;

      Type[] newComponents = new Type[dimension()];

      for (int i=dimension(); i-->0;)
        newComponents[i] = component(i).copy(parameters);

      return new NamedTupleType(newComponents,_fields);
    }

  public final boolean isEqualTo (Type type)
    {
      if (this == type)
        return true;

      if (!(type instanceof NamedTupleType))
        return false;

      NamedTupleType other = (NamedTupleType)type;
          
      if (dimension() != other.dimension())
        return false;

      for (int i=dimension(); i-->0;)
        if (!_fields[i].equals(other.fields()[i])
            || !component(i).isEqualTo(other.component(i)))
          return false;

      return true;
    }

  public final boolean isEqualTo (Type type, HashMap parameters)
    {
      if (this == type)
        return true;

      if (!(type instanceof NamedTupleType))
        return false;

      NamedTupleType other = (NamedTupleType)type;
          
      if (dimension() != other.dimension())
        return false;

      for (int i=dimension(); i-->0;)
        if (!_fields[i].equals(other.fields()[i])
            || !component(i).isEqualTo(other.component(i),parameters))
          return false;

      return true;
    }

  public final String toString ()
    {
      StringBuffer buf = new StringBuffer("<");

      for (int i=0; i<dimension(); i++)
        buf.append(_fields[index()[i]])
           .append(":")
           .append(component(index()[i]))
           .append(i==dimension()-1?"":",");

      return buf.append(">").toString();
    }
}
