// FILE. . . . . d:/hak/hlt/src/hlt/fot/fuz/ArgumentPositionMapping.java
// EDIT BY . . . Hassan Ait-Kaci
// ON MACHINE. . Hak-Laptop
// STARTED ON. . Sun Jul 15 07:37:57 2018

/**
 * @version     Last modified on Wed May 15 05:07:48 2019 by hak
 * @author      <a href="mailto:hak@acm.org">Hassan A&iuml;t-Kaci</a>
 * @copyright   &copy; <a href="http://www.hassan-ait-kaci.net/">by the author</a>
 */

package hlt.fot.fuz;

import hlt.fot.Functor;

import hlt.language.util.SetOf;
import hlt.language.util.ArrayList;
import hlt.language.util.IntArrayList;

/**
 * This class represents an argument-position mapping as a set of pairs
 * between distinct but <tt>&alpha;</tt>-similar <a
 * href="../Functor.html"><tt>Functor</tt></a>s for each admissible
 * fuzzy degree <tt>&alpha;</tt> in the defined functor similarity.
 * 
 * @see         ../Signature
 * @see         ../Functor
 * @see         SignatureSimilarity
 *
 */
public class ArgumentPositionMapping
{
  /**
   * This mapping's fuzzy degree. It is initialized in the
   * constructor and never changes.
   */
  private double degree;

  /**
   * Returns this mapping's fuzzy degree.
   */
  public double degree ()
  {
    return degree;
  }

  /**
   * The position maps this mapping is part of.
   */
  private ArgumentPositionMaps positionMaps;

  /**
   * Returns the position maps this mapping is part of.
   */
  public ArgumentPositionMaps positionMaps ()
  {
    return positionMaps;
  }

  /**
   * The index in <tt>positionMaps.argumentMappings()</tt> of
   * this mapping.
   */
  private int mappingIndex;

  /**
   * Returns the index in <tt>positionMaps.argumentMappings()</tt> of
   * this mapping.
   */
  public int mappingIndex ()
  {
    return mappingIndex;
  }

  public void setMappingIndex (int index)
  {
    mappingIndex = index;
  }

  private int degreeIndex;

  /**
   * Returns the index in <tt>similarity().degrees()</tt> of this
   * mapping's fuzzy degree.
   */
  public int degreeIndex ()
  {
    return degreeIndex;
  }

  /* ************************************************************************ */

  /**
   * Returns the similarity this refers to.
   */
  public  SignatureSimilarity similarity ()
  {
    return positionMaps.similarity();
  }

  /**
   * Returns the similarity's signature.
   */
  public  SimilarFunctorSignature signature ()
  {
    return similarity().signature();
  }

  /* ************************************************************************ */

  ///////////////////////
  // FROM functor data //
  ///////////////////////

  /**
   * This mapping's "from" <tt>Functor</tt>. It is initialized in the
   * constructor and never changes.
   */
  private Functor from;

  /**
   * Returns the "from" function symbol (a <tt>Functor</tt>) of this
   * <tt>ArgumentPositionMapping</tt>.
   */
  public Functor from ()
  {
    return from;
  }

  /**
   * The <tt>ArrayList</tt> <tt>domainPositions</tt> is a list of
   * mutually distinct <tt>Integer</tt> objects. Its size is equal to
   * <tt>from.arity()</tt>.  Each <tt>Integer</tt> element at index
   * <tt>i</tt> in this list, <tt>0</tt> &le; <tt>i</tt> &lt;
   * <tt>from.arity()</tt>, has an <tt>int</tt> value equal to
   * <tt>i+1</tt>. It is initialized in the constructor and is never
   * modified thereafter because it serves as the base structure for the
   * set <tt>domain</tt>.
   */
  private ArrayList domainPositions;

  /**
   * The full set of <tt>Integer</tt> elements of
   * <tt>domainPositions</tt> of the <tt>from</tt> functor representing
   * <tt>{1,..., from.arity()}</tt>. It is initialized in the
   * constructor and never changes.
   */
  private SetOf fullDomainPositions;

  /**
   * A set of <tt>Integer</tt> elements of <tt>domainPositions</tt> of
   * the <tt>from</tt> functor. It is initialized by default in the
   * constructor to the set
   * <tt>{1,...,Math.min(from.arity(),to.arity())}</tt> but can be
   * modified using the <tt>defineMapping</tt> method.
   */
  private SetOf domain;

  /**
   * Returns the domain of this mapping as a set of positions if one is
   * defined, or <tt>null</tt> if <tt>from.arity()</tt> = <tt>0</tt>;
   * otherwise sets <tt>domain</tt> to the set <tt>{1, ...,
   * Math.min(from.arity(),to.arity())}</tt> and returns it.
   */
  public SetOf domain ()
  {
    if (domain != null)
      return domain;
    
    if (from.arity() == 0)
      return null;

    domain = new SetOf(domainPositions);

    for (int pos = 0; pos < Math.min(from.arity(),to.arity()); pos++)
      domain.add(domainPositions.get(pos));
    
    return domain;
  }

  /**
   * Sets this mapping's domain to the specified verified domain and
   * returns it.
   */
  public SetOf setDomain (SetOf domain)
  {
    return this.domain = verifiedDomain(domain);
  }

  /**
   * Verifies that the specified set is a subset of <tt>from</tt>'s full
   * domain and that its number of elements does not exceed
   * <tt>Math.min(from.arity(),to.arity())</tt>. If ok, it returns the
   * domain. Otherwise, it throws an
   * <tt>BadArgumentPositionMappingException</tt> with a detailed
   * message.
   */
  public SetOf verifiedDomain (SetOf domain)
  {
    if (!domain.isSubsetOf(fullDomainPositions) || domain.size() > Math.min(from.arity(),to.arity()))
      throw new BadArgumentPositionMappingException
	    ("fuzzy degree: "+degree+", "+
	     "from: "+from+", "+
	     "to: "+to+"; "+
	     "argument position map's domain "+domain+
	     " should be a subset of "+fullDomainPositions+
	     " and not exceed "+ Math.min(from.arity(),to.arity()));

    return domain;
  }

  /* ************************************************************************ */

  /////////////////////
  // TO functor data //
  /////////////////////

  /**
   * This mapping's "to" <tt>Functor</tt>.
   */
  private Functor to;

  /**
   * Returns the "to" function symbol (a <tt>Functor</tt>) of this
   * <tt>ArgumentPositionMapping</tt>. It is initialized in the
   * constructor and never changes.
   */
  public Functor to ()
  {
    return to;
  }

  /**
   * The <tt>ArrayList</tt> <tt>rangePositions</tt> is a list of
   * mutually distinct <tt>Integer</tt> objects. Its size is equal to
   * <tt>to.arity()</tt>. Each <tt>Integer</tt> element at index
   * <tt>i</tt> in this list, <tt>0</tt> &le; <tt>i</tt> &lt;
   * <tt>to.arity()</tt>, has an <tt>int</tt> value equal to
   * <tt>i+1</tt>. It is initialized in the constructor and is never
   * modified thereafter because it serves as the base structure for the
   * set <tt>range</tt>.
   */
  private ArrayList rangePositions;

  /**
   * The full set of <tt>Integer</tt> elements of
   * <tt>rangePositions</tt> of the <tt>to</tt> functor representing
   * <tt>{1,...,to.arity()}</tt>.  It is initialized in the constructor
   * and never changes. It is initialized in the constructor and never
   * changes.
   */
  private SetOf fullRangePositions;

  /**
   * A set of <tt>Integer</tt> elements of <tt>rangePositions</tt> of
   * the <tt>to</tt> functor.  It is initialized by default in the
   * constructor to the set
   * <tt>{1,...,Math.min(from.arity(),to.arity())}</tt> but can be
   * modified using the <tt>defineMapping</tt> method.
   */
  private SetOf range;

  /**
   * Returns the range of this mapping as a set of positions if one is
   * defined, or <tt>null</tt> if <tt>to.arity()</tt> = <tt>0</tt>;
   * otherwise sets <tt>range</tt> to the set <tt>{1, ...,
   * Math.min(from.arity(),to.arity())}</tt> and returns it.
   */
  public SetOf range ()
  {
    if (range != null)
      return range;
    
    if (from.arity() == 0)
      return null;

    range = new SetOf(rangePositions);

    for (int pos = 0; pos < Math.min(from.arity(),to.arity()); pos++)
      range.add(rangePositions.get(pos));
    
    return range;
  }

  /**
   * Sets this mapping's range to the specified verified range and
   * returns it.
   */
  public SetOf setRange (SetOf range)
  {
    return this.range = verifiedRange(range);
  }

  /**
   * Verifies that the specified set is a subset of <tt>to</tt>'s full
   * range and that its number of elements does not exceed
   * <tt>Math.min(from.arity(),to.arity())</tt>. If ok, it returns the
   * domain. Otherwise, it throws an
   * <tt>BadArgumentPositionMappingException</tt> with a detailed
   * message.
  
   */
  public SetOf verifiedRange (SetOf range)
  {
    if (!range.isSubsetOf(fullRangePositions) || range.size() > Math.min(from.arity(),to.arity()))
      throw new BadArgumentPositionMappingException
	("fuzzy degree: "+degree+", "+
	 "from: "+from+", "+
	 "to: "+to+"; "+
	 "argument position map's range "+range+
	 " should be a subset of "+fullRangePositions+
	 " and not exceed "+ Math.min(from.arity(),to.arity()));

    return range;
  }

  /* ************************************************************************ */

  //////////////////
  // MAPPING data //
  //////////////////
  
  /**
   * The two arrays <tt>mapping</tt> and <tt>inverse</tt> represent an
   * <tt>ArgumentPositionMapping</tt> object. They are arrays of
   * argument positions that correspond when comparing the distinct and
   * <tt>degree</tt>-similar functors <tt>from</tt> and
   * <tt>to</tt>. This correspondence must be injective.  Therefore,
   * these arrays are non-<tt>null</tt> only for a non-empty
   * correspondence (<i>i.e.</i>, when neither of the functors is a
   * constant or when the similarity does not involve any arguments in
   * either of the functors).  Otherwise, they must be both defined and
   * have equal length <tt>size</tt> &isin; {<tt>1</tt>, ...,
   * <tt>Math.min(from.arity(),to.arity())</tt>}. If <tt>from</tt>'s
   * argument position <tt>i</tt> &isin; <tt>this.domain()</tt> and
   * <tt>to</tt>'s argument position <tt>j</tt> &isin;
   * <tt>this.range</tt> correspond, then there must be a unique
   * <tt>k</tt> &isin; <tt>{0, ..., size-1}</tt> such that
   * <tt>mapping[k] = j</tt> and <tt>inverse[k] = i</tt>.
   *
   *<p>
   *
   * The arrays <tt>mapping</tt> and <tt>inverse</tt> are set by default
   * to values that represent the identity on the largest set of
   * argument positions common to <tt>from</tt> and <tt>to</tt>. So,
   * <tt>size</tt> = <tt>Math.min(from.arity(),to.arity())</tt> and
   * <tt>domain</tt> = <tt>range</tt> = {<tt>1</tt>, ...,
   * <tt>size</tt>}, such that for all <tt>k</tt> &isin; {<tt>0</tt>,
   * ..., <tt>size-1</tt>}, <tt>mapping[k]</tt> = <tt>inverse[k]</tt> =
   * <tt>k+1</tt>, since positions start at <tt>1</tt> while array
   * indices start at <tt>0</tt>.
   */
  private int[] mapping;
  private int[] inverse;

  /**
   * Returns <tt>true</tt> iff this argument position mapping is empty.
   */
  public boolean isEmpty ()
  {
    return mapping == null;
  }

  /**
   * Returns the size of this <tt>ArgumentPositionMapping</tt>.
   */
  public int size ()
  {
    return isEmpty() ? 0 : mapping.length; // == inverse.length
  }

  /* ************************************************************************ */

  /////////////////
  // Constructor //
  /////////////////

  /**
   * This constructs a new <tt>ArgumentPositionMapping</tt> object to be
   * part of the specified <tt>ArgumentPositionMaps</tt> at
   * approximation degree of index <tt>index</tt> in
   * <tt>similarity().degrees()</tt> between <tt>Functor</tt>
   * <tt>from</tt> and <tt>Functor</tt> <tt>to</tt> with the argument
   * position map specified by <tt>positionPairs</tt>. It is constructed
   * when invoked by the <tt>defineArgumentMapping(index,f,g,pairs)</tt>
   * method in an <tt>ArgumentPositionMaps</tt> object <tt>maps</tt>
   * where all the necessary info is to be registered and completed by
   * closing the declared information when verified to be consistent.
   *
   * <p>If <tt>positionPairs</tt> is <tt>null</tt>, it is set to the
   * identity on the set
   * <tt>{1,...,Math.min(from.arity(),to.arity())}</tt>.  Otherwise, it
   * defines an argument-position mapping as a list of <tt>int</tt>s of
   * the form <tt>[i<sub>1</sub>, j<sub>1</sub>, ..., i<sub>n</sub>,
   * j<sub>n</sub>]</tt>. If it contains valid argument position pairs
   * for the <tt>from</tt> and <tt>to</tt> functors, it will define the
   * set of <tt>domainPositions</tt> <tt>domain</tt> =
   * <tt>{Integer.valueOf(i<sub>1</sub>), ..., Integer.valueOf(i<sub>n</sub>)}</tt> and
   * its set of <tt>rangePositions</tt> <tt>range</tt> =
   * <tt>{Integer.valueOf(j<sub>1</sub>), ..., Integer.valueOf(j<sub>n</sub>)}</tt>, and
   * also define the corresponding arrays of values of these positions
   * <tt>mapping</tt> and <tt>inverse</tt>; otherwise, it throws a
   * <tt>BadArgumentPositionMappingException</tt> with a detailed
   * message.
   * 
   * <p>For example, let us assume that <tt>from.arity()</tt> =
   * <tt>5</tt> and <tt>to.arity()</tt> = <tt>4</tt>, and that their
   * argument mapping specifier is of size <tt>3</tt> and equal to:
   * <tt>2:4 5:2 3:1</tt>, which corresponds to the list of
   * <tt>int</tt>s: <tt>[2, 4, 5, 2, 3, 1]</tt>. This will give:
   * <ul>
   * <li><tt>domain</tt> = <tt>{Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(5)}</tt>,</li>
   * <li><tt>range</tt> = <tt>{Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(4)}</tt>,</li>
   * <li><tt>mapping</tt> = <tt>[2, 5, 3]</tt>,</li>
   * <li> <tt>inverse</tt> = <tt>[4, 2, 1]</tt>.</li>
   * </ul>
   *
   * Note that, while order and repeated elements do matter for lists,
   * order is irrelevant for sets and there can be no repeated
   * element. We shall display a list as a comma-separated sequence of
   * their elements between square brackets, and display sets
   * comma-separated sequences of their elements between curly braces.
   */
  public ArgumentPositionMapping (ArgumentPositionMaps maps,
				  int index, Functor from, Functor to,
				  IntArrayList positionPairs)
  {
    // set this mapping's reference map (where it is defined):
    positionMaps = maps;

    // set the degree index to the specified int:
    degreeIndex = index;
    // set the approximation degree of this mapping (a double):
    degree = maps.degrees().get(degreeIndex);

    // set the source functor:
    this.from = from;
    // set the target functor:
    this.to   = to;

    // a local shorthand for the source's arity
    int fromArity = from.arity();
    // a local shorthand for the target's arity
    int toArity   = to.arity();

    // a local shorthand for the smaller arity between source and target
    int minArity  = Math.min(fromArity,toArity);
    // a local shorthand for the larger arity between source and target
    int maxArity  = Math.max(fromArity,toArity);

    // unique reference list for the corresponding domain set to be built:
    domainPositions = Functor.argumentList(fromArity);
    // unique reference list for the corresponding range set to be built:
    rangePositions  = Functor.argumentList(toArity);

    // corresponding domain - a set of domain positions:
    domain = new SetOf(domainPositions);
    // corresponding range - a set of range positions:
    range  = new SetOf(rangePositions);

    if (positionPairs == null) // no position pairs are specified
      {
	// set domain and range to the least set of positions by default:
	for (int i = 0; i < minArity; i++)
	  {
	    Integer position = Integer.valueOf(i+1);
	    domain.add(position);
	    range.add(position);
	  }

	// initialize this mapping and its inverse to the identity on
	// {1,...,Math.min(from.arity(),to.arity())} by default:
	if (minArity > 0)
	  {
	    mapping = new int[minArity];
	    inverse = new int[minArity];

	    for (int k = 0; k < minArity; k++)
	      mapping[k] = inverse[k] = k+1;
	  }
      }
    else // position pairs are specified
      {
	int cardinality = positionPairs.size() / 2; // since size is necessarily even

	mapping = new int[cardinality];
	inverse = new int[cardinality];

	for (int i = 0; i < cardinality; i++)
	  {
	    domain.add(Integer.valueOf(mapping[i] = positionPairs.get(2*i)));
	    range.add(Integer.valueOf(inverse[i] = positionPairs.get(2*i+1)));
	  }

	// verify that all argument positions are ok and actually define a one-to-one mapping:

	if (domain.size() > fromArity)
	  throw new BadArgumentPositionMappingException
	    ("fuzzy degree: "+degree+", "+
	     "from: "+from+", "+
	     "to: "+to+"; "+
	     "mapping domain too large for functor "+from+"/"+fromArity+": "+domain);

	if (range.size() > toArity)
	  throw new BadArgumentPositionMappingException
	    ("fuzzy degree: "+degree+", "+
	     "from: "+from+", "+
	     "to: "+to+"; "+
	     "mapping range too large for functor "+to+"/"+toArity+": "+range);

	if (domain.size() != range.size())
	  throw new BadArgumentPositionMappingException
	    ("fuzzy degree: "+degree+", "+
	     "from: "+from+", "+
	     "to: "+to+
	     "; non one-one argument-position mapping from :"+domain+" to "+range);

      }
  }
}
