/***************************** 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 *************************************/

#ifndef BUFRFILTER_H
#define BUFRFILTER_H

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

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

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

class BufrFilterCondition;
class MvEccBufr;
class MvKeyProfile;

class MvKeyValue
{
public:
    MvKeyValue() :
        isSet_(false) {}
    MvKeyValue(const std::string& key, MvVariant::Type type) :
        key_(key),
        value_(type),
        isSet_(false) {}

    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_;
};

class MvBufrValue
{
    friend class MvBufrConditionGroup;
    friend class MvBufrValueGroup;

public:
    MvBufrValue(const MvKeyValue& value, bool valueTypeKnown);
    MvBufrValue(const MvKeyValue& value, const MvKeyConditionDefinition& condDef, bool valueTypeKnown);
    //MvBufrValue(const MvKeyValue& value,MvKeyCondition* cond, bool valueTypeKnown);

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

    void setRankCondition(MvKeyCondition* rankCond);

protected:
    void init();

    MvKeyValue value_;
    MvKeyCondition* cond_;
    MvKeyConditionDefinition condDef_;
    MvKeyCondition* rankCond_;
    bool valueTypeKnown_;
    bool hasRank_;
    std::string keyNameWithoutRank_;
};

class MvBufrValueGroup
{
public:
    MvBufrValueGroup() :
        includeMissingValue_(false) {}
    ~MvBufrValueGroup();

    void setIncludeMissingValue(bool b) { includeMissingValue_ = b; }
    virtual void add(const MvBufrValue&);
    virtual void checkCurrentKey(MvObs* obs);
    virtual void reset();
    std::size_t size() const { return items_.size(); }
    const MvKeyValue& value(int i) const { return items_[i].value_; }
    const MvKeyValue& value(const std::string& key) const;
    bool isAllValueSet() const;
    bool isNoValueSet() const;
    bool isEmpty() const { return items_.empty(); }

protected:
    void adjustType(int, int);
    bool checkCurrentKey(MvObs* obs, int idx);
    bool isMissingValue(double val);
    bool isMissingValue(long val);

    std::vector<MvBufrValue> items_;
    bool includeMissingValue_;
    static long longMissingValue_;
    static double doubleMissingValue_;
};

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

    virtual void add(const MvBufrValue&);
    bool match() const { return allMatch_; }
    void checkCurrentKey(MvObs*);
    void checkConditions(MvObs*);
    void reset();

protected:
    void updateMatchStatus();

    bool allMatch_;
};

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

class BufrFilterEngineObserver
{
public:
    BufrFilterEngineObserver() {}
    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;
    void clear();
    bool isExtractedDefined() const;
    void run(const BufrFilterDef& def, const std::string& resFileName, MvKeyProfile* result,
             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);

    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 writeCompressed(MvObs*);
    void addToResult(MvObs*);
    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;

    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_;
    std::vector<int> filteredSubSets_;
    BufrFilterEngineObserver* observer_;
    bool hasAttributeCondition_;
    MvBufrPreFilter preFilter_;
};

#endif  // BUFRFILTER_H
