#ifndef BitSet_h
#define BitSet_h

#include <memory>
#include <cstring>

#include "myException.h"


#ifdef _MSVC_LANG
// Visual Studio compiler
#include <intrin.h>
#define __builtin_popcountll __popcnt64

static inline int __builtin_ctz(unsigned x) {
    unsigned long ret;
    _BitScanForward(&ret, x);
    return (int)ret;
}

static inline int __builtin_ctzll(unsigned long long x) {
    unsigned long ret;
    _BitScanForward64(&ret, x);
    return (int)ret;
}
#endif


// ***********************************************
// ***********************************************
// ***********************************************

class BitSet {
private:
    std::uint64_t nblock;
    std::uint64_t nbyte;
    std::uint64_t* store;
    std::uint64_t* bit_at_one_store;
    std::uint64_t bit_at_one;
public:
    BitSet(std::uint64_t totale) {
        nblock = totale / 64 + 1;
        nbyte = sizeof(std::uint64_t) * nblock;
        store = new std::uint64_t[nblock];
        bit_at_one_store = new std::uint64_t[nblock];
        std::memset(store, 0, nbyte);
        std::memset(bit_at_one_store, 0, nbyte);
        bit_at_one = 0;
    }
    
    ~BitSet() {
        delete[] store;
        delete[] bit_at_one_store;
    }
    
    void set(std::uint64_t v) {
        std::uint64_t p = v / 64;
        if (!check(v)) {
            ++bit_at_one;
            ++bit_at_one_store[p];
        }
        std::uint64_t s = v % 64;
        std::uint64_t mask = 1;
        mask = mask << s;
        store[p] = store[p] | mask;
    }
    
    template<typename Iterator>
    void set(Iterator begin, Iterator end) {
        for (Iterator it = begin; it != end; it++) {
            set(*it);
        }
    }
    
    void reset(std::uint64_t v) {
        std::uint64_t p = v / 64;
        if (check(v)) {
            --bit_at_one;
            --bit_at_one_store[p];
        }
        std::uint64_t s = v % 64;
        std::uint64_t mask = 1;
        mask = ~(mask << s);
        store[p] = store[p] & mask;
    }

    bool check(std::uint64_t v) const {
        std::uint64_t p = v / 64;
        std::uint64_t s = v % 64;
        std::uint64_t mask = 1;
        mask = mask << s;
        std::uint64_t r = store[p] & mask;
        return r != 0;
    }
    
    void resetAll() {
        std::memset(store, 0, nbyte);
        std::memset(bit_at_one_store, 0, nbyte);
        bit_at_one = 0;
    }
    
    bool empty() const {
        return bit_at_one == 0;
    }
    
    std::uint64_t count() const {
        return bit_at_one;
    }
    
    std::uint64_t size() const {
        return nbyte * 8;
    }
    
    std::uint64_t get(std::uint64_t n) const {
        if (bit_at_one < n) {
            std::string err_str = "BitSet::get wrong position";
            throw_line(err_str);
        }
        std::uint64_t storep = 0;
        while (bit_at_one_store[storep] < n) {
            n -= bit_at_one_store[storep++];
        }
        std::uint64_t mask = 1;
        std::uint64_t b = store[storep];
        while (n > 0) {
            mask = (~(b) + 1) & b;
            b = b & ~mask;
            --n;
        }
        std::uint64_t position = (std::uint64_t) __builtin_ctzll(mask); //Returns the number of trailing 0-bits in x, starting at the least significant bit position. If x is 0, the result is undefined
        position += 64 * storep;
        return position;
    }
    
    void set_difference(BitSet& m, BitSet& s)  {
        if (nblock != m.nblock || nblock != s.nblock || nbyte != m.nbyte || nbyte != s.nbyte) {
            std::string err_str = "BitSet::difference wrong arguments";
            throw_line(err_str);
        }
        bit_at_one = 0;
        for (std::uint64_t storep = 0; storep < nblock; ++storep) {
            std::uint64_t block_m = m.store[storep];
            std::uint64_t block_s = s.store[storep];
            std::uint64_t block = block_m & (block_m ^ block_s);
            store[storep] = block;
            std::uint64_t quanti_uno = (std::uint64_t) __builtin_popcountll(block); //Returns the number of 1-bits in x
            bit_at_one_store[storep] = quanti_uno;
            bit_at_one += quanti_uno;
        }
    }
    
    std::string to_string() const {
        std::string result = "";
        result += "{";
        std::uint64_t conta = 0;
        for (auto v = cbegin(); v != cend(); v++) {
            if ((++conta) < this->count()) {
                result += std::to_string(*v) + " ";
            } else {
                result += std::to_string(*v);
            }
        }
        result += "}";
        return result;
    }
    
    struct Iterator
    {
        using iterator_category = std::forward_iterator_tag;
        using difference_type   = std::ptrdiff_t;
        using value_type        = std::uint64_t;
        using pointer           = std::uint64_t*;  // or also value_type*
        using reference         = std::uint64_t;  // or also value_type&

        Iterator(std::uint64_t si, std::uint64_t b, std::uint64_t m, std::uint64_t nb, std::uint64_t* st, std::uint64_t* bone)
            : storep(si), block(b), mask(m), nblock(nb), store(st), bit_at_one_store(bone) {}
        Iterator(const Iterator& m)
            : storep(m.storep), block(m.block), mask(m.mask), nblock(m.nblock), store(m.store), bit_at_one_store(m.bit_at_one_store) {}

        
        
        reference operator*() const {
            std::uint64_t position = (std::uint64_t) __builtin_ctzll(mask); //Returns the number of trailing 0-bits in x, starting at the least significant bit position. If x is 0, the result is undefined
            position += 64 * storep;
            return position;
            
        }

        // Prefix increment
        Iterator& operator++() {
            if (block != 0) {
                mask = (~(block) + 1) & block;
                block = block & ~mask;
            } else {
                ++storep;
                while (storep < nblock && !bit_at_one_store[storep]) {
                    ++storep;
                }
                if (storep < nblock) {
                    mask = 1;
                    block = store[storep];
                    mask = (~(block) + 1) & block;
                    block = block & ~mask;
                } else {
                    storep = 0;
                    block = 0;
                    mask = 0;
                    nblock = 0;
                    store = nullptr;
                    bit_at_one_store = nullptr;
                }
            }
            return *this;
        }

        // Postfix increment
        Iterator operator++(int) { Iterator tmp = *this; ++(*this); return tmp; }

        friend bool operator== (const Iterator& a, const Iterator& b) {
            return (a.storep == b.storep &&
                    a.block == b.block &&
                    a.mask == b.mask &&
                    a.store == b.store);
        };
        friend bool operator!= (const Iterator& a, const Iterator& b) {
            return !(a.storep == b.storep && a.block == b.block && a.mask == b.mask && a.store == b.store);
        };
    private:
        std::uint64_t storep;
        std::uint64_t block;
        std::uint64_t mask;

        std::uint64_t nblock;
        std::uint64_t* store;
        std::uint64_t* bit_at_one_store;
    };
    
    
    typedef Iterator iterator;
    typedef Iterator const_iterator;
    
    iterator begin() {
        std::uint64_t storep = 0;
        while (!bit_at_one_store[storep]) {
            ++storep;
        }
        if (storep < nblock) {
            std::uint64_t mask = 1;
            std::uint64_t block = store[storep];
            mask = (~(block) + 1) & block;
            block = block & ~mask;
            return iterator(storep, block, mask, nblock, store, bit_at_one_store);
            
        }
        return iterator(0, 0, 0, 0, nullptr, nullptr);
    }
    
    iterator end() {
        return iterator(0, 0, 0, 0, nullptr, nullptr);
    }
    
    const_iterator cbegin() const {
        std::uint64_t storep = 0;
        while (!bit_at_one_store[storep]) {
            ++storep;
        }
        if (storep < nblock) {
            std::uint64_t mask = 1;
            std::uint64_t block = store[storep];
            mask = (~(block) + 1) & block;
            block = block & ~mask;
            return const_iterator(storep, block, mask, nblock, store, bit_at_one_store);
            
        }
        return const_iterator(0, 0, 0, 0, nullptr, nullptr);
    }
    
    const_iterator cend() const {
        return const_iterator(0, 0, 0, 0, nullptr, nullptr);
    }
};


// ***********************************************
// ***********************************************
// ***********************************************

#endif /* BitSet_h */
