#include "shiftpropagator.hh"


using namespace Gecode;


Shift::Shift (Space& home, Int::IntView x0, Int::IntView x1, int64_t block_offset) :
  Propagator (home),
  x0 (x0),
  x1 (x1),
  m_block_offset (block_offset)
{
  this->x0.subscribe (home, *this, Int::PC_INT_DOM);
}


Shift::Shift (Space& home, bool share, Shift& p) :
  Propagator (home, share, p) 
{
  this->x0.update (home, share, p.x0);
  this->x1.update (home, share, p.x1);
}


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


size_t Shift::dispose (Space& home) 
{
  this->x0.cancel (home, *this, Int::PC_INT_DOM);
  (void) Propagator::dispose (home);
  return sizeof (*this);
}


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

ExecStatus Shift::propagate (Space& home, const Gecode::ModEventDelta& med) 
{
  // See section 23.2 for naive propagator for domain equal.

  if (m_block_offset == 0) {
    Int::ViewRanges<Int::IntView> r0 (x0);
    GECODE_ME_CHECK (x1.inter_r (home, r0));
    if (x1.assigned ()) {
      return home.ES_SUBSUMED (*this);
    } else {
      return ES_FIX;
    }
  } else {
    Int::ViewRanges<Int::IntView> r01 (x0);
    int num_old_ranges = IntSet (r01).ranges ();
    int r[num_old_ranges + 1][2];
      
    const int64_t min_value = Int::Limits::min;
    const int64_t max_value = Int::Limits::max;
      
    int count = 0; 

    if (m_block_offset > 0) {
      r[count][0] = min_value;
      r[count][1] = min_value + m_block_offset;
      count++;
    } 

    // Fill in array r by iterating over i0.
    for (Int::ViewRanges<Int::IntView> r02 (x0); r02 (); ++r02) {
      int64_t new_min = r02.min (); new_min += m_block_offset;
      int64_t new_max = r02.max (); new_max += m_block_offset;
      if (new_min > max_value || new_max < min_value) {
        // out of range, add nothing
      } else if (new_max > max_value) {
        r[count][0] = new_min;
        r[count][1] = max_value;
      } else if (new_min < min_value) {
        r[count][0] = min_value;
        r[count][1] = new_max;
      } else {
        // min_value <= new_min <= new_max <= max_value
        r[count][0] = new_min;
        r[count][1] = new_max;
      }
      std::cout << "count = " << count << ", r[count][0] = " << r[count][0] << ", r[count][1] = " << r[count][1] << std::endl;
      count++;
    }

    if (m_block_offset < 0) {
      r[count][0] = max_value + m_block_offset;
      r[count][1] = max_value;
      count++;
    } 
      
    IntSet new_set (r, count);
    IntSetRanges new_set_ranges (new_set);
    
    GECODE_ME_CHECK (x1.inter_r (home, new_set_ranges));
    if (x1.assigned ()) {
      return home.ES_SUBSUMED (*this);
    } else {
      return ES_FIX;
    }
  }
}


ExecStatus Shift::post (Space& home, Int::IntView x0, Int::IntView x1, int64_t block_offset)
{
  (void) new (home) Shift (home, x0, x1, block_offset);
  return ES_OK;
}


void shift (Space& home, IntVar x0, IntVar x1, int64_t block_offset)
{
  Int::IntView x0V (x0);
  Int::IntView x1V (x1);
  if (Shift::post (home, x0V, x1V, block_offset) != ES_OK) {
    home.fail ();
  }
}
