/*

    This file is part of the Maude 3 interpreter.

    Copyright 1997-2003 SRI International, Menlo Park, CA 94025, USA.

    This program 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 of the License, or
    (at your option) any later version.

    This program 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.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

*/

//
//	Implementation of match compilation for AC and ACU theories.
//	Part 2: aliens only case.
//

void
ACU_Term::compileAliensOnlyCase(ACU_LhsAutomaton* automaton,
				const Vector<Pair>& nonGroundAliens,
				const VariableInfo& variableInfo,
				NatSet& boundUniquely,
				bool& subproblemLikely)
{
  subproblemLikely = false;
  //
  //	Find a "best" sequence to match NGAs that can be "forced".
  //
  CP_Sequence bestSequence;
  findConstraintPropagationSequence(nonGroundAliens, boundUniquely, bestSequence);
  //
  //	Compile forced NGAs and add them to the ACU_LhsAutomaton.
  //
  int nrForced_NGAs = bestSequence.nrIndependent;
  for (int i = 0; i < nrForced_NGAs; i++)
    {
      const Pair& p = nonGroundAliens[bestSequence.sequence[i]];
      bool spl;
      LhsAutomaton* subAutomaton = p.term->compileLhs(false, variableInfo, boundUniquely, spl);
      subproblemLikely = subproblemLikely || spl;
      automaton->addNonGroundAlien(p.term, subAutomaton, p.multiplicity);
    }
  Assert(boundUniquely == bestSequence.bound,
	 "ACU_Term::compileAliensOnlyCase(): bound uniquely clash");
  //
  //	Heuristically reorder any remaining NGAs.
  //
  int nrNonGroundAliens = nonGroundAliens.size();
  if (nrForced_NGAs < nrNonGroundAliens)
    {
      Assert(nrNonGroundAliens - nrForced_NGAs >= 2,
	     "ACU_Term::compileAliensOnlyCase(): lone non-forceable alien is impossible");
      subproblemLikely = true;
      weakConstraintPropagation(nonGroundAliens, boundUniquely, nrForced_NGAs, bestSequence.sequence);
    }
  //
  //	Compile remaining NGAs and add them to the ACU_LhsAutomaton.
  //
  for (int i = nrForced_NGAs; i < nrNonGroundAliens; i++)
    {
      const Pair& p = nonGroundAliens[bestSequence.sequence[i]];
      //
      //	We need a local copy of the set uniquely bound variables since we
      //	cannot propagate constraint on variables generated by non-forced NGAs.
      //
      NatSet local(boundUniquely);
      bool spl;
      LhsAutomaton* subAutomaton = p.term->compileLhs(false, variableInfo, local, spl);
      subproblemLikely = subproblemLikely || spl;
      automaton->addNonGroundAlien(p.term, subAutomaton, p.multiplicity);
    }
  automaton->complete(ACU_LhsAutomaton::ALIENS_ONLY, bestSequence.nrIndependent);
}

void
ACU_Term::findConstraintPropagationSequence(const Vector<Pair>& aliens,
					    const NatSet& boundUniquely,
					    CP_Sequence& bestSequence)
{
  int nrAliens = aliens.length();
  Vector<int> currentSequence(nrAliens);
  for (int i = 0; i < nrAliens; i++)
    currentSequence[i] = i;
  bestSequence.cardinality = -1;
  findConstraintPropagationSequence(aliens, currentSequence, boundUniquely,
				    0, bestSequence);
  Assert(bestSequence.cardinality >= 0, "ACU_Term::findConstraintPropagationSequence() : didn't find a sequence");
}
		    
void
ACU_Term::findConstraintPropagationSequence(const Vector<Pair>& aliens,
					    const Vector<int>& currentSequence,
					    const NatSet& boundUniquely,
					    int step,
					    CP_Sequence& bestSequence)
{
  bool expandedAtLeastOneBranch = false;
  //
  //        Try to grow search tree.
  //
  int nrAliens = aliens.size();
  for (int i = step; i < nrAliens; i++)
    {
      Term* t = aliens[currentSequence[i]].term;
      //
      //	Check that t won't steal a subject from another NGA - essentially it
      //	has to fail early on all instances of the remaining NGAs.
      //
      for (int j = step; j < nrAliens; j++)
	{
	  if (j != i && !(t->earlyMatchFailOnInstanceOf(aliens[currentSequence[j]].term)))
	    goto nextAlien;
	}
      {
	//
	//	Build a new sequence by adding t.
	//
	Vector<int> newSequence(currentSequence);
	swap(newSequence[step], newSequence[i]);
	int newStep = step + 1;
	bool usefulToMatchNow = false;
	//
	//	Now see if term will force the binding of additional variables.
	//
	NatSet newBound(boundUniquely);
	t->analyseConstraintPropagation(newBound);
	NatSet newlyBoundUniquely(newBound);
	newlyBoundUniquely.subtract(boundUniquely);
	if (!newlyBoundUniquely.empty())
	  {
	    //
	    //	Since we uniquely bound additional variables we might "ground out match"
	    //	on additional NGAs that can be added to the new sequence.
	    //
	    //	Matching t now is considered useful if it allows an NGA to ground out match
	    //	or it binds uniquely a variable occurring in a remaining NGA. Actually the former
	    //	implies the latter but we need to test the former anyway.
	    //
	    for (int j = newStep; j < nrAliens; j++)
	      {
		Term* a = aliens[newSequence[j]].term;
		if (a->willGroundOutMatch(newBound))
		  {
		    swap(newSequence[newStep], newSequence[j]);
		    ++newStep;
		    usefulToMatchNow = true;
		  }
		usefulToMatchNow = usefulToMatchNow || !(newlyBoundUniquely.disjoint(a->occursBelow()));
	      }
	  }
	//
	//	We consider postponement of matching to be useful if t had an unbound variable that occurs
	//	in a remaining NGA.
	//
	bool usefulToPostpone = false;
	NatSet unbound(t->occursBelow());
	unbound.subtract(newBound);
	if (!unbound.empty())
	  {
	    for (int j = newStep; j < nrAliens; j++)
	      {
		Term* a = aliens[newSequence[j]].term;
		if (!(unbound.disjoint(a->occursBelow())))
		  {
		    usefulToPostpone = true;
		    break;
		  }
	      }
	  }
	if (usefulToPostpone)
	  {
	    //
	    //	We might get a better sequence by matching t later.
	    //
	    if (!expandedAtLeastOneBranch || usefulToMatchNow)
	      {
		//
		//	But either we have yet to expand a branch from the current state or we
		//	might find a better sequence by matching now so we explore this branch anyway.
		//
		findConstraintPropagationSequence(aliens, newSequence, newBound, newStep, bestSequence);
		expandedAtLeastOneBranch = true;
	      }
	  }
	else
	  {
	    //
	    //	We lose nothing by matching t now, so continue with new sequence and prune
	    //	any unexplored paths from this node since they can't produce a better
	    //	sequence than the best obtained by extending the new sequence.
	    //
	    findConstraintPropagationSequence(aliens, newSequence, newBound, newStep, bestSequence);
	    return;
	  }
      }
    nextAlien:
      ;
    }
  if (expandedAtLeastOneBranch)
    return;
  //
  //    Didn't find any forceable NGAs - leaf node of search tree.
  //
  //	We want a sequence that guarantees unique bindings for the greatest number of variables (to cut down
  //	the match-time search space) and we break ties in favor of longer sequences (more NGAs forced).
  //
  int n = boundUniquely.cardinality();
  if (n > bestSequence.cardinality ||
      (n == bestSequence.cardinality && step > bestSequence.sequence.length()))
    {
      bestSequence.sequence = currentSequence;  // deep copy
      bestSequence.nrIndependent = step;
      bestSequence.bound = boundUniquely;  // deep copy
      bestSequence.cardinality = n;
    }
}
