#ifndef DATAPROV_H
#define DATAPROV_H

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

// Training data(Rid):
//  First line: the total number of features(fsize)
//  from the second line to last-1
//      nametag(string) fsize number of features for nametag
//          possible: "-1" nametag mean don't care(partially implemented, not sure)
//  last line(delimiter to terminate data read): 0

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;}
    void addEntry(DataEntry* d){
        int ext=false;
        all.push_back(d);
        if (d->qid=="-1") {
            other.push_back(d);
            return;
        }
        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);
            d->rank=uniq.size()-1;
        }
    }
    // A lot of getters
    inline int getfSize(){return n;}
    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 double getBha(int x){
        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)
        {
            double acc=0;
            for (int j=0;j<16;++j,++i)
                acc += sqrt((*id)[i] * (*oth)[i]);
            res-=log(acc+1e-30);
        }
        return res;
    }
    inline double getHell(int x){
        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)
        {
            double acc=0;
            for (int j=0;j<16;++j,++i)
                acc += sqrt((*id)[i] * (*oth)[i]);
            res+=sqrt(1-acc);
        }
        return res;
    }
    // master cal -> possible multiplexing
    inline double cal(Eigen::VectorXd *id,Eigen::VectorXd *oth,int i) {
        return fabs((*id)[i] - (*oth)[i]);
    }
    // TODO getvec as VectorXd -> deprecating due to performance issues
    inline Eigen::VectorXd getVec(int 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);
        }
        Eigen::VectorXd res(n);
        for (int i=0;i<n;++i)
            res(i)=cal(id,oth,i);
        return res;
    };
    // w*Vec -> linear factor
    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 += cal(id,oth,i)*w[i];
        return res;
    }
    // w*Vec -> linear factor
    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] += cal(id,oth,i)*w;
    }
    // get label of vector x
    inline double getL(int x){
        int a,b,q=getqSize();
        a=x/q;
        b=x%q;
        if (single)
        {
            if (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