/***************************** LICENSE START ***********************************

 Copyright 2017 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#pragma once

#include <string>
#include <vector>
#include <map>
#include <set>

#include "MvObs.h"
#include "MvObsSet.h"
#include "BufrFilterDef.h"

#include "MvVariant.h"
#include "MvKeyCondition.h"
#include "MvEccBufr.h"

using namespace metview;

class BufrFilterCondition;
class MvEccBufr;
class MvKeyProfile;
class MvKey;

class MvKeyValue
{
public:
    MvKeyValue() = default;
    MvKeyValue(const std::string& key, MvVariant::Type type) :
        key_(key),
        value_(type) {}

    const std::string& key() const { return key_; }
    const MvVariant& value() const { return value_; }
    MvVariant::Type type() const { return value_.type(); }
    void resetValue() { isSet_ = false; }
    bool isSet() const { return isSet_; }
    void setValue(const MvVariant& v)
    {
        value_ = v;
        isSet_ = true;
    }

protected:
    std::string key_;
    MvVariant value_;
    bool isSet_{false};
};

class MvBufrValueItem
{
    friend class MvBufrConditionGroup;
    friend class MvBufrValueGroup;

public:
    MvBufrValueItem() = default;
    MvBufrValueItem(const MvKeyValue& value, bool valueTypeKnown, const std::vector<MvKeyConditionDefinition>& coordCondDefs = std::vector<MvKeyConditionDefinition>());
    MvBufrValueItem(const MvKeyValue& value, const MvKeyConditionDefinition& condDef, bool valueTypeKnown, const std::vector<MvKeyConditionDefinition>& coordCondDefs = std::vector<MvKeyConditionDefinition>());
    ~MvBufrValueItem();

    const std::string& keyNameWithoutRank() const { return keyNameWithoutRank_; }
    bool isSameKey(const std::string& name) const;

    void setRankCondition(MvKeyCondition* rankCond);
    bool adjustConditions(MvObs* obs);
    bool allCondsCreated() const;
    bool allCoordCondsMatch() const;
    bool collectable() const { return collectable_; }
    void setCollectable(bool b) { collectable_ = b; }
    void setEnabledCoordConds(bool);
    const std::string& label() const { return label_; }
    void setLabel(const std::string& label) { label_ = label; }
    const MvKeyValue& value() const { return value_; }

protected:
    void init();

    MvKeyValue value_;
    MvKeyCondition* cond_{nullptr};
    MvKeyConditionDefinition condDef_;
    MvKeyCondition* rankCond_{nullptr};
    bool valueTypeKnown_{false};
    bool collectable_{true};
    bool hasRank_{false};
    std::string keyNameWithoutRank_;
    std::vector<MvKeyCondition*> coordConds_;
    std::vector<MvKeyConditionDefinition> coordCondDefs_;
    bool conditionsAdjusted_{false};
    int resultIndex_{-1};
    std::string label_;
};

class MvBufrValueGroup
{
public:
    MvBufrValueGroup() = default;
    ~MvBufrValueGroup();

    static long longMissingValue_;
    static double doubleMissingValue_;

    void setIncludeMissingValue(bool b) { includeMissingValue_ = b; }
    virtual void add(const MvBufrValueItem&);
    virtual void checkCurrentKey(MvObs* obs);
    virtual void reset();
    std::size_t size() const { return items_.size(); }
    const MvBufrValueItem& item(int i) const { return items_[i]; }
    const MvBufrValueItem& item(const std::string&) const;
    const MvBufrValueItem& itemByResultIndex(int) const;
    bool isAllValueSet() const;
    bool isNoValueSet() const;
    bool isEmpty() const { return items_.empty(); }
    void setDirectMode(bool dm) { directMode_ = dm; }
    void updateNonCollectables();
    void setResultIndex(int idx, int resultIdx);
    void adjustConditions(MvObs*);

protected:
    void adjustType(int, int);
    bool checkCurrentKey(MvObs* obs, int idx);
    bool checkCurrentKeyDirect(MvObs* obs, int idx);
    bool isMissingValue(double val);
    bool isMissingValue(long val);
    bool evalCondition(MvKeyCondition* cond, MvObs* obs, long& lVal, double& dVal, std::string& sVal);

    std::vector<MvBufrValueItem> items_;
    bool includeMissingValue_{false};
    bool directMode_{false};
    bool conditionsAdjusted_{false};
};

class MvBufrConditionGroup : public MvBufrValueGroup
{
public:
    MvBufrConditionGroup() :
        allMatch_(true) {}

    void add(const MvBufrValueItem&) override;
    bool match() const { return allMatch_; }
    void checkCurrentKey(MvObs*) override;
    void checkConditions(MvObs*);
    void reset() override;
    bool valuesDiffer(const MvBufrConditionGroup& o) const;

protected:
    void updateMatchStatus();

    bool allMatch_;
};

class MvBufrStandardGroup : public MvBufrValueGroup
{
public:
    MvBufrStandardGroup() = default;
    void checkCurrentKey(MvObs*);
};

class BufrFilterEngineObserver
{
public:
    BufrFilterEngineObserver() = default;
    virtual void notifyBufrFilterProgress(int) = 0;
};

class MvBufrPreFilter
{
public:
    MvBufrPreFilter() :
        enabled_(false) {}

    void setMessageNumber(int);
    void setEditionNumber(int);
    void setOriginatingCentre(int);
    void setOriginatingCentreAsStr(const std::string&);
    void setOriginatingSubCentre(int);
    void setMasterTableVersion(int);
    void setLocalTableVersion(int);
    void setMessageType(int);
    void setMessageSubType(int);
    void setMessageRdbType(int);

    bool isEnabled() const { return enabled_; }
    void evalFilter(const std::vector<MvEccBufrMessage*>& msgData,
                    std::vector<size_t>& matchVec, int& lastCnt) const;

protected:
    bool evalFilter(MvEccBufrMessage* msg, int msgCnt) const;
    bool evalMessageNumber(int) const;
    bool evalEditionNumber(MvEccBufrMessage* msg) const;
    bool evalOriginatingCentre(MvEccBufrMessage* msg) const;
    bool evalOriginatingCentreAsStr(MvEccBufrMessage* msg) const;
    bool evalOriginatingSubCentre(MvEccBufrMessage* msg) const;
    bool evalMasterTableVersion(MvEccBufrMessage* msg) const;
    bool evalLocalTableVersion(MvEccBufrMessage* msg) const;
    bool evalMsgType(MvEccBufrMessage* msg) const;
    bool evalMsgSubType(MvEccBufrMessage* msg) const;
    bool evalMsgRdbType(MvEccBufrMessage* msg) const;

    bool enabled_;
    std::vector<int> messageNumber_;  // starts at 1
    std::vector<int> editionNumber_;
    std::vector<int> originatingCentre_;
    std::vector<std::string> originatingCentreStr_;
    std::vector<int> originatingSubCentre_;
    std::vector<int> masterTableVersion_;
    std::vector<int> localTableVersion_;
    std::vector<int> messageType_;
    std::vector<int> messageSubType_;
    std::vector<int> messageRdbType_;
};


class BufrFilterEngine
{
public:
    enum FilterMode
    {
        IconMode,
        GuiMode
    };

    BufrFilterEngine(const std::string& inFileName, FilterMode filterMode, BufrFilterEngineObserver* observer);
    ~BufrFilterEngine();

    void add(const std::string&, const std::string&);
    const std::string& value(const std::string& key, bool mustExist = true) const;
    void values(const std::string& key, std::vector<std::string>& valueVec, const std::string& separator = "/") const;
    bool isExtractedDefined() const;
    void run(const BufrFilterDef& def, const std::string& resFileName, MvKeyProfile* resProf,
             int totalMsgNum, const std::vector<MvEccBufrMessage*>&);

    void runWithBufrData(const BufrFilterDef& def, const std::string& resFileName, MvKeyProfile* resProf,
                         int totalMsgNum, MvEccBufr* bufrData);

    void setObserver(BufrFilterEngineObserver* obs) { observer_ = obs; }

    MvObsSetIterator* obsIter() const { return obsIter_; }

    void toGeopoints(const std::string& fName, const std::string&);
    void toCsv(const std::string& fName);

protected:
    // NR_returnObs  /**< - return the next available subset (from this or the next BUFR msg */
    // NR_returnMsg  /**< - return the first subset from the next BUFR message */

    enum CollectMode
    {
        CollectFirst,
        CollectAll
    };
    enum OutType
    {
        BufrOutput,
        CsvOutput,
        NoOutput
    };

    void getOutputOptions();
    void getIndexOptions();
    void getEditionOptions();
    void getTypeOptions();
    void getIdentOptions();
    void getTimeOptions();
    void getAreaOptions();
    void getCustomOptions();
    bool getRank(const std::string& rankKey, std::string& rankValue) const;
    bool getRanks(const std::string& rankKey, std::vector<int>& rankValue) const;

    void buildConditionDef(const std::string& key, const std::string& operKey,
                           const std::string& valueKey, MvKeyConditionDefinition& condDef);

    void buildKeyCoordConditionDefs(const std::string& name, std::string& keyName,
                                    std::vector<MvKeyConditionDefinition>& conds,
                                    bool& needConditionInOutput);

    bool isNumber(const std::string& val) const;
    bool isKeyValueNumber(const std::string& key) const;
    void intValue(const std::string& key,
                  const std::string& param,
                  int minVal, int& outVal) const;
    void getIntValues(const std::string& key,
                      const std::string& param,
                      int minVal, std::vector<int>& outVec) const;
    void getDoubleValues(const std::string& key,
                         const std::string& param,
                         double minVal, std::vector<double>& outVec) const;
    void getStringValues(const std::string& key,
                         const std::string& param,
                         std::vector<std::string>& outVec) const;

    void filterOne();
    void initCompressedCache(MvObs* obs);
    void extractValues(MvObs* obs, bool& didExtract, int& collectCnt);
    void extractValuesDirect(MvObs* obs, bool& didExtract, int& collectCnt);

    void filterOneMissingElem();
    void writeCompressed(MvObs*);
    void addToResult(MvObs*, const MvBufrConditionGroup&);
    void close();
    void progress(int);

    bool parseDate(const std::string& val, int& year, int& month, int& day, std::string& err) const;
    bool parseTime(const std::string& val, int& hour, int& minute, int& second, std::string& err) const;
    bool checkHour(int h, std::string& err) const;
    bool checkMinute(int m, std::string& err) const;
    bool checkSecond(int s, std::string& err) const;
    bool parseTimeWindow(std::string& winVal, int& winSec, std::string& err) const;
    bool checkLon(float lon1, float lon2, std::string& err) const;
    bool checkLat(float lat1, float lat2, std::string& err) const;
    std::string toGeopointsTime(const std::string& tval) const;
    void parseArray(const std::string& val, std::vector<std::string>&) const;
    std::string outputToString(MvKey* key, int i) const;
    void makeColumnNamesUnique();

    FilterMode filterMode_;
    MvObsSet* inObs_;
    MvObsSet* outBufr_;
    mutable MvObsSetIterator* obsIter_;
    MvObs currentObs_;
    std::string inFileName_;
    int msgCnt_;
    mutable ENextReturn obsOrMsg_;
    BufrFilterDef def_;
    OutType outType_;
    MvBufrConditionGroup coordCond_;
    MvBufrValueGroup location_;
    MvBufrValueGroup extracted_;
    bool extractedHasRank_;
    MvBufrConditionGroup customCond_;
    MvKeyProfile* result_;
    CollectMode collectMode_;
    bool includeMissingValue_;
    bool includeMissingElement_;
    bool directMode_{false};
    std::vector<int> filteredSubSets_;
    BufrFilterEngineObserver* observer_;
    bool hasAttributeCondition_;
    MvBufrPreFilter preFilter_;
    std::set<std::string> allNeededDataKeys_;
};
