#include "minsearch.hh"
#include "patternminsearcher.hh"
#include "util.hh"


using namespace Gecode;


MinSearch::MinSearch (Space& home, Int::IntView clusterStart, Int::IntView offsetStart, Int::IntView clusterEnd, Int::IntView offsetEnd, MinSearcher& ms, GetDataSource callback) :
  Propagator (home),
  m_callback (callback),
  ds (m_callback (home)),
  clusterStart (clusterStart),
  offsetStart (offsetStart),
  clusterEnd (clusterEnd),
  offsetEnd (offsetEnd),
  m_ms (ms)
{
  // TODO: should also subscribe to an Int::IntView from ds.unavailable ().
  clusterStart.subscribe (home, *this, Int::PC_INT_DOM);
  offsetStart.subscribe (home, *this, Int::PC_INT_DOM);
  clusterEnd.subscribe (home, *this, Int::PC_INT_DOM);
  offsetEnd.subscribe (home, *this, Int::PC_INT_DOM);
}


MinSearch::MinSearch (Space& home, bool share, MinSearch& p) :
  Propagator (home, share, p),
  m_callback (p.m_callback),
  ds (m_callback (home)),
  m_ms (p.m_ms) // Note that this is copying the reference from another Space, rather than cloning the MinSearcher.
{
  clusterStart.update (home, share, p.clusterStart);
  offsetStart.update (home, share, p.offsetStart);
  clusterEnd.update (home, share, p.clusterEnd);
  offsetEnd.update (home, share, p.offsetEnd);
  // std::cout << "MinSearch copy invoked: " << x0 << std::endl;
}
  

Propagator* MinSearch::copy (Space& home, bool share)
{
  return new (home) MinSearch (home, share, *this);
}


size_t MinSearch::dispose (Space& home) 
{
  clusterStart.cancel (home, *this, Int::PC_INT_DOM);
  offsetStart.cancel (home, *this, Int::PC_INT_DOM);
  clusterEnd.cancel (home, *this, Int::PC_INT_DOM);
  offsetEnd.cancel (home, *this, Int::PC_INT_DOM);
  (void) Propagator::dispose (home);
  return sizeof (*this);
}


PropCost MinSearch::cost (const Space& home, const ModEventDelta& med) const 
{
  return PropCost::binary (PropCost::HI);
}


ExecStatus MinSearch::propagate (Space& home, const Gecode::ModEventDelta& med) 
{
  int64_t maxEnd = clusterEnd.max ();
  maxEnd *= BLOCK_SIZE;
  maxEnd += (BLOCK_SIZE - 1);

  Int::ViewRanges<Int::IntView> r_clusterStart (clusterStart);
  IntSet s_clusterStart (r_clusterStart);
  IntSetRanges r_clusterStart_copy (s_clusterStart);
  for (; r_clusterStart_copy (); ++r_clusterStart_copy) {
    int64_t l = r_clusterStart_copy.min ();
    l *= BLOCK_SIZE;
    l += offsetStart.min (); // of interest when clusterStart is assigned
    int64_t u = r_clusterStart_copy.max ();
    u *= BLOCK_SIZE;
    u += (BLOCK_SIZE - 1);
    switch (m_ms.find_minimum (home, ds, l, u, maxEnd)) {
    case PATTERN_RESULT_FOUND: 
      {
        if (true) {
          int clusterStartNewMin = (int) (l / BLOCK_SIZE);
          int offsetStartNewMinConditional = (int) (l % BLOCK_SIZE);
          int clusterEndNewMin = (int) (maxEnd / BLOCK_SIZE);
          int offsetEndNewMinConditional = (int) (maxEnd % BLOCK_SIZE);
          if (true) {
            GECODE_ME_CHECK (clusterStart.gq (home, clusterStartNewMin));
          } else {
          }
          GECODE_ME_CHECK (clusterEnd.gq (home, clusterEndNewMin)); 
          if (clusterStart.assigned () && clusterStart.val () == clusterStartNewMin) {
            GECODE_ME_CHECK (offsetStart.gq (home, offsetStartNewMinConditional));
          } else {
            rel (home, (clusterStart == clusterStartNewMin) >> (offsetStart >= offsetStartNewMinConditional), ICL_DOM);
          }
          rel (home, 
               ((clusterStart == clusterStartNewMin) && (offsetStart == offsetStartNewMinConditional))
               >> 
               ((clusterEnd == clusterEndNewMin) && (offsetEnd == offsetEndNewMinConditional)), ICL_DOM);
        }
        if (clusterStart.assigned () && offsetStart.assigned () && clusterEnd.assigned () && offsetEnd.assigned ()) {
          return home.ES_SUBSUMED (*this);
        } else {
          return ES_FIX;
        }
      }
      break;
        
    case PATTERN_RESULT_FAILED: 
      {
        GECODE_ME_CHECK (clusterStart.gr (home, r_clusterStart_copy.max ()));
      }
      break;

    case PATTERN_RESULT_INDETERMINATE: 
      {
        return ES_NOFIX;
      }
      break;
    }
  }
  GECODE_NEVER;
}


ExecStatus MinSearch::post (Space& home, Int::IntView clusterStart, Int::IntView offsetStart, Int::IntView clusterEnd, Int::IntView offsetEnd, MinSearcher& ms, GetDataSource callback)
{
  (void) new (home) MinSearch (home, clusterStart, offsetStart, clusterEnd, offsetEnd, ms, callback);
  return ES_OK;
}


void pattern_min_search (Space& home, IntVar clusterStart, IntVar offsetStart, IntVar clusterEnd, IntVar offsetEnd, std::list<unsigned char>& pattern, GetDataSource callback)
{
  PatternMinSearcher* pattern_min_searcher = new PatternMinSearcher (pattern); 
  Int::IntView clusterStartV (clusterStart);
  Int::IntView offsetStartV (offsetStart);
  Int::IntView clusterEndV (clusterEnd);
  Int::IntView offsetEndV (offsetEnd);
  if (MinSearch::post (home, clusterStartV, offsetStartV, clusterEndV, offsetEndV, *pattern_min_searcher, callback) != ES_OK) {
    home.fail ();
  }
}
