/* Copyright (c) 2018 -- 2022
   Charles Jordan (Hokkaido University, Sapporo, Japan),
   Michael Joswig, Lars Kastner (Technische Universitaet Berlin, Germany)

   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 3, or (at your option) any
   later version: http://www.gnu.org/licenses/gpl.txt.

   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.
-----------------------------------------------------------------------------
*/

#ifndef LONGBITSET_HH
#define LONGBITSET_HH

#include <polymake/Main.h>
#include <polymake/Vector.h>
#include <polymake/Bitset.h>
#include <polymake/GenericSet.h>
#include <bitset>
#include "Global.hh"
#include "PlainArray.hh"

using Int=pm::Int;

class LongBitset;
typedef unsigned long ENC;
#define ENC_SIZE 64

class LongBitset_iterator {
public:
   using iterator_category = pm::forward_iterator_tag;
   using value_type = Int;
   using reference = Int;
   using pointer = const Int*;
   using difference_type = ptrdiff_t;
   using iterator = LongBitset_iterator;
   using const_iterator = LongBitset_iterator;

   friend class LongBitset;
   protected:
      ENC encodingcopy;
      Int cur;
   public:
      LongBitset_iterator(const ENC& enc): 
         encodingcopy(enc), cur(encodingcopy == 0 ? 0 : __builtin_ctzl(enc))
         {
            encodingcopy >>= cur;
         };
      LongBitset_iterator(const ENC& enc, Int cur_pos):
         encodingcopy(enc), cur(cur_pos) {
            // std::cout << "Enc: " << std::bitset<ENC_SIZE>(enc);
            // std::cout << " cp: " << cur_pos << std::endl;
         };


         
      reference operator* () const { return cur; }
      pointer operator-> () const { return &cur; }

      LongBitset_iterator& operator++ () {
         Int zeros = 1;
         if (encodingcopy ^ 1UL)
            zeros += __builtin_ctzl(encodingcopy >> 1);
         encodingcopy >>= zeros;
         cur += zeros;
         return *this;
      }

      bool at_end() const {
         return 0UL == encodingcopy;
      }

      bool operator== (const LongBitset_iterator& sisi) const { 
         // std::cout << "cur: " << cur << " sisi.cur: " << sisi.cur << std::endl;
         return cur==sisi.cur; }
      bool operator!= (const LongBitset_iterator& sisi) const { return !operator==(sisi); }

};

class LongBitset : public pm::GenericSet<LongBitset, Int, pm::operations::cmp> {
   public:
      using value_type = Int;
      using const_reference = const Int;
      using reference = const_reference;
      pm::operations::cmp get_comparator() const { return pm::operations::cmp(); }

   private:
      ENC encoding;
   public:
      LongBitset():encoding(0UL){}
      LongBitset(const LongBitset& sis):
         encoding(sis.encoding){}


      LongBitset(const pm::Vector<Int>& v){
         encoding=0UL;
         for(const auto& i: v){
            set_ith_bit_to_one(i);
         }
      }

      LongBitset(const pm::Set<Int>& pms){
         encoding=0UL;
         for(const auto& i: pms){
            set_ith_bit_to_one(i);
         }
      }

      using iterator = LongBitset_iterator;
      using const_iterator = LongBitset_iterator;


      void set_ith_bit_to_zero(const Int i){
         encoding &= ~(1UL << i);
      }

      void set_ith_bit_to_one(const Int i){
         encoding |= 1UL << i;
      }

      LongBitset operator+ (Int i) const{ 
         LongBitset result(*this);
         result.set_ith_bit_to_one(i); 
         return result;
      }
      LongBitset operator- (Int i) const{ 
         LongBitset result(*this);
         result.set_ith_bit_to_zero(i); 
         return result;
      }
      LongBitset& operator*= (const LongBitset& sis){
         encoding &= sis.encoding;
         return *this;
      }
      LongBitset& operator+= (const LongBitset& sis){
         encoding |= sis.encoding;
         return *this;
      }
      LongBitset& operator+= (const Int i){ 
         set_ith_bit_to_one(i);
         return *this;
      }
      LongBitset operator* (const LongBitset& sis) const{
         LongBitset result(*this);
         result *= sis;
         return result;
      }

      bool operator< (const LongBitset& sis) const { return encoding < sis.encoding;}
      bool operator<= (const LongBitset& sis) const { return encoding <= sis.encoding;}
      bool operator> (const LongBitset& sis) const { return encoding > sis.encoding;}
      bool operator>= (const LongBitset& sis) const { return encoding >= sis.encoding;}

      bool empty() const { return encoding == 0;}
      Int back() const {return encoding==0 ? -1 : (ENC_SIZE - __builtin_clzl(encoding)-1);} // FIXME
      Int front() const {return __builtin_ctzl(encoding); }
      iterator begin() const { 
         // std::cout << "Construct iterator: ";
         // print();
         // std::cout << "Size: " << size() << " back: " << back() << std::endl;
         // std::cout << "Begin " << std::bitset<64>(encoding) << std::endl;
         return iterator(encoding); 
      }
      iterator end() const { 
         // std::cout << "End " << std::bitset<64>(encoding) << std::endl;
         return iterator(0UL, empty() ? 0 : back()+1);
      }
      void push_back(const Int& i){ set_ith_bit_to_one(i); }
      void pop_back(){
         if (!empty())
            set_ith_bit_to_zero(back());
      }
      Int size() const {
         // std::cout << "size" << std::endl;
         return __builtin_popcountl(encoding);
      }

      operator pm::Vector<Int>() const {
         pm::Vector<Int> result(size());
         Int j = 0;
         for(const auto& i: *this){
            result[j]=i;
            j++;
         }
         return result;
      }

      void print() const {
         std::cout << "Simplex: " << size() << " - ";
         std::cout << std::bitset<ENC_SIZE>(encoding);
         // for(const auto& i: *this){
         //    std::cout << i << " ";
         // }
         std::cout << std::endl;
      }

      const unsigned long get_rep() const{
         return encoding;
      }

      ///////////////////////////////////////////////////////////////////////////////
      // Stuff that TOPCOM needs:
      LongBitset& operator-=(const size_type i) { // delete integer
         set_ith_bit_to_zero(i);
         return *this;
      }
      LongBitset& operator-=(const LongBitset& lb) {
         encoding &= ~lb.encoding;
         return *this;
      }
      bool operator== (const LongBitset& lb) const { 
         return encoding == lb.encoding;
      }
      bool operator!= (const LongBitset& lb) const { 
         return encoding != lb.encoding;
      }
      LongBitset& operator^=(const size_type i){ 
         encoding ^= (1UL << i);
         return *this;
      }
      LongBitset& operator^=(const LongBitset& lb){
         encoding ^= lb.encoding;
         return *this;
      }
      LongBitset& clear(){	
         encoding = 0UL;
         return *this;
      }
      const bool contains(const size_type i) const{
         return (encoding & (1UL << i)) != 0UL;
      }
      
};

namespace pm {

/*
template <>
struct spec_object_traits<LongBitset> : spec_object_traits<is_container> {
   using generic_tag = is_set;
};
*/
template <>
struct hash_func<LongBitset, is_set> : hash_func<long> {
   size_t operator() (const LongBitset& s) const
   {
      return s.get_rep();
   }
};

}

template<typename pmSet>
class pmSet4Topcom : public pmSet {

   private:
//      pmSet base;

   public:

      // constructors
      pmSet4Topcom() : pmSet() {}
      pmSet4Topcom(const PlainArray<size_type>& pa) : pmSet() { // constructor with given PlainArray of integers
         for(Int i=0; i<pa.maxindex(); i++){
            pmSet::operator+=(pa[i]);
         }
      }
      pmSet4Topcom(const unsigned long& l): pmSet(){
         pmSet::operator+=(l);
      }
      pmSet4Topcom(const size_type a, const size_type b): pmSet(pm::sequence(a,b)){ }

      template <typename SetType>
      pmSet4Topcom(const pm::GenericSet<SetType>& set) : pmSet(set) { }

      // iterators
      using iterator = typename pmSet::const_iterator;
//      using const_iterator = typename pmSet::const_iterator;
/*
      iterator begin() const { 
         return base.begin();
      }
      iterator end() const { 
         return base.end();
      }
*/
      // operators
      pmSet4Topcom& operator+= (const pmSet4Topcom& other){
         pmSet::operator+=(other);
         return *this;
      }

/*      pmSet4Topcom& operator-=(const size_type i) { // delete integer
         return *this -= i;
      }
*/
      pmSet4Topcom& operator-=(const pmSet4Topcom& other) {
         pmSet::operator-=(other);
         return *this;
      }

      template<typename Subtractor>
         pmSet4Topcom operator-(const Subtractor& s) const{
            pmSet4Topcom result(*this);
            return result -= s;
         }
      template<typename Summand>
         pmSet4Topcom operator+(const Summand& s) const{
            pmSet4Topcom result(*this);
            return result += s;
         }
/*
//      bool operator< (const pmSet4Topcom& sis) const { return base < sis.base;}
//      bool operator> (const pmSet4Topcom& sis) const { return base > sis.base;}
      bool operator== (const pmSet4Topcom& lb) const { 
         return base == lb.base;
      }
      bool operator!= (const pmSet4Topcom& lb) const { 
         return base != lb.base;
      }
      pmSet4Topcom& operator^=(const size_type i){ // add if not contained/delete if contained
         base ^= i;
         return *this;
      }
      pmSet4Topcom& operator^=(const pmSet4Topcom& lb){ // add if not contained/delete if contained
         base ^= lb.base;
         return *this;
      }
      pmSet4Topcom& operator*= (const pmSet4Topcom& sis){
         base *= sis.base;
         return *this;
      }


      // methods
      const bool contains(const size_type i) const{
         return base.contains(i);
      }
*/
      const size_type card() const{
         return static_cast<const pmSet*>(this)->size();
      }
      const size_type size() const{
         return 1;
      }
      const bool superset(const pmSet4Topcom& lb) const { // superset relation
         // return ((~base.encoding) & lb.base.encoding) == 0UL;
         //pmSet4Topcom tmp(*this * lb);
         return pm::incl(lb,*this) < 1;
      }
      const bool subset(const pmSet4Topcom& lb) const { // superset relation
         return lb.superset(*this);
      }


      bool is_empty() const {
         return this->empty();
      }


      const unsigned long     key(size_type n) const{
         return pm::hash_func<pmSet>()(*this);
      }


      const size_type keysize()        const {
         return 1;
      }
      const size_type invariant()      const {
         return pm::hash_func<pmSet>()(*this);
      }
/*      pmSet4Topcom& clear(){			// erase all
         base.clear();
         return *this;
      }
*/
      const size_type intersection_card(const pmSet4Topcom** setarray,
            const size_type size,
            size_type&
            common_index) const {
         if (is_empty()) {
            return 0;
         }
         if (size == 0) {
            typename pmSet4Topcom::iterator iter = this->begin();
            common_index = *iter;
            if (++iter == this->end()) {
               return 1;
            }
            else {
               return 2;
            }
         }

         pmSet4Topcom tmp(*this);
         // get minimal _no_of_blocks:
         for (size_type j = 0; j < size; ++j) {
            tmp *= *(setarray[j]);
            if(tmp.is_empty()){
               return 0;
            }
         }
         return std::min(tmp.card(),(size_type) 2);
      }

      // as a special case of the previous function:
      // 0 means 0 elements, 1 means at least 1 element:
      const size_type intersection_nonempty(const pmSet4Topcom** setarray,
            const size_type size,
            size_type&
            common_index) const {
         if (is_empty()) {
            return 0;
         }
         if (size == 0) {
            return 1;
         }
         pmSet4Topcom tmp(*this);
         // get minimal _no_of_blocks:
         for (size_type j = 0; j < size; ++j) {
            tmp *= *(setarray[j]);
            if(tmp.is_empty()){
               return 0;
            }
         }
         return tmp.is_empty() ? 0 : 1;
      }


      // iostream:
      friend std::istream& operator>>(std::istream& ist, pmSet4Topcom& is) {
         return is.read(ist);
      }
      friend std::ostream& operator<<(std::ostream& ost, const pmSet4Topcom& is) {
         return is.write(ost);
      }
      // stream output:
      std::ostream& write(std::ostream& ost) const {
         size_type count = 0;

         ost << "{";
         for (const auto& i: *this){
            if (count)
               ost << ",";
            ost << i;
            count++;
         }
         ost << "}";
         return ost;
      }

      // stream input:
      std::istream& read(std::istream& ist) {
         char c;
         size_type elem;

         this->clear();
         ist >> std::ws >> c;
         if (c == '{') {
            while (ist >> std::ws >> c) {
               if (isspace(c)) {
                  continue;
               }
               if (c == '}') {
                  break;
               }
               if (c == ',') {
                  continue;
               }
               if (isdigit(c)) {
                  ist.putback(c);
                  ist >> elem;
                  *this += elem;
               }
               else {
                  ist.clear(std::ios::failbit);
                  return ist;
               }
            }
         }
         else {
            ist.clear(std::ios::failbit);
            return ist;
         }
         ist.clear(std::ios::goodbit);
         return ist;
      }


};


class pmBitsetWrap : public pm::Bitset {

   public:
      // constructors
      pmBitsetWrap() : pm::Bitset() {}
      pmBitsetWrap(const PlainArray<size_type>& pa) : pm::Bitset() { // constructor with given PlainArray of integers
         for(Int i=0; i<pa.maxindex(); i++){
            pm::Bitset::operator+=(pa[i]);
         }
      }
      pmBitsetWrap(const unsigned long& l): pm::Bitset(){
         pm::Bitset::operator+=(l);
      }
      pmBitsetWrap(const size_type a, const size_type b): pm::Bitset(pm::sequence(a,b)){ }

      template <typename SetType>
      pmBitsetWrap(const pm::GenericSet<SetType>& set) : pm::Bitset(set) { }

      bool operator<(const Bitset& other) const {
         return mpz_cmp(get_rep(), other.get_rep()) == -1;
      }
      bool operator<=(const Bitset& other) const {
         return mpz_cmp(get_rep(), other.get_rep()) <= 0;
      }
      bool operator>(const Bitset& other) const {
         return mpz_cmp(get_rep(), other.get_rep()) == 1;
      }
      bool operator>=(const Bitset& other) const {
         return mpz_cmp(get_rep(), other.get_rep()) >= 0;
      }
};

#endif
