#if !defined  HAVE_SETPART_NONCROSSING_LL_H__
#define       HAVE_SETPART_NONCROSSING_LL_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-catalan-rgs.h"

#include "comb/comb-print.h"

#include "fxtio.h"
#include "fxttypes.h"
//#include "jjassert.h"

#include <vector>

//#define SETPART_NONCROSSING_LL_OPT  // slightly slower, so: nope!


class setpart_noncrossing_ll
// Noncrossing set partitions via Catalan restricted growth strings (RGS).
// Sets are in the form vector< vector<ulong> > for easy traversal,
//   see the print() method.
// Lexicographic order for the RGS.
// The number of length-n RGS is (OEIS sequence A000108)
//  1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, ...
{
private:
    ulong *A;  // digits of the RGS: a[k] <= a[k-1] + 1
    ulong n;   // Number of digits (paren pairs)

    std::vector< std::vector<ulong> > VV;
    ulong n_s;  // number of (sub-)sets

    ulong * LS;  // scratch: which set is open at tree levels
    // used in setup_mset_part() and update_mset_part()

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

public:
    explicit setpart_noncrossing_ll(ulong tn)
    {
        n = tn;
        A = new ulong[n + 2];
        A[0] = 0;  // unused
        A[n+1] = 0;  // sentinel, read-only

        VV.reserve(n);
        for (ulong j=0; j<n; ++j)
        {
            VV.push_back( std::vector<ulong> {} );
            VV.at(j).reserve( n - j );
        }

        LS = new ulong[n];

        first();
    }

    ~setpart_noncrossing_ll()
    {
        delete [] A;
        delete [] LS;
    }

    const std::vector< std::vector<ulong> > & data()  const  { return VV; }

    const ulong *data_rgs()  const  { return A + 1; }

    ulong num_sets()  const  { return n_s; }
    ulong size_rgs()  const  { return n + 1; }


private:
    ulong next_rgs()
    // Return position of leftmost change, zero with last.
    {
        ulong j = n;
        while ( j > 1 )
        {
            if ( A[j] <= A[j-1] )
            {
                ++A[j];
                return j;
            }
            A[j] = 0;
            --j;
        }

        return 0;  // current is last
    }

    void setup_mset_part()
    // wasteful to redo with every update of the RGS
    {
        for (auto & V : VV)  { V.clear(); }
        n_s = 0;
        long a1 = -1;  // fake to make first step an up-step
        for (ulong j=1; j<=n; ++j)
        {
            const long a0 = (long)A[j+0];  // this level
            if ( a1 < a0 )  // level up
            {
                // open new set at level:
                LS[ a0 ] = n_s;  // set we are looking at
                VV.at( n_s ).push_back( j );
                n_s += 1;  // number of sets; also next set
            }
            else
            {
                const ulong i = LS[ a0 ];  // set we are looking at
                VV.at( i ).push_back( j );
            }
            a1 = a0;
        }
    }

public:
    void first()
    {
        for (ulong k=0; k<size_rgs(); ++k)  A[k] = 0;
        setup_mset_part();
        n_s = ( n != 0 );
    }

#if defined SETPART_NONCROSSING_LL_OPT
private:
    void setup_LS( ulong k )
    {
        n_s = 0;
        long a1 = -1;  // fake to make first step an up-step
        for (ulong j=1; j<k; ++j)
        {
            const long a0 = (long)A[j+0];  // this level
            if ( a1 < a0 )  // level up
            {
                // open new set at level:
                LS[ a0 ] = n_s;  // set we are looking at
                n_s += 1;  // number of sets; also next set
            }
            a1 = a0;
        }
    }

    void update_mset_part( ulong j )
    {
        ulong nd = n + 1 - j;  // number of elements to remove

        // remove all elements >= j:
        ulong s = 0;
        for (auto & V : VV)
        {
            ulong i = V.size();
            if ( 0 == i )  { goto all_done; }
            do
            {
                --i;
                if ( V.at( i ) >= j )  // remove element
                {
                    V.pop_back();
                    nd -= 1;
                    if ( V.size() == 0 )  // set now empty
                    {
                        n_s -= 1;
                        if ( nd == 0 )  { goto all_done; }
                        goto set_done;
                    }
                    else
                    {
                        if ( nd == 0 )  { goto all_done; }
                    }
                }
                else
                {
                    goto set_done;
                }
            set_done: ;
            }
            while ( i != 0 );
            ++s;
        }
    all_done: ;
        setup_LS( j );  // this kills performance!

        // put elements back in:
        ulong a1 = A[j-1];
        for ( /*j=j*/; j<=n; ++j)
        {
            const ulong a0 = A[j+0];  // this level
            if ( a1 < a0 )  // level up
            {
                // open new set at level:
                LS[ a0 ] = n_s;  // set we are looking at
                VV.at( n_s ).push_back( j );

                n_s += 1;  // number of sets; also next set
            }
            else
            {
                const ulong i = LS[ a0 ];  // set we are looking at
                VV.at( i ).push_back( j );
            }
            a1 = a0;
        }
    }
#endif  // SETPART_NONCROSSING_LL_OPT

public:
    ulong next()
    {
        const ulong j = next_rgs();
        if ( j!=0 )
        {
#if defined SETPART_NONCROSSING_LL_OPT
            update_mset_part( j );
#else
            setup_mset_part();  // wasteful: rather update
#endif
        }
        return j;
    }


    ulong test()  const
    {
        if ( ! is_catalan_rgs(data_rgs(), n) )  { return 10; }

        {
            for (ulong j=n_s; j < VV.size(); ++j)
            {
                const ulong s = VV.at( j ).size();
                if ( s != 0 )  { return 20; }
            }

            ulong t = 0;
            for (ulong j=0; j < n_s; ++j)
            {
                const ulong s = VV.at( j ).size();
                if ( s==0 )  { return 21; }
                if ( s > n )  { return 22; }
                t += s;
            }
            if ( t != n )  { return 23; }
        }

        return 0;
    }

    bool OK()  const
    {
        const ulong t = test();
        if ( 0 == t )  { return true; }

        cout << " :: failed with test() == " << t << "\n";
        return false;
    }

    void print_rgs(const char *bla, bool dfz=true)  const
    { print_vec(bla, data_rgs(), n, dfz); }

    void print(const char *bla)  const
    {
        if ( bla )  cout << bla;
        cout << "{ ";
        for (ulong s=0; s<n_s; ++s)
        {
            const auto & V = VV.at( s );
            const ulong sz = V.size();
//            cout << s << ":";  // set number
            cout << "{";
            for (ulong i=0; i<sz; ++i)
            {
                const auto & e = V.at( i );
                cout << e;
                if ( i+1 != sz )  { cout << ", "; }
            }
            cout << "}";
            if ( s+1 != n_s )  { cout << ", "; }
        }
        cout << " }";
    }
};
// -------------------------


#endif  // !defined HAVE_SETPART_NONCROSSING_LL_H__
