/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
 *  Main authors:
 *     Guido Tack <tack@gecode.org>
 *     Gabor Szokoli <szokoli@gecode.org>
 *     Christian Schulte <schulte@gecode.org>
 *
 *  Copyright:
 *     Guido Tack, 2004
 *     Christian Schulte, 2004
 *     Gabor Szokoli, 2004
 *
 *  This file is part of Gecode, the generic constraint
 *  development environment:
 *     http://www.gecode.org
 *
 *  Permission is hereby granted, free of charge, to any person obtaining
 *  a copy of this software and associated documentation files (the
 *  "Software"), to deal in the Software without restriction, including
 *  without limitation the rights to use, copy, modify, merge, publish,
 *  distribute, sublicense, and/or sell copies of the Software, and to
 *  permit persons to whom the Software is furnished to do so, subject to
 *  the following conditions:
 *
 *  The above copyright notice and this permission notice shall be
 *  included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

namespace Gecode { namespace Set {

  /*
   * BndSet
   *
   */

  forceinline
  BndSet::BndSet(void) :
    first(nullptr), last(nullptr), _size(0), _card(0) {}

  forceinline RangeList*
  BndSet::fst(void) const {
    return first;
  }

  forceinline RangeList*
  BndSet::lst(void) const {
    return last;
  }

  forceinline void
  BndSet::dispose(Space& home) {
    if (fst()!=nullptr)
      fst()->dispose(home,lst());
  }

  forceinline void
  BndSet::fst(RangeList* f) {
    first = f;
  }

  forceinline void
  BndSet::lst(RangeList* l) {
    last = l;
  }

  forceinline
  BndSet::BndSet(Space& home, int mn, int mx) {
    if (mn>mx) {
      fst(nullptr); lst(nullptr); _size = 0;
    } else {
      RangeList* p =
        new (home) RangeList(mn,mx,nullptr);
      fst(p); lst(p);
      _size = static_cast<unsigned int>(mx-mn+1);
    }
  }

  forceinline RangeList*
  BndSet::ranges(void) const {
    return fst();
  }

  forceinline unsigned int
  BndSet::size(void) const {
    return _size;
  }

  forceinline bool
  BndSet::empty(void) const {
    return (_size==0);
  }

  forceinline int
  BndSet::min(void) const {
    if (fst()==nullptr)
      return MIN_OF_EMPTY;
    else
      return fst()->min();
  }

  forceinline int
  BndSet::max(void) const {
    if (lst()==nullptr)
      return MAX_OF_EMPTY;
    else
      return lst()->max();
  }

  // nth smallest element
  forceinline int
  BndSet::minN(unsigned int n) const {
    for (RangeList* c = fst(); c != nullptr; c = c->next()) {
      if (c->width() > n)
        return static_cast<int>(c->min() + n);
      n -= c->width();
    }
    return MIN_OF_EMPTY;
  }

  forceinline unsigned int
  BndSet::card(void) const {
    return _card;
  }

  forceinline void
  BndSet::card(unsigned int c) {
    _card = c;
  }

  forceinline void
  BndSet::update(Space& home, BndSet& d) {
    if (d.fst() == fst())
      return;
    if (fst() != nullptr)
      fst()->dispose(home,lst());
    _size = d.size();
    if (_size == 0) {
      fst(nullptr); lst(nullptr);
      return;
    }

    int n=0;
    for (RangeList* c = d.fst(); c != nullptr; c = c->next())
      n++;

    RangeList* r = home.alloc<RangeList>(n);
    fst(r); lst(r+n-1);

    {
      RangeList* c = d.fst();
      for (int i=0; i<n; i++) {
        r[i].min(c->min());
        r[i].max(c->max());
        r[i].next(&r[i+1]);
        c = c->next();
      }
    }
    r[n-1].next(nullptr);
  }

  template<class I> forceinline bool
  BndSet::overwrite(Space& home, I& ri) {
    // Is new domain empty?
    if (!ri()) {
      //Was it empty?
      if (fst()==nullptr)
        return false;
      fst()->dispose(home,lst());
      _size=0; fst(nullptr); lst(nullptr);
      return true;
    }

    RangeList* f =
      new (home) RangeList(ri.min(),ri.max(),nullptr);
    RangeList* l = f;
    unsigned int s = ri.width();

    ++ri;

    while (ri()) {
      RangeList *n = new (home) RangeList(ri.min(),ri.max(),nullptr);
      l->next(n);
      l=n;
      s += ri.width();
      ++ri;
    }

    if (fst() != nullptr)
      fst()->dispose(home,lst());
    fst(f); lst(l);

    // If the size did not change, nothing changed, as overwriting
    // must not at the same time include and exclude elements.
    if (size() == s)
      return false;

    _size = s;
    return true;
  }

  forceinline void
  BndSet::become(Space& home, const BndSet& that) {
    if (fst()!=nullptr) {
      assert(lst()!=nullptr);
      assert(fst()!= that.fst());
      fst()->dispose(home,lst());
    }
    fst(that.fst());
    lst(that.lst());
    _size = that.size();
    assert(isConsistent());
  }

  forceinline bool
  BndSet::in(int i) const {
    for (RangeList* c = fst(); c != nullptr; c = c->next()) {
      if (c->min() <= i && c->max() >= i)
        return true;
      if (c->min() > i)
        return false;
    }
    return false;
  }

  /*
   * Range iterator for BndSets
   *
   */

  forceinline
  BndSetRanges::BndSetRanges(void) {}

  forceinline
  BndSetRanges::BndSetRanges(const BndSet& s)
    : Iter::Ranges::RangeList(s.ranges()) {}

  forceinline void
  BndSetRanges::init(const BndSet& s) {
    Iter::Ranges::RangeList::init(s.ranges());
  }

  /*
   * GLBndSet
   *
   */

  forceinline
  GLBndSet::GLBndSet(void) {}

  forceinline
  GLBndSet::GLBndSet(Space&) {}

  forceinline
  GLBndSet::GLBndSet(Space& home, int mi, int ma)
    : BndSet(home,mi,ma) {}

  forceinline
  GLBndSet::GLBndSet(Space& home, const IntSet& s)
    : BndSet(home,s) {}

  forceinline void
  GLBndSet::init(Space& home) {
    dispose(home);
    fst(nullptr);
    lst(nullptr);
    _size = 0;
  }

  forceinline bool
  GLBndSet::include(Space& home, int mi, int ma, SetDelta& d) {
    assert(ma >= mi);
    if (fst()==nullptr) {
      RangeList* p = new (home) RangeList(mi,ma,nullptr);
      fst(p);
      lst(p);
      _size=static_cast<unsigned int>(ma-mi+1);
      d._glbMin = mi;
      d._glbMax = ma;
      return true;
    }
    bool ret = include_full(home, mi, ma, d);
    assert(isConsistent());
    return ret;
  }

  template<class I> bool
  GLBndSet::includeI(Space& home, I& i) {
    if (!i())
      return false;
    BndSetRanges j(*this);
    Iter::Ranges::Union<BndSetRanges,I> ij(j,i);
    bool me = overwrite(home, ij);
    assert(isConsistent());
    return me;
  }


  /*
   * LUBndSet
   *
   */

  forceinline
  LUBndSet::LUBndSet(void) {}

  forceinline
  LUBndSet::LUBndSet(Space& home)
    : BndSet(home,Limits::min,Limits::max) {}

  forceinline
  LUBndSet::LUBndSet(Space& home, int mi, int ma)
    : BndSet(home,mi,ma) {}

  forceinline
  LUBndSet::LUBndSet(Space& home, const IntSet& s)
    : BndSet(home,s) {}

  forceinline void
  LUBndSet::init(Space& home) {
    RangeList *p =
      new (home) RangeList(Limits::min,
                           Limits::max,
                           nullptr);
    fst(p);
    lst(p);
    _size = Limits::card;
  }

  forceinline bool
  LUBndSet::exclude(Space& home, int mi, int ma, SetDelta& d) {
    assert(ma >= mi);
    if ((mi > max()) || (ma < min())) { return false; }
    if (mi <= min() && ma >= max() ) { //the range covers the whole set
      d._lubMin = min();
      d._lubMax = max();
      fst()->dispose(home,lst()); fst(nullptr); lst(nullptr);
      _size=0;
      return true;
    }
    bool ret =  exclude_full(home, mi, ma, d);
    assert(isConsistent());
    return ret;
  }

  forceinline bool
  LUBndSet::intersect(Space& home, int mi, int ma) {
    assert(ma >= mi);
    if ((mi <= min()) && (ma >= max())) { return false; }
    if (_size == 0) return false;
    if (ma < min() || mi > max() ) { // empty the whole set
     fst()->dispose(home,lst()); fst(nullptr); lst(nullptr);
     _size=0;
     return true;
    }
    bool ret = intersect_full(home, mi, ma);
    assert(isConsistent());
    return ret;
  }

  template<class I> bool
  LUBndSet::intersectI(Space& home, I& i) {
    if (fst()==nullptr) { return false; }
    if (!i()) {
      fst()->dispose(home,lst()); fst(nullptr); lst(nullptr);
      _size=0;
      return true;
    }
    BndSetRanges j(*this);
    Iter::Ranges::Inter<BndSetRanges,I> ij(j,i);
    bool ret = overwrite(home, ij);
    assert(isConsistent());
    return ret;
  }

  template<class I> bool
  LUBndSet::excludeI(Space& home, I& i) {
    if (!i()) { return false; }
    BndSetRanges j(*this);
    Iter::Ranges::Diff<BndSetRanges,I> ij(j,i);
    bool ret = overwrite(home, ij);
    assert(isConsistent());
    return ret;
  }

  forceinline void
  LUBndSet::excludeAll(Space& home) {
    fst()->dispose(home,lst()); fst(nullptr); lst(nullptr);
    _size=0;
  }

  /*
   * A complement iterator spezialized for the BndSet limits
   *
   */
  template<class I>
  RangesCompl<I>::RangesCompl(void) {}

  template<class I>
  RangesCompl<I>::RangesCompl(I& i)
    : Iter::Ranges::Compl<Limits::min,
                          Limits::max,
                          I>(i) {}

  template<class I> void
  RangesCompl<I>::init(I& i) {
    Iter::Ranges::Compl<Limits::min,
      Limits::max,I>::init(i);
  }

}}

// STATISTICS: set-var

