// ========================================================= //
//                                                           //
//   File      : saiop.h                                     //
//   Purpose   : operations on SAI                           //
//                                                           //
//   Coded by Ralf Westram (coder@reallysoft.de) in Oct 19   //
//   http://www.arb-home.de/                                 //
//                                                           //
// ========================================================= //

#ifndef SAIOP_H
#define SAIOP_H

#ifndef ARB_STRARRAY_H
#include <arb_strarray.h>
#endif
#ifndef ERRORORTYPE_H
#include <ErrorOrType.h>
#endif
#ifndef _GLIBCXX_STRING
#include <string>
#endif
#ifndef ARBDB_BASE_H
#include <arbdb_base.h>
#endif
#ifndef _GLIBCXX_VECTOR
#include <vector>
#endif

#define sai_assert(cond) arb_assert(cond)

class SaiOperator;

typedef SmartPtr<SaiOperator> SaiOperatorPtr;

typedef ErrorOr<std::string>    ErrorOrString;
typedef ErrorOr<SaiOperatorPtr> ErrorOrSaiOperatorPtr;

enum SaiOperatorType {
    SOP_ACI,       // N->1
    SOP_TRANSLATE, // 1->1
    SOP_MATRIX,    // 2->1
    SOP_BOOLCHAIN, // N->1
    // (avoid changing order! int is saved to config)
};

#define SAI_OPERATOR_TYPES 4

class SaiCalcEnv : virtual Noncopyable {
    const CharPtrArray&  input;   // reference to input data (not owned; has to persist while 'this' is used)
    GBDATA              *gb_main; // needed by SaiAciApplicator

public:
    SaiCalcEnv(const CharPtrArray& input_, GBDATA *gb_main_) :
        input(input_),
        gb_main(gb_main_)
    {}

    const CharPtrArray& get_input() const { return input; }
    GBDATA *get_gbmain() const { return gb_main; }

    GB_ERROR check_lengths_equal(size_t& len) const;
};


class SaiOperator {
    SaiOperatorType type;

    static const char *typeName[];

public:
    SaiOperator(SaiOperatorType type_) : type(type_) {}
    virtual ~SaiOperator() {}

    static ErrorOrSaiOperatorPtr make(SaiOperatorType type, const char *config); // factory
    static const char *type_name(SaiOperatorType type) { return typeName[type]; }

    SaiOperatorType get_type() const { return type; }
    virtual std::string get_config() const = 0; // generate config string
    std::string get_description() const;

    virtual ErrorOrString apply(const SaiCalcEnv& calcEnv) const = 0;
};

// -----------------------------------
//      different operator types:

class SaiTranslator FINAL_TYPE : public SaiOperator {
    char transtab[256];

public:
    SaiTranslator(char default_translation) :
        SaiOperator(SOP_TRANSLATE)
    {
        transtab[0] = 0;
        memset(transtab+1, default_translation, 255);
    }

    static ErrorOrSaiOperatorPtr make(const char *config);

    std::string get_config() const                       OVERRIDE;
    ErrorOrString apply(const SaiCalcEnv& calcEnv) const OVERRIDE;

    void addTranslation(const char *from, char to);
    void deduceTranslations(class ConfigMapping& mapping) const; // order may differ from that defined via addTranslation()
};

class SaiMatrixTranslator FINAL_TYPE : public SaiOperator {
    static const char           DEFAULT_INDEX_CHAR = 'A'; // the index char of the default translator table
    SaiOperatorPtr              firstToIndexChar;         // translate 1st SAI content to index character (index into vector 'secondToResult')
    std::vector<SaiOperatorPtr> secondToResult;           // translate 2nd SAI to result character

    SaiMatrixTranslator() : SaiOperator(SOP_MATRIX) {} // used by factory (make)

public:
    SaiMatrixTranslator(SaiOperatorPtr Default) :
        SaiOperator(SOP_MATRIX),
        firstToIndexChar(new SaiTranslator(DEFAULT_INDEX_CHAR))
    {
        secondToResult.push_back(Default);
    }

    static ErrorOrSaiOperatorPtr make(const char *config);

    std::string get_config() const                       OVERRIDE;
    ErrorOrString apply(const SaiCalcEnv& calcEnv) const OVERRIDE;

    void addOperator(const char *from, SaiOperatorPtr to);
};

enum SaiBoolOp {
    SBO_FIRST,
    SBO_AND,
    SBO_OR,
    SBO_XOR,
    SBO_NAND,
    SBO_NOR,
    SBO_XNOR,
};

typedef SmartPtr<class SaiBoolRule> SaiBoolRulePtr;
typedef ErrorOr<SaiBoolRulePtr>     ErrorOrSaiBoolRulePtr;

class SaiBoolRule {
    SaiBoolOp   op;
    bool        specifyTrueChars;
    std::string chars;

    bool charSpecified(char c) const { return chars.find_first_of(c) != std::string::npos; }

public:
    SaiBoolRule(SaiBoolOp op_, bool specifyTrueChars_, const char *chars_) :
        op(op_),
        specifyTrueChars(specifyTrueChars_),
        chars(chars_)
    {}

    SaiBoolOp get_op() const { return op; }
    bool specifiesTrueChars() const { return specifyTrueChars; }
    const char *get_chars() const { return chars.c_str(); }

    void prepare_input_data(const char *input, size_t len, char *output) const {
        for (size_t p = 0; p<len; ++p) {
            output[p] = charSpecified(input[p]) == specifyTrueChars ? '1' : '0';
        }
        sai_assert(input[len] == 0);
        output[len] = 0;
    }

    void apply(char *inout, const char *in, size_t len) const;

    static ErrorOrSaiBoolRulePtr make(const char *fromString);
    std::string to_string() const;
};


class SaiBoolchainOperator: public SaiOperator {
    std::vector<SaiBoolRule> rule;
    char outTrans[2];

public:
    SaiBoolchainOperator(char c_0, char c_1) :
        SaiOperator(SOP_BOOLCHAIN)
    {
        outTrans[0] = c_0;
        outTrans[1] = c_1;
    }

    static ErrorOrSaiOperatorPtr make(const char *config);

    std::string get_config() const                       OVERRIDE;
    ErrorOrString apply(const SaiCalcEnv& calcEnv) const OVERRIDE;

    void addRule(const SaiBoolRule& r) {
        sai_assert(correlated(rule.empty(), r.get_op() == SBO_FIRST)); // 1st rule needs special operator type
        rule.push_back(r);
    }

#if defined(UNIT_TESTS)
    void dropRule() { rule.pop_back(); } // shall only be used in unit-tests
#endif

    size_t count_rules() const { return rule.size(); }
    const SaiBoolRule& getRule(size_t idx) const {
        sai_assert(idx<rule.size());
        return rule[idx];
    }
    char getOutTrans(bool i) const { return outTrans[i]; }
};


class SaiAciApplicator : public SaiOperator {
    std::string aci;

public:
    SaiAciApplicator(const char *aci_) :
        SaiOperator(SOP_ACI),
        aci(aci_)
    {}

    static ErrorOrSaiOperatorPtr make(const char *config);

    std::string get_config() const                       OVERRIDE;
    ErrorOrString apply(const SaiCalcEnv& calcEnv) const OVERRIDE;

    const std::string get_aci() const { return aci; }
};


#else
#error saiop.h included twice
#endif // SAIOP_H
