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

#include "comb/is-setpart-rgs.h"
#include "comb/is-noncrossing-setpart-rgs.h"
#include "comb/comb-print.h"

#include "fxttypes.h"
#include "jjassert.h"


#define SETPART_NONCROSSING_RGS_OPT // speedup by a factor of 4

class setpart_noncrossing_rgs
// Noncrossing set partitions of the n-set as restricted growth strings (RGS).
// Lexicographic order.
{
private:
    ulong n;   // Number of elements of set (set = {1,2,3,...,n})
    ulong *S;  // RGS
    ulong *M;  // m[k] = max(s[0], s[1], ..., s[k-1]) + 1 where s = rgs
    ulong *V;  // V[k] != 0 if digit k forbidden

    setpart_noncrossing_rgs(const setpart_noncrossing_rgs&) = delete;
    setpart_noncrossing_rgs & operator = (const setpart_noncrossing_rgs&) = delete;
public:
    explicit setpart_noncrossing_rgs(ulong tn)
    {
        n = tn;
        M = new ulong[n+1];
        M[0] = 1;    // sentinel:  m[0] = 1
        S = new ulong[n+1];
        S[n] = n+1;    // sentinel
        V = new ulong[n+1];
        first();
    }

    ~setpart_noncrossing_rgs()
    {
        delete [] S;
        delete [] M;
        delete [] V;
    }

    const ulong* data()  const  { return S; }
    ulong num_sets()  const  { return ( n ? M[n] : 0 ); }

    void first()
    {
        for (ulong k=0; k<n; ++k)  S[k] = 0;
        for (ulong k=1; k<=n; ++k)  M[k] = 1;
        for (ulong k=0; k<=n; ++k)  V[k] = 0;
    }

private:
    void may_forbid( ulong j )
    {
        const ulong v0 = S[j];
        const ulong v1 = S[j-1];
        // with down step, forbid values v0+1 ... v1
        for (ulong t=v0+1; t<=v1; ++t)  { V[t] += 1; }
    }
    void may_allow( ulong j )
    {
        const ulong v0 = S[j];
        const ulong v1 = S[j-1];
        // with down step, re-allow values v0+1 ... v1
        for (ulong t=v0+1; t<=v1; ++t)  { V[t] -= 1; }
    }

#if defined SETPART_NONCROSSING_RGS_OPT
public:
    bool next()
    {
        if ( M[n] >= n )  return false;

        ulong k = n;
        do
        {
            --k;
            may_allow( k );
        }
        while ( S[k] + 1 > M[k] );  // may read sentinel
//        jjassert( k != 0 );

//        setup_V( k );  // expensive

        do  { S[k] += 1; }  while ( V[ S[k] ] != 0 );
        may_forbid( k );

        ulong mm = M[k];
        mm += ( S[k] >= mm );
        M[k+1] = mm;  // == max2(M[k], S[k]+1)

        for ( ulong j = k+1; j < n; ++j )  // fill tail with zeros
        {
            S[j] = 0;
            M[j+1] = mm;
        }
        may_forbid( k+1 );

        return true;
    }
#else  // SETPART_NONCROSSING_RGS_OPT
private:
    void setup_V( ulong k )
    // Expensive
    {
        // Complexity is O(k^2), but appears to really be O(k*log_2(k))
        for (ulong j=0; j<n; ++j)  { V[j] = 0; }
        ulong j = 1;
        do  { may_forbid( j ); }  while ( ++j < k );
    }

public:
    bool next()
    {
        if ( M[n] >= n )  return false;

        ulong k = n;
        do  { --k; }  while ( S[k] + 1 > M[k] );  // may read sentinel
//        jjassert( k != 0 );

        setup_V( k );  // expensive

        do  { S[k] += 1; }  while ( V[ S[k] ] != 0 );

        ulong mm = M[k];
        mm += ( S[k] >= mm );
        M[k+1] = mm;  // == max2(M[k], S[k]+1)

        for ( ulong j = k+1; j < n; ++j )  // fill tail with zeros
        {
            S[j] = 0;
            M[j+1] = mm;
        }

        return true;
    }
#endif  // SETPART_NONCROSSING_RGS_OPT

    void print_V(const char *bla, bool dfz=true)  const  // debug
    { print_vec(bla, V, n+0, dfz); }

    void print_M(const char *bla, bool dfz=true)  const  // debug
    { print_vec(bla, M, n+0, dfz); }

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

    void print_sets(const char *bla, ulong off=1)  const
    { print_setpart(bla, data(), n, num_sets(), off); }

    bool OK()  const
    {
        if ( ! is_setpart_rgs( data(), n) )  return false;

        if ( n != 0 )  // prefix maxima in m[0,1,2,...,n-1] OK?
        {
            ulong mx1 = 0;
            for (ulong j=0; j<n; ++j)
            {
                mx1 += ( S[j] >= mx1 );
                const ulong mj = M[j+1];
                if ( mj != mx1 )  return false;
            }
        }

        if ( ! is_noncrossing_setpart_rgs( data(), n) )  return false;

        return true;
    }
};
// -------------------------


#endif  // !defined HAVE_SETPART_NONCROSSING_RGS_H__
