class FrobeniusStrategy : public Strategy {
public:
  FrobeniusStrategy(const vector<mpz_class>& degrees,
		    mpz_class* frobeniusNumber,
		    TermHandler& handler):
    _dimension(handler.getDimension()),
    _partialDegrees(handler.getDimension()),
    _degreeMultiples(0),
    _initialDegree(degrees[0]),
    _handler(handler),
    _frobeniusNumber(frobeniusNumber) {
    ASSERT(_dimension >= 3); // TODO: handle the case _dimension == 3
    ASSERT(degrees.size() == _dimension + 1);
    ASSERT(frobeniusNumber != 0);

    createDegreeMultiples(degrees);
  }

  virtual ~FrobeniusStrategy() {
    ASSERT(_degreeMultiples != 0);
    for (unsigned int i = 0; i < _dimension; ++i)
      delete[] _degreeMultiples[i];
    delete[] _degreeMultiples;

    *_frobeniusNumber = _maximumDegreeSeen;
  }

  virtual bool consideringCall(const ExternalTerm& b,
			       SortedTermList::iterator it,
			       const SortedTermList& terms) {
    return true;
  }

  virtual bool startingCall(const ExternalTerm& b,
			    const SortedTermList& terms) {
    int position = terms.position();

    if (position == 0)
      _partialDegrees[0] = -_initialDegree;
    else {
      const Degree& degree = _degreeMultiples[position - 1][b[position - 1]];
      _partialDegrees[position] = _partialDegrees[position - 1] + degree;
    }

    return !canSkipDueToUpperBound(terms, _partialDegrees[position]);
  }

  virtual void endingCall(const ExternalTerm& b,
			  const SortedTermList& terms) {
  }

  virtual void foundSolution(const ExternalTerm& b) {
    Degree degree = _partialDegrees[_dimension - 2] +
      _degreeMultiples[_dimension - 2][b[_dimension - 2]] +
      _degreeMultiples[_dimension - 1][b[_dimension - 1]];

    if (degree > _maximumDegreeSeen)
      _maximumDegreeSeen = degree;
  }

private:
  bool canSkipDueToUpperBound(const SortedTermList& terms, const Degree& degree) {
    unsigned int position = terms.position();

    if (position > _dimension - 3)
      return false;

    ExternalTerm lcm(_handler);
    SortedTermList::iterator end = terms.end();
    for (SortedTermList::iterator it = terms.begin(); it != end; ++it)
      _handler.lcm(lcm, lcm, &*it, position);

    Degree upperBound = degree;
    for (unsigned int i = position; i < _dimension; ++i) {
      ASSERT(lcm[i] > 0);
      upperBound += _degreeMultiples[i][lcm[i] - 1];
    }

    if (upperBound <= _maximumDegreeSeen)
      return true;


    if (false) { // too expensive to compute
      Degree maxMinLoss = 0; // loss of degree
      for (SortedTermList::iterator it = terms.begin(); it != end; ++it) {
	Degree minLoss = -1;
	for (unsigned int i = position; i < _dimension; ++i) {
	  Degree loss = _degreeMultiples[i][lcm[i] - 1] - _degreeMultiples[i][_handler.getExponent(&*it, i)];
	  if (minLoss == -1 || loss < minLoss)
	    minLoss = loss;
	}
	if (minLoss > maxMinLoss)
	  maxMinLoss = minLoss;
      }

      if (upperBound - maxMinLoss <= _maximumDegreeSeen)
	return true;
    }

    // This works almost as well while being much faster to compute.
    // It is still too expensive a bound.
    if (false) {
      SortedTermList::iterator bestCandidate = end;
      int maxMinValue = -1;
      for (SortedTermList::iterator it = terms.begin(); it != end; ++it) {
	int minValue = -1;
	for (unsigned int i = position; i < _dimension; ++i) {
	  int value = lcm[i] - _handler.getExponent(&*it, i);
	  if (minValue == -1 || value < minValue) {
	    minValue = value;
	  }
	}
	if (maxMinValue == -1 || minValue > maxMinValue) {
	  maxMinValue = minValue;
	  bestCandidate = it;
	  
	}
      }
      ASSERT(bestCandidate != end);
	
      Degree minLoss = -1;
      for (unsigned int i = position; i < _dimension; ++i) {
	Degree loss = _degreeMultiples[i][lcm[i] - 1] -
	  _degreeMultiples[i][_handler.getExponent(&*bestCandidate, i)];
	if (minLoss == -1 || loss < minLoss)
	  minLoss = loss;
      }
      

      if (upperBound - minLoss <= _maximumDegreeSeen)
	return true;
    }


    return false;
  }

  void createDegreeMultiples(const vector<mpz_class>& degrees) {
    ASSERT(_degreeMultiples == 0);
    ASSERT(degrees.size() == _dimension + 1);

    _degreeMultiples = new Degree*[_dimension];
    for (unsigned int position = 0; position < _dimension; ++position) {
      const vector<mpz_class>& decompressionMap = _handler.getDecompressionMap(position);
      _degreeMultiples[position] = new Degree[decompressionMap.size()];

      for (Exponent exponent = 0;
	   exponent < decompressionMap.size(); ++exponent)
	_degreeMultiples[position][exponent] =
	  _handler.getDecompressionMap(position)[exponent] * degrees[position + 1];
    }
  }

  unsigned int _dimension;
  vector<Degree> _partialDegrees;
  Degree** _degreeMultiples;
  Degree _initialDegree;
  Degree _maximumDegreeSeen;
  TermHandler& _handler;
  mpz_class* _frobeniusNumber;
};
