// ========================================================= //
//                                                           //
//   File      : xferset.h                                   //
//   Purpose   : field transfer sets                         //
//                                                           //
//   Coded by Ralf Westram (coder@reallysoft.de) in Mar 19   //
//   http://www.arb-home.de/                                 //
//                                                           //
// ========================================================= //

#ifndef XFERSET_H
#define XFERSET_H

#ifndef ERRORORTYPE_H
#include <ErrorOrType.h>
#endif
#ifndef ARBDB_H
#include <arbdb.h>
#endif

#ifndef _GLIBCXX_VECTOR
#include <vector>
#endif
#ifndef _GLIBCXX_STRING
#include <string>
#endif
#ifndef _GLIBCXX_CSTDLIB
#include <cstdlib>
#endif

#define xf_assert(cond) arb_assert(cond)

#define NOSEP ""

struct ConfigMapping;

namespace FieldTransfer {
    using std::string;

    class TransportedData {
        GB_TYPES sourceType; // found type (GB_NONE -> field does not exist)

        // members to store data of different types:
        string data_s;
        int    data_i;
        float  data_f;

        string err; // if non-empty -> something went wrong

        static bool state_valid() { return !GB_have_error(); }

        explicit TransportedData(GB_TYPES t, GB_ERROR error) : sourceType(t), err(error) { xf_assert(t == GB_NONE); xf_assert(error); xf_assert(state_valid()); } // otherwise use GB_TYPES-ctor!
        explicit TransportedData(GB_TYPES); // dangerous & forbidden
        explicit TransportedData() : sourceType(GB_NONE) { xf_assert(state_valid()); } // no data and no error (e.g. missing source field). called by none()

    public:
        explicit TransportedData(const string& s) : sourceType(GB_STRING), data_s(s) { xf_assert(state_valid()); }
        explicit TransportedData(int i) : sourceType(GB_INT), data_i(i) { xf_assert(state_valid()); }
        explicit TransportedData(float f) : sourceType(GB_FLOAT), data_f(f) { xf_assert(state_valid()); }

        static TransportedData none() { return TransportedData(); }
        static TransportedData makeError(GB_ERROR error) { return TransportedData(GB_NONE, error); }

        bool failed() const { return sourceType == GB_NONE && !err.empty(); }
        bool exists() const {
            xf_assert(!failed()); // failed transports can never have existing data!
            return sourceType != GB_NONE;
        }
        GB_TYPES getType() const { xf_assert(exists()); return sourceType; }

        const string& getString() const { xf_assert(getType() == GB_STRING); return data_s; }
        int getInt() const { xf_assert(getType() == GB_INT); return data_i; }
        float getFloat() const { xf_assert(getType() == GB_FLOAT); return data_f; }

        GB_ERROR getError() const {
            xf_assert(failed());
            static string kept;
            kept = err; // avoid 'err' gets destroyed together with 'this'
            return kept.c_str();
        }
    };

    class ReadRule {
        string fields;       // single field or list of fields (separated by ';'). field may be hierarchical (i.e. contain '/').
        string separator;
        string aci;          // empty = > do not use ACI
        bool   single_field; // true if 'fields' contains only 1 field

        TransportedData readTypedFromField(GB_TYPES readAsType, GBDATA *gb_field) const;
        TransportedData aciAppliedTo(const string& toStr, GBDATA *gb_main, GBDATA *gb_dest_item) const;

        void detectSingleField() { single_field = fields.find_first_of(';') == string::npos; }

    protected:
        void saveReadConfig(ConfigMapping& cfgmap) const;
        string describe() const;

    public:
        ReadRule(const string& fields_, const string& separator_) : // simple rule
            fields(fields_),
            separator(separator_)
        {
            detectSingleField();
        }
            ReadRule(const char *fields_, const char *separator_, const char *aci_) : // ACI rule
            fields(fields_),
            separator(separator_),
            aci(aci_)
        {
            detectSingleField();
        }

        TransportedData readFrom(GBDATA *gb_item, GBDATA *gb_dest_item) const;
        const string& getACI() const { return aci; }
        const string& getSourceFields() const { return fields; }
        const string& getSeparator() const { return separator; }

        bool multiple_source_fields() const { return !single_field; }
    };

    class WriteRule {
        string   name;
        GB_TYPES targetType; // @@@ need to distinguish between forced type by-config and by-existing-target-field

        mutable bool isValidKey;

    protected:
        void saveWriteConfig(ConfigMapping& cfgmap) const;
        string describe() const;

    public:
        explicit WriteRule(const string& name_) :
            name(name_),
            targetType(GB_NONE),
            isValidKey(false)
        {}
        WriteRule(const char *name_, GB_TYPES forceType) :
            name(name_),
            targetType(forceType),
            isValidKey(false)
        {}

        GB_ERROR check_hkey() const {
            /*! similar to GB_check_hkey, but checked only once for each VALID key, i.e. for each rule */
            GB_ERROR error = NULp;
            if (!isValidKey) {
                error            = GB_check_hkey(name.c_str());
                if (error) error = GBS_global_string("in target field: %s", error);
                else isValidKey  = true;
            }
            return error;
        }
        const string& targetField() const { return name; }

        bool forcesType() const { return targetType != GB_NONE; }
        GB_TYPES getTargetType() const { xf_assert(forcesType()); return targetType; }
        void setTargetType(GB_TYPES forceType) {
            xf_assert(!forcesType()); // already has been forced
            targetType = forceType;
        }

        __ATTR__USERESULT GB_ERROR writeTo(const TransportedData& data, GBDATA *gb_item, bool acceptLossyConversion) const;

    };

    class                    Rule;
    typedef SmartPtr<Rule>   RulePtr;
    typedef ErrorOr<RulePtr> ErrorOrRulePtr;

    class Rule : public ReadRule, public WriteRule {
        bool precision_loss_permitted; // @@@ move to WriteRule?

    public:
        Rule(const ReadRule& howRead, const WriteRule& howWrite) :
            ReadRule(howRead),
            WriteRule(howWrite),
            precision_loss_permitted(false)
        {}

        __ATTR__USERESULT GB_ERROR transferBy(GBDATA *gb_source, GBDATA *gb_dest) const;

        static RulePtr makeSimple(const string& src, const string& sep, const string& dest) { return new Rule(ReadRule(src, sep), WriteRule(dest)); }
        static RulePtr makeAciConverter(const char *src, const char *sep, const char *aci, const char *dest) { return new Rule(ReadRule(src, sep, aci), WriteRule(dest)); }
        static ErrorOrRulePtr makeFromConfig(const char *config);

        static RulePtr forceTargetType(GB_TYPES forceType, RulePtr rule) {
            rule->setTargetType(forceType);
            return rule;
        }

        bool precisionLossPermitted() const {
            return precision_loss_permitted;
        }
        void permitPrecisionLoss() {
            xf_assert(!precisionLossPermitted()); // why do you multi-permit?
            precision_loss_permitted = true;
        }
        static RulePtr permitPrecisionLoss(RulePtr rule) {
            rule->permitPrecisionLoss();
            return rule;
        }

        string getConfig() const;
        string getShortDescription() const;
    };

    typedef std::vector<RulePtr>          RuleContainer;
    typedef RuleContainer::const_iterator RuleIterator;

    class                       RuleSet;
    typedef SmartPtr<RuleSet>   RuleSetPtr;
    typedef ErrorOr<RuleSetPtr> ErrorOrRuleSetPtr;

    class RuleSet {
        // defines a transfer from item A to item B.
        // items may e.g. be species, genes, ...

        RuleContainer rules;   // rules to transfer single fields
        string        comment; // description of RuleSet
        bool          transferUndefFields;

    public:
        RuleSet() : transferUndefFields(false) {}

        bool empty() const { return rules.empty(); }
        size_t size() const { return rules.size(); }
        bool validIdx(int idx) const { return idx>=0 && size_t(idx)<size(); }

        void add(RulePtr rule) { rules.push_back(rule); }

        RulePtr getPtr(int idx) {
            xf_assert(validIdx(idx));
            return rules[idx];
        }
        const Rule& get(int idx) const {
            xf_assert(validIdx(idx));
            return *rules[idx];
        }
        void replace(int idx, RulePtr rule) {
            xf_assert(validIdx(idx));
            xf_assert(rule.isSet());
            rules[idx] = rule;
        }
        void erase(int idx) {
            xf_assert(validIdx(idx));
            rules.erase(rules.begin()+idx);
        }
        int insertBefore(int idx, RulePtr rule) {
            if (validIdx(idx)) {
                rules.insert(rules.begin()+idx, rule);
                return idx;
            }
            // invalid index -> silently append:
            add(rule);
            return int(size()-1);
        }

        void setComment(const string& newComment) { comment = newComment; }
        const string& getComment() const { return comment; }

        bool shallTransferUndefFields() const { return transferUndefFields; }
        void set_transferUndefFields(bool transferThem) { transferUndefFields = transferThem; }

        __ATTR__USERESULT GB_ERROR transferBy(GBDATA *gb_source, GBDATA *gb_dest) const;

        __ATTR__USERESULT GB_ERROR saveTo(const char *filename) const;
        static ErrorOrRuleSetPtr loadFrom(const char *filename);

        void extractUsedFields(StrArray& input, StrArray& output) const;
    };

    enum ItemCloneType {                     // (all types but CLONE_INTO_EXISTING create a cloned species)
        REPLACE_ITEM_BY_CLONE,               // delete source item, create + keep cloned item (used by IMPORT).
        RENAME_ITEM_WHILE_TEMP_CLONE_EXISTS, // temp. rename source. create clone. on destruction: delete clone + restore source item (used by EXPORT).
        CLONE_INTO_EXISTING,                 // keep source, update existing target by overwriting target fields (used by MERGE).
        REAL_CLONE,                          // keep source, create + keep clone (will be used by MERGE). problematic if source+clone reside in same parent container!
    };

    enum ClonableItemType {
        CLONE_ITEM_SPECIES,
    };

    struct AlignmentTransporter {
        virtual ~AlignmentTransporter() {}
        virtual bool shallCopyBefore() const = 0; // shall ItemClonedByRuleSet::copyAlignments() be called b4 calling transport()?
        virtual GB_ERROR transport(GBDATA*gb_src_item, GBDATA *gb_dst_item) const = 0;
    };
    typedef SmartPtr<AlignmentTransporter> AlignmentTransporterPtr;

    class ItemClonedByRuleSet : virtual Noncopyable {
        ClonableItemType itemtype;

        GBDATA *gb_source;
        GBDATA *gb_clone;

        ItemCloneType type;

        string errorCopy;
        string orgName;

#if defined(ASSERTION_USED)
        mutable bool checked4error;
        mutable bool userCallbackUsed;
#endif

        static string lastReportedError;

        GB_ERROR overlayOrCloneSub(const char *subName, GBDATA *gb_sub);
        GB_ERROR cloneMissingSub(const char *subName, GBDATA *gb_sub);
        GB_ERROR copySubIfMissing(const char *subName);
        GB_ERROR copyAlignments();
        const char *get_id_field() const;

    public:
        ItemClonedByRuleSet(GBDATA*& gb_item, ClonableItemType itemtype_, RuleSetPtr ruleset, ItemCloneType type_, GBDATA *gb_refItem, const AlignmentTransporter *aliTransporter);
        ~ItemClonedByRuleSet();

        // access result:
        bool has_error() const {
#if defined(ASSERTION_USED)
            checked4error = true;
#endif
            return !errorCopy.empty();
        }
        GB_ERROR get_error() const {
            // result persists until next call of this function
            xf_assert(checked4error); // use has_error()!
            xf_assert(has_error()); // logic error
            lastReportedError = errorCopy;
            return lastReportedError.c_str();
        }
        GBDATA *get_clone() {
            xf_assert(checked4error); // use has_error()!
            xf_assert(!has_error()); // logic error
            xf_assert(gb_clone);
            return gb_clone;
        }
    };

};

#else
#error xferset.h included twice
#endif // XFERSET_H
