/*
 * (C) Copyright 1996-2016 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */


#ifndef EpsJSon_H
#define EpsJSon_H

#include "magics.h"


#include "WrepJSonAttributes.h"

#include "Data.h"
#include "MagDateTime.h"
#include "MagicsDecoder.h"
#include "UserPoint.h"
#include "json_spirit.h"

#include <limits>
#include "Matrix.h"

namespace magics {


struct InputWrep {
    bool empty() { return steps_.empty(); }
    map<string, vector<double>> values_;
    map<string, double> info_;
    map<string, vector<vector<double>>> ensembleValues_;

    vector<double> steps_;
    vector<double> levels_;
    int index(double val) {
        if (val == -1) {
            return 0;
        }
        if (steps_.empty())
            return -1;
        vector<double>::iterator step = steps_.begin();
        int index                     = 0;
        while (*step != val) {
            ++step;
            if (step == steps_.end()) {
                return -1;
            }
            index++;
        }
        return index;
    }
    void print() {
        cout << "InputWrep" << endl;
        string sep = "steps[";
        for (vector<double>::iterator step = steps_.begin(); step != steps_.end(); ++step) {
            cout << sep << *step;
            sep = ", ";
        }
        cout << "]" << endl;
        for (map<string, double>::iterator value = info_.begin(); value != info_.end(); ++value) {
            cout << value->first << " = " << value->second << endl;
        }
        for (map<string, vector<double>>::iterator value = values_.begin(); value != values_.end(); ++value) {
            string sep = value->first + "[";
            for (vector<double>::iterator val = value->second.begin(); val != value->second.end(); ++val) {
                std::cout << sep << *val;
                sep = ", ";
            }
            cout << "]" << endl;
        }
        for (map<string, vector<vector<double>>>::iterator value = ensembleValues_.begin();
             value != ensembleValues_.end(); ++value) {
            string sep = value->first + "[";
            for (vector<vector<double>>::iterator val = value->second.begin(); val != value->second.end(); ++val) {
                std::cout << sep << val->size();
                sep = ", ";
            }
            cout << "]" << endl;
        }
    }
};


class WrepJSon : public Data, public PointsList, public WrepJSonAttributes {
public:
    WrepJSon();
    virtual ~WrepJSon();

    typedef void (WrepJSon::*Method)(const json_spirit::Value&);
    typedef void (WrepJSon::*Decoder)();
    typedef void (WrepJSon::*TransformationHandler)(Transformation&);
    typedef double (WrepJSon::*HeightCorrection)(double);
    typedef json_spirit::Value (WrepJSon::*MetaMethod)();
    map<string, Method> methods_;
    map<string, Decoder> decoders_;
    map<string, TransformationHandler> transformationHandlers_;
    map<string, HeightCorrection> heightCorrections_;
    map<string, MetaMethod> metaMethods_;

    void efi();
    void cdf();
    void eps();
    void basic();
    void data();
    void profile();
    void tephigram();
    void hodograph();
    void cape();


    void profile(Transformation&);
    void eps(Transformation&);
    void efi(Transformation&);
    void cdf(Transformation&);

    // Simple  input
    void x_values(const json_spirit::Value&);
    void y_values(const json_spirit::Value&);
    void values(const json_spirit::Value&);
    void x_date_values(const json_spirit::Value&);
    void y_date_values(const json_spirit::Value&);
    void clim_step(const json_spirit::Value&);

    // tephigram
    void param(const json_spirit::Value&);
    void levels(const json_spirit::Value&);
    void data(const json_spirit::Value&, vector<double>&);

    // Hodograph
    void hodo_u(const json_spirit::Value&);
    void hodo_v(const json_spirit::Value&);

    // Cape
    void cape0(const json_spirit::Value&);
    void cape1(const json_spirit::Value&);
    void cape2(const json_spirit::Value&);
    void cape3(const json_spirit::Value&);
    void cape_dig(const json_spirit::Value&);


    // common
    void location(const json_spirit::Value&);
    void station_name(const json_spirit::Value&);
    void valid_time(const json_spirit::Value&);
    void epsz(const json_spirit::Value&);
    void detz(const json_spirit::Value&);
    void date(const json_spirit::Value&);
    void expver(const json_spirit::Value&);
    void height(const json_spirit::Value&);
    void time(const json_spirit::Value&);
    void step(const json_spirit::Value&);
    void api(const json_spirit::Value&);
    void x_min_value(const json_spirit::Value&);

    virtual void parameter(const json_spirit::Value&);
    virtual void eps(const json_spirit::Value&);
    virtual void clim(const json_spirit::Value&);
    virtual void efi(const json_spirit::Value&);
    void dig(const json_spirit::Value&);
    void ignore(const json_spirit::Value&) {}
    void missing(const json_spirit::Value&);
    void mask(const json_spirit::Value&);
    void station(const json_spirit::Value&);
    void metadata(const json_spirit::Value&);
    void temperature_correction(const json_spirit::Value&);
    void points_along_meridian(const json_spirit::Value&);


    json_spirit::Value temperature_correction();
    json_spirit::Value temperature_adjustment();
    json_spirit::Value eps_resolution();
    json_spirit::Value deterministic_resolution();
    json_spirit::Value height();
    json_spirit::Value station_name();

    MatrixHandler& matrix();
    void customisedPoints(const std::set<string>&, CustomisedPointsList&);
    void customisedPoints(const Transformation&, const std::set<string>&, CustomisedPointsList&);

    virtual void set(const map<string, string>& map) { WrepJSonAttributes::set(map); }
    virtual void set(const XmlNode& node) { WrepJSonAttributes::set(node); }

    virtual void visit(Transformation&);
    virtual void visit(TextVisitor&);
    void visit(const XmlNode& node);

    virtual void decode();

    double correctDetz(double);
    double correctEpsz(double);

    void visit(MetaDataVisitor&);
    void points(const Transformation&, vector<UserPoint>&);
    void customisedPoints(const Transformation& t, const std::set<string>& n, CustomisedPointsList& out, bool all) {
        customisedPoints(t, n, out);
    }
    PointsHandler& points(const Transformation& t, bool);


protected:
    //! Method to print string about this class on to a stream of type ostream (virtual).
    virtual void print(ostream&) const;
    DateTime base_;
    vector<CustomisedPoint*> points_;
    PointsList list_;
    double minx_;
    double maxx_;
    double minClim_;
    double maxClim_;
    double miny_;
    double maxy_;
    double shift_;
    double missing_;
    double latitude_;
    double longitude_;
    double height_;
    double epsz_;
    double detz_;
    double scaling_factor_;
    double offset_factor_;
    double mask_;
    double x_min_value_;
    string station_name_;
    double station_latitude_;
    double station_longitude_;
    string date_;
    string time_;
    int step_;
    string file_;
    string valid_time_;
    string expver_;
    string api_;
    string capekey_;
    string tephikey_;


    json_spirit::Value metadata_;
    InputWrep values_;
    InputWrep* current_;

    InputWrep clim_;

    map<string, InputWrep> eps_;
    map<string, InputWrep> efi_;
    map<string, InputWrep> capes_;


    int points_along_meridian_;
    Matrix matrix_;

    bool xdate_;
    DateTime xBase_;
    bool ydate_;
    DateTime yBase_;
    bool regular_;

private:
    //! Copy constructor - No copy allowed
    WrepJSon(const WrepJSon&);
    //! Overloaded << operator to copy - No copy allowed
    WrepJSon& operator=(const WrepJSon&);

    // -- Friends
    //! Overloaded << operator to call print().
    friend ostream& operator<<(ostream& s, const WrepJSon& p) {
        p.print(s);
        return s;
    }
};

}  // namespace magics
#endif
