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

 Copyright 2012 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 <iostream>
#include <map>
#include <vector>
#include <list>
#include <assert.h>

//#include <Metview.h>
//#include "MvObs.h"


class MvOdbBitfieldMember
{
public:
    MvOdbBitfieldMember(std::string name, int pos, int size) :
        name_(name),
        pos_(pos),
        size_(size) {}
    std::string name() { return name_; }
    int pos() { return pos_; }
    int size() { return size_; }

private:
    std::string name_;
    int pos_;
    int size_;
};


class MvOdbColumn
{
public:
    enum OdbColumnType
    {
        None,
        Int,
        Float,
        String,
        Bitfield,
        Double
    };

    MvOdbColumn() :
        type_(None),
        num_(0),
        min_(1.0e+08),
        max_(-1.0e+08),
        loaded_(false) {}
    void addToStats(float);
    void addToStats(double);
    void compStats();

    std::string name() const { return name_; }
    std::string shortName() const { return shortName_; }
    OdbColumnType type() const { return type_; }
    std::string unit() { return unit_; }
    const std::vector<MvOdbBitfieldMember*>& bitfield() { return bitfield_; }
    int bitfieldNum() { return bitfield_.size(); }

    float min() { return min_; }
    float max() { return max_; }
    float avg() { return avg_; }
    float std() { return std_; }

    void setName(std::string);
    void setType(OdbColumnType s) { type_ = s; }
    void setUnit(std::string s) { unit_ = s; }
    void addBitfieldMember(MvOdbBitfieldMember* b) { bitfield_.push_back(b); }

    void unload();
    bool isLoaded() const { return loaded_; }
    void setLoaded(bool);
    void addStringData(std::string s) { sval_.push_back(s); }
    void addFloatData(float f) { fval_.push_back(f); }
    void addDoubleData(double d) { dval_.push_back(d); }
    void addIntData(int i) { ival_.push_back(i); }
    const std::vector<double>& doubleData() const { return dval_; }
    const std::vector<float>& floatData() const { return fval_; }
    const std::vector<int>& intData() const { return ival_; }
    const std::vector<std::string>& stringData() const { return sval_; }
    int rowNum() const;


private:
    std::string name_;
    std::string shortName_;
    OdbColumnType type_;
    std::string unit_;
    std::vector<MvOdbBitfieldMember*> bitfield_;

    int num_;
    float min_;
    float max_;
    float avg_;
    float std_;

    bool loaded_;
    std::vector<float> fval_;
    std::vector<int> ival_;
    std::vector<std::string> sval_;
    std::vector<double> dval_;
};


class MvAbstractOdb
{
public:
    enum OdbVersion
    {
        UnknownVersion,
        VersionOld,
        VersionNew
    };

    MvAbstractOdb(const std::string&, OdbVersion);
    MvAbstractOdb(const std::string&, const std::string&, const std::string&, OdbVersion);
    virtual ~MvAbstractOdb();

    virtual void init() = 0;

    const std::string& path() const { return path_; }
    const std::string& query() const { return query_; }
    OdbVersion version() const { return version_; }
    int chunkSize() const { return chunkSize_; }
    int currentChunk() const { return currentChunk_; }
    int nbRowsInChunk() const { return nbRowsInChunk_; }

    virtual void setChunkSize(int i) { chunkSize_ = i; }
    virtual void setCurrentChunk(int i) { currentChunk_ = i; }

    virtual int totalSizeInMb() = 0;
    virtual int rowNum() = 0;
    virtual int columnNum() = 0;
    virtual const MvOdbColumn* column(std::string) = 0;
    virtual const MvOdbColumn* column(int) = 0;
    virtual const std::vector<MvOdbColumn*>& columns() = 0;
    virtual const MvOdbColumn* loadColumn(std::string, bool reload = false) = 0;
    virtual const MvOdbColumn* loadColumn(int, bool reload = false) = 0;
    virtual void unloadColumn(std::string) = 0;
    virtual void unloadColumn(int) = 0;
    virtual void loadAllColumns() = 0;
    virtual void unloadAllColumns() = 0;

    virtual void generateStats(int) = 0;
    virtual bool toGeopoints(std::string, std::string) = 0;
    virtual bool hasColumn(const std::string&) = 0;

protected:
    virtual void readMetaData() = 0;
    virtual int findColumn(std::string) = 0;
    void detectOdbVersion();

    std::string path_;
    std::string sourcePath_;
    std::string query_;
    std::vector<MvOdbColumn*> column_;
    int rowNum_{0};
    bool headerIsRead_{false};

    OdbVersion version_;

    int chunkSize_{0};
    int currentChunk_{0};
    int nbRowsInChunk_{0};
};

#ifdef METVIEW_ODB_NEW
class MvOdb : public MvAbstractOdb
{
public:
    MvOdb(std::string);
    MvOdb(std::string, std::string, std::string);
    ~MvOdb();

    void init();

    void setChunkSize(int);
    void setCurrentChunk(int);

    int totalSizeInMb();
    int rowNum();
    int columnNum();
    const MvOdbColumn* column(std::string);
    const MvOdbColumn* column(int);
    const std::vector<MvOdbColumn*>& columns();
    const MvOdbColumn* loadColumn(std::string, bool reload = false);
    const MvOdbColumn* loadColumn(int, bool reload = false);
    void unloadColumn(std::string);
    void unloadColumn(int);
    void loadAllColumns();
    void unloadAllColumns();

    void generateStats(int);
    bool toGeopoints(std::string, std::string);
    static bool retrieveToFile(std::string, std::string, std::string);

    bool hasColumn(const std::string&);

protected:
    void readMetaData();
    int findColumn(std::string);
};
#endif

#ifdef METVIEW_ODB_OLD
class MvOldOdb : public MvAbstractOdb
{
public:
    MvOldOdb(std::string);
    MvOldOdb(std::string, std::string, std::string);
    ~MvOldOdb();

    void init();

    int totalSizeInMb() { return 0; }
    int rowNum();
    int columnNum();
    const MvOdbColumn* column(std::string);
    const MvOdbColumn* column(int);
    const std::vector<MvOdbColumn*>& columns();
    const MvOdbColumn* loadColumn(std::string, bool reload = false);
    const MvOdbColumn* loadColumn(int, bool reload = false);
    void unloadColumn(std::string);
    void unloadColumn(int);
    void loadAllColumns(){};
    void unloadAllColumns(){};

    void generateStats(int);
    bool toGeopoints(std::string, std::string);

    bool hasColumn(const std::string&) { return false; }

protected:
    void readMetaData();
    int findColumn(std::string);
};
#endif

class MvOdbFactory
{
public:
    MvOdbFactory() = default;
    static MvAbstractOdb* make(std::string path, std::string sourcePath = std::string(), std::string query = std::string());
};
