#if !defined HAVE_BINARY_SL_GRAY_H__
#define      HAVE_BINARY_SL_GRAY_H__
// This file is part of the FXT library.
// Copyright (C) 2012, 2013, 2014, 2015, 2019, 2024 Joerg Arndt
// License: GNU General Public License version 3 or later,
// see the file COPYING.txt in the main directory.

#include "comb/comb-print.h"
#include "bits/bin-to-sl-gray.h"  // sl_gray_to_bin()

#include "fxttypes.h"


// Cf. bits/bit-sl-gray.h for generation in a binary word.
// Cf. comb/subset-lex.h for subset-lex order.
// Cf. comb/mixedradix-subset-lex.h for subset-lex order for multisets.


class binary_sl_gray
// Binary numbers in a minimal-change order
// related so subset-lex order ("SL-Gray" order).
// Representation as delta-sets (binary words), in A[].
// Loopless generation, only O(1) data beyond the array of bits.
// Successive transitions are mostly adjacent,
//   and otherwise have distance 3.
// Special case of class mixed_radix_sl_gray for base 2.
// Cf. OEIS sequence A217262.
// See Joerg Arndt, Subset-lex: did we miss an order?, (2014)
//   http://arxiv.org/abs/1405.6503
{
protected:
    ulong n;   // number of digits
    ulong t;   // aux: current track (0 <= t <= n)
    ulong d;   // aux: direction in which track tries to move
    ulong *A;  // digits

    ulong j;   // position of last change; returned by pos()
    int dm;    // direction of last change; returned by dir()

    binary_sl_gray(const binary_sl_gray&) = delete;
    binary_sl_gray & operator = (const binary_sl_gray&) = delete;

public:
    explicit binary_sl_gray(ulong tn)
    {
        n = tn;
        A = new ulong[n+2];  // sentinels at both ends

        A[n+1] = +1;  // != 0
        A[0] = +1;

        ++A;  // nota bene

        first();
    }

    ~binary_sl_gray()
    {
        --A;
        delete [] A;
    }

    const ulong * data()  const  { return A; }
    ulong pos()  const  { return j; }  // position of last change
    int dir()  const  { return dm; }   // direction of last change
    // internal data:
    ulong track()  const  { return t; }   // current track
    ulong track_dir()  const  { return d; }   // direction of current track


    void first()
    {
        for (ulong k=0; k<n; ++k)  A[k] = 0;
        t = 0;
        d = +1;

        j = ( n>=2 ? 1 : 0);  // wrt. last word
        dm = -1;  // wrt. last word
    }

    void last()
    {
        for (ulong k=0; k<n; ++k)  A[k] = 0;

        j = ( n>=2 ? 1 : 0);  // wrt. first word
        dm = +1;  // wrt. first word

        if ( n >= 2 )
        {
            A[1]  = 1;
            t = 1;
            d = -1UL;
        }
        else
        {
            A[0]  = n;  // zero for the empty set (for stopping condition)
            t = 0;
            d = +1;
        }
    }


    bool next()
    // Generate successor.
    // Return false if current was last.
    // Loopless algorithm.
    {
        if ( d == +1 )  // try to append trailing ones
        {
            if ( A[t] == 0 )  // can append  // may read sentinel a[n]
            {
                A[t] = 1;
                j = t;  // record position of change
                dm = +1;  // record direction of change
                ++t;
            }
            else
            {
                d = -1UL;  // change direction
                t = n - 1;
                j = t - 1;  // record position of change
                if ( j > n )  return false;  // current is last (only for n <= 1)
                A[j] = 1 - A[j];
                dm = ( A[j] ? +1 : -1 );  // record direction of change
            }
        }
        else  // d == -1  // try to remove trailing ones
        {
            if ( A[t-1] != 0 )   // can remove  // t - 1 >= 0
            {
                A[t] = 0;
                j = t;  // record position of change
                dm = -1;  // record direction of change
                --t;
            }
            else
            {
                d = +1;  // change direction
                j = t - 2;  // record position of change
                if ( (long)j < 0 )  return false;  // current is last
                A[j] = 1 - A[j];
                dm = ( A[j] ? +1 : -1 );  // record direction of change
                ++t;
            }
        }

        return true;
    }

    bool prev()
    // Generate predecessor.
    // Return false if current was first.
    // Loopless algorithm.
    {
        if ( d != +1 )  // d==-1  // try to append trailing ones
        {
            if ( A[t+1] == 0 )  // can append  // may read sentinel a[n]
            {
                A[t+1] = 1;
                j = t+1;  // record position of change
                dm = +1;  // record direction of change
                ++t;
            }
            else
            {
                d = +1;  // change direction
                t = n - 1;
                j = t - 1;  // record position of change
                A[j] = 1 - A[j];
                dm = ( A[j] ? +1 : -1 );  // record direction of change
                ++t;
            }
        }
        else  // d == +1  // try to remove trailing ones
        {
            if ( t == 0 )
            {
                if ( A[0]==0 )  return false;  // (only for n <= 1)
                A[0] = 0;
                return true;
            }

            // t - 1 >= -1 (can read low sentinel)
            if ( A[t-2] != 0 )  // can remove
            {
                A[t-1] = 0;
                j = t - 1;  // record position of change
                dm = -1;  // record direction of change
                --t;
            }
            else
            {
                d = -1UL;  // change direction
                j = t - 3;  // record position of change
                A[j] = 1 - A[j];
                dm = ( A[j] ? +1 : -1 );  // record direction of change
                --t;
            }
        }

        return true;
    }


    void unrank(ulong r)
    {
        ulong w = bin_to_sl_gray(r, n);
        ulong m = 1UL << (n-1);;
        for (ulong k=0;  k<n;  ++k, m>>=1)
        {
            bool q =  ( w & m );
            A[k] = q;
        }
    }

    ulong rank()  const
    {
        ulong m = 1UL << (n-1);;
        ulong w = 0;
        for (ulong k=0;  k<n;  ++k, m>>=1)
            if ( A[k]  != 0 )  w |= m;
        return  sl_gray_to_bin(w, n);
    }

    void print(const char *bla, bool dfz=false)  const
    // If dfz is true then Dots are printed For Zeros.
    { print_mixedradix(bla, data(), n, dfz); }
};
// -------------------------



#endif  // !defined HAVE_BINARY_SL_GRAY_H__
