#ifndef DATAPROV_H
#define DATAPROV_H

#include<Eigen/Dense>
#include "../tools/easylogging++.h"
#include<vector>
#include<math.h>

// TODO decide how to construct training data
// One possible way for training data:
//  Matrix composed of an array of feature vectors
//  Labels are composed of linked list, such as
//      6,3,4,0,5,0,0
//      =>  0->6 | 1->3 | 2->4->5
//  How to compensate for non exhaustive labeling?
//      Use -1 to indicate not yet labeled data
//      -1s will be excluded from training

typedef struct DataEntry{
    std::string qid;
    double rank;
    Eigen::VectorXd feature;
} DataEntry;

class DataList{
private:
    int n;
    std::vector<DataEntry*> data;
public:
    unsigned long getSize(){return data.size();}
    void addEntry(DataEntry* d){data.push_back(d);}
    void setfSize(int fsize){n=fsize;}
    int getfSize(){return n;}
    void clear(){
        for (int i=0;i<data.size();++i)
            delete data[i];
        data.clear();
    }
    static DataEntry* copyEntry(DataEntry* d)
    {
        DataEntry* dat = new DataEntry;
        dat->rank = d->rank;
        dat->qid = d->qid;
        dat->feature.resize(d->feature.rows());
        for (int i=0;i<d->feature.rows();++i)
        {
            dat->feature(i)=d->feature(i);
        }
        return dat;
    }
    inline std::vector<DataEntry*>& getData(){
        return data;
    }
    ~DataList(){
        clear();
    }
};

class RidList{
private:
    int n;
    std::vector<DataEntry*> uniq;
    std::vector<DataEntry*> other;
    std::vector<DataEntry*> all;
public:
    static bool single;
    void clear(){
        uniq.clear();
        other.clear();
        all.clear();
    }
    void setfSize(int fsize){n=fsize;}
    inline int getfSize(){return n;}
    void addEntry(DataEntry* d){
        int ext=false;
        if (d->qid=="-1")
            other.push_back(d);
        for (int i=0;i<uniq.size();++i)
            if (uniq[i]->qid==d->qid)
            {
                ext = true;
                d->rank = i;
            }
        if (ext)
            other.push_back(d);
        else
            uniq.push_back(d);
        all.push_back(d);
    }
    inline DataEntry* getU(int x)
    {
        return uniq[x];
    }
    inline DataEntry* getO(int x)
    {
        return other[x];
    }
    inline DataEntry* getAll(int x)
    {
        return all[x];
    }
    inline std::string getQid(int x)
    {
        int a,b,q=getqSize();
        a=x/q;
        if (single)
            return getU(a)->qid;
        return getAll(a)->qid;
    }
    inline int getqSize()
    {
        if (single)
            return (int)other.size();
        return (int)(all.size()-1);
    }
    inline int getuSize()
    {
        if (single)
            return (int)uniq.size();
        return (int)all.size();
    }
    inline int getSize()
    {
        return getuSize()*getqSize();
    }
    inline Eigen::VectorXd getVec(int x){
        int a,b,q=getqSize();
        a=x/q;
        b=x%q;
        Eigen::VectorXd *id,*oth;
        id = &(uniq[a]->feature);
        if (single)
            oth = &(other[b]->feature);
        else if (b<a)
            oth = &(all[b]->feature);
        else
            oth = &(all[b+1]->feature);
        return (*id-*oth).cwiseAbs();
    };
    inline double getVecDot(int x,const Eigen::VectorXd &w)
    {
        int a,b,q=getqSize();
        a=x/q;
        b=x%q;
        double res = 0;
        Eigen::VectorXd *id,*oth;
        if (single)
        {
            id = &(uniq[a]->feature);
            oth = &(other[b]->feature);
        }
        else {
            id = &(all[a]->feature);
            if (b<a)
                oth = &(all[b]->feature);
            else
                oth = &(all[b+1]->feature);
        }
        for (int i=0;i<n;++i)
            res += fabs((*id)[i] - (*oth)[i])*w[i];
        return res;
    }
    inline void addVecw(int x,double w,Eigen::VectorXd &X)
    {
        int a,b,q=getqSize();
        a=x/q;
        b=x%q;
        Eigen::VectorXd *id,*oth;
        if (single)
        {
            id = &(uniq[a]->feature);
            oth = &(other[b]->feature);
        }
        else {
            id = &(all[a]->feature);
            if (b<a)
                oth = &(all[b]->feature);
            else
                oth = &(all[b+1]->feature);
        }
        for (int i=0;i<n;++i)
            X[i] += fabs((*id)[i] - (*oth)[i])*w;
    }
    inline double getL(int x){
        int a,b,q=getqSize();
        a=x/q;
        b=x%q;
        if (single)
        {
            if (std::fabs(other[b]->rank - a) < 1e-5)
                return 1;
            return -1;
        }
        double id,oth;
        id = all[a]->rank;
        if (b<a)
            oth = all[b]->rank;
        else
            oth = all[b+1]->rank;
        if (fabs(oth - id) < 1e-5)
            return 1;
        return -1;
    };
};

class DataProvider  //Virtual base class for data input
{
protected:
    bool eof;
public:
    DataProvider():eof(false){};

    bool EOFile(){return eof;}
    virtual void getAllDataSet(RidList &out) = 0;
    virtual int getDataSet(DataList &out) = 0;
    virtual int open()=0;
    virtual int close()=0;
};

#endif