// See the file "COPYING" in the main distribution directory for copyright.

#include "zeek/EquivClass.h"

#include "zeek/zeek-config.h"

#include "zeek/CCL.h"
#include "zeek/util.h"

namespace zeek::detail {

EquivClass::EquivClass(int arg_size) {
    size = arg_size;
    fwd = new int[size];
    bck = new int[size];
    equiv_class = new int[size];
    rep = new int[size];
    ccl_flags = nullptr;
    num_ecs = 0;

    ec_nil = no_class = no_rep = size + 1;

    bck[0] = ec_nil;
    fwd[size - 1] = ec_nil;

    for ( int i = 0; i < size; ++i ) {
        if ( i > 0 ) {
            fwd[i - 1] = i;
            bck[i] = i - 1;
        }

        equiv_class[i] = no_class;
        rep[i] = no_rep;
    }
}

EquivClass::~EquivClass() {
    delete[] fwd;
    delete[] bck;
    delete[] equiv_class;
    delete[] rep;
    delete[] ccl_flags;
}

void EquivClass::ConvertCCL(CCL* ccl) {
    // For each character in the class, add the character's
    // equivalence class to the new "character" class we are
    // creating.  Thus when we are all done, the character class
    // will really consist of collections of equivalence classes
    // instead of collections of characters.

    int_list* c_syms = ccl->Syms();
    int_list* new_syms = new int_list;

    for ( auto sym : *c_syms ) {
        if ( IsRep(sym) )
            new_syms->push_back(SymEquivClass(sym));
    }

    ccl->ReplaceSyms(new_syms);
}

int EquivClass::BuildECs() {
    // Create equivalence class numbers.  If bck[x] is nil,
    // then x is the representative of its equivalence class.

    for ( int i = 0; i < size; ++i )
        if ( bck[i] == ec_nil ) {
            equiv_class[i] = num_ecs++;
            rep[i] = i;
            for ( int j = fwd[i]; j != ec_nil; j = fwd[j] ) {
                equiv_class[j] = equiv_class[i];
                rep[j] = i;
            }
        }

    return num_ecs;
}

void EquivClass::CCL_Use(CCL* ccl) {
    // Note that it doesn't matter whether or not the character class is
    // negated.  The same results will be obtained in either case.

    if ( ! ccl_flags ) {
        ccl_flags = new int[size];
        for ( int i = 0; i < size; ++i )
            ccl_flags[i] = 0;
    }

    int_list* csyms = ccl->Syms();
    for ( size_t i = 0; i < csyms->size(); /* no increment */ ) {
        int sym = (*csyms)[i];

        int old_ec = bck[sym];
        int new_ec = sym;

        size_t j = i + 1;

        for ( int k = fwd[sym]; k && k < size; k = fwd[k] ) { // look for the symbol in the character class
            for ( ; j < csyms->size(); ++j ) {
                if ( (*csyms)[j] > k )
                    // Since the character class is sorted,
                    // we can stop.
                    break;

                if ( (*csyms)[j] == k && ! ccl_flags[j] ) {
                    // We found an old companion of sym
                    // in the ccl.  Link it into the new
                    // equivalence class and flag it as
                    // having been processed.
                    bck[k] = new_ec;
                    fwd[new_ec] = k;
                    new_ec = k;

                    // Set flag so we don't reprocess.
                    ccl_flags[j] = 1;

                    // Get next equivalence class member.
                    break;
                }
            }

            if ( j < csyms->size() && (*csyms)[j] == k )
                // We broke out of the above loop by finding
                // an old companion - go to the next symbol.
                continue;

            // Symbol isn't in character class.  Put it in the old
            // equivalence class.
            bck[k] = old_ec;
            if ( old_ec != ec_nil )
                fwd[old_ec] = k;

            old_ec = k;
        }

        if ( bck[sym] != ec_nil || old_ec != bck[sym] ) {
            bck[sym] = ec_nil;
            fwd[old_ec] = ec_nil;
        }

        fwd[new_ec] = ec_nil;

        // Find next ccl member to process.
        for ( ++i; i < csyms->size() && ccl_flags[i]; ++i )
            // Reset "doesn't need processing" flag.
            ccl_flags[i] = 0;
    }
}

void EquivClass::UniqueChar(int sym) {
    // If until now the character has been a proper subset of
    // an equivalence class, break it away to create a new ec.

    if ( fwd[sym] != ec_nil )
        bck[fwd[sym]] = bck[sym];

    if ( bck[sym] != ec_nil )
        fwd[bck[sym]] = fwd[sym];

    fwd[sym] = ec_nil;
    bck[sym] = ec_nil;
}

void EquivClass::Dump(FILE* f) {
    fprintf(f, "%d symbols in EC yielded %d ecs\n", size, num_ecs);
    for ( int i = 0; i < size; ++i )
        if ( SymEquivClass(i) != 0 ) // skip usually huge default ec
            fprintf(f, "map %d ('%c') -> %d\n", i, i, SymEquivClass(i));
}

int EquivClass::Size() const { return padded_sizeof(*this) + util::pad_size(sizeof(int) * size * (ccl_flags ? 5 : 4)); }

} // namespace zeek::detail
