#include <iostream>
#include <vector>
#include <set>
#include <assert.h>
#include <cmath>
#include <map>
#include <complex>
#include <gmpxx.h>
#include <algorithm>
#include "algebraic.hpp"

using namespace std;
template<class Qel>
class simplex{//this is a basic unoptimized implementation of the simplex algorithm
  public:
  vector<Qel> opt;
  vector<vector<Qel>> coefs;
  simplex(){}
  simplex(vector<Qel>& o,  vector<vector<Qel>>& c):opt(o), coefs(c){ }
  void pivoting(int i){
    Qel min=-1;
    int p=0;
    for(size_t j=0;j<coefs.size();j++){
      if(coefs[j][i] <= 0) continue;
      Qel v=coefs[j].back()/coefs[j][i];
      if(v<min || min ==-1){
        p = j; 
        min = v;
      }
    }
    Qel v = 1/coefs[p][i];
    for(size_t j=0; j<coefs[p].size(); j++) coefs[p][j] *= v;
    for(size_t k=0; k<coefs.size(); k++){
      if(k == p) continue;
      v = coefs[k][i];
      for(size_t j=0; j<coefs[k].size(); j++) coefs[k][j]-= coefs[p][j]*v;
    }
    v = opt[i];
    for(size_t j=0; j<opt.size(); j++) opt[j]-= coefs[p][j]*v;
  }

  Qel solve(){
    while(true){
      int pivot =0;
      Qel low = opt[0];
      for(size_t j=1; j+1<opt.size(); j++)  if(opt[j]> low){low =opt[j]; pivot =j; }
      if(low <= 0) break;
      pivoting(pivot);   
    }
    return opt.back();
  }  
};

template<class Qel>
bool isinconvexhull(vector<vector<Qel>>& points, vector<Qel>&p){// return true iff p is coordinatewise smaller than any point from the convex hull of points
  bool smaller = true, diff = true;
  for(size_t j=0;j<points.size();j++){//first we check if the point is coorinatewise smaller than one of the vertices, this way we call the simplex less often
    smaller = true;
    diff= false;
    for(size_t i=0; i<points[j].size(); i++){
      if(p[i]>points[j][i]) smaller = false;
      if(p[i]!=points[j][i]) diff = true;
    }
    if(smaller && diff) return true;
  }
  simplex<Qel> a;//now we call the simplex
  int vb = points.size()+2*p.size();
  a.coefs = vector<vector<Qel>>(p.size()+1,vector<Qel>(vb+1, 0));
  for(size_t j=0;j<p.size();j++){
    for(size_t i=0;i<points.size(); i++) a.coefs[j][i] = points[i][j];
    a.coefs[j][j+points.size()]=-1;
    a.coefs[j][j+points.size()+p.size()]=1;
    a.coefs[j].back() = p[j];
  }
  
  a.opt = vector<Qel>(vb+1, 0);
  for(size_t i=0; i<points.size()+p.size();i++)
    for(size_t j=0; j<a.coefs.size(); j++) a.opt[i]+= a.coefs[j][i];
  for(size_t j=0; j<a.coefs.size(); j++) a.opt.back()+= a.coefs[j].back();
  for(size_t i=0;i<points.size();i++) a.coefs[p.size()][i]=1;
  a.coefs[p.size()].back()=1;
  Qel tempS = a.solve();
  return (tempS ==0);
}

template<class Qel>
void findconvexhull(vector<vector<Qel>>& points){// we only keep the smallest subset of points that has the same conv< (the set Hull<)
  //the naive way to do that is to check for any point if it is in the conv< of the other points
  vector<Qel> p = points.back();
  points.pop_back();
  for(int k=points.size()-1; k>=0; k--){ 
    if(!isinconvexhull(points, p)){
      p.swap(points[k]);      
    }else{
      p.swap(points[k]);
      points[k].swap(points.back());
      points.pop_back();      
    }
  }
  if(!isinconvexhull(points, p)) points.push_back(p);
}

template<class Qel>
void auxnewVec(vector<vector<Qel> >& newsep, const vector<vector<Qel>> &sep, const vector<vector<vector<int>>>& operators, 
               set<vector<Qel>>& seen,vector<bool>& isNew, Qel& coef){
  //this function apply the operators when they are matrices to obtain the new points
  for(size_t i=0; i<sep.size(); i++){
    if(!(isNew[i])) continue;
    for(size_t j=0; j<operators.size(); j++){
      vector<Qel> u(sep[i].size(), 0);
      for(int n=0; n<operators[j].size(); n++)
        for(int m=0; m<operators[j][n].size(); m++)
          u[n]+= operators[j][n][m]*sep[i][m]/coef;
      bool ok = true;
      for(size_t k=0;k<newsep.size();k++) if(newsep[k] == u) ok = false;
      if(ok && seen.insert(u).second && !isinconvexhull(newsep, u)) newsep.push_back(u);
    }
  }
}

template<class Qel>
void auxnewVec(vector<vector<Qel> >& newsep, const vector<vector<Qel>> &sep, const vector<vector<vector<pair<int,int>>>>& operators, 
               set<vector<Qel>>& seen, vector<bool>& isNew, Qel& coef){
  //this function apply the operators when they are bilinear maps to obtain the new points
  for(size_t i=0; i<sep.size(); i++){
    for(size_t ii=0; ii<sep.size(); ii++){
      if(!(isNew[i] || isNew[ii])) continue;
      for(size_t j=0; j<operators.size(); j++){
        vector<Qel> u(sep[i].size(), 0);
        for(int n=0; n<operators[j].size(); n++)
          for(int m=0; m<operators[j][n].size(); m++) 
            u[n]+=sep[i][operators[j][n][m].first]*sep[ii][operators[j][n][m].second];
        bool ok= true;
        for(size_t k=0;k<newsep.size();k++) if(newsep[k]==u) ok = false;
        if(ok && seen.insert(u).second && !isinconvexhull(newsep, u)) newsep.push_back(u);
      }
    }
  }
}


template<class operator_Class, class Qel>
bool findX(operator_Class& operators, vector<vector<Qel>>& sep, Qel& coef){
  // This function implements the two algorithms from the article
  // We used template programming to have only one function that works for both linear and bilinear maps
  set<vector<Qel>> seen;
  for(size_t i=0; i<sep.size(); i++) seen.insert(sep[i]);
  vector<bool> isNew(sep.size(), true);

  for(int exec=0; exec<1000; exec++){
    vector<vector<Qel>> newsep(sep);
    auxnewVec(newsep, sep, operators, seen, isNew, coef); 
    findconvexhull(newsep);
    sort(newsep.begin(), newsep.end());
    isNew = vector<bool> (newsep.size(), true);
    
    bool done =true;
    for(size_t i=0; i<newsep.size(); i++){
      for(size_t j=0;j<sep.size();j++)
        if(newsep[i]==sep[j]) isNew[i] = false;
      if(isNew[i]) done = false;
    }
    sep.swap(newsep);
    if(done) break;
    cout<<"************************     Iteration  "<<exec<<":  The size of the convex hull is : "<<sep.size()<<endl;
    cout<<"The vertices of the convex hull are:"<<endl;
    for(size_t i=0; i<sep.size();i++){
      for(size_t j=0; j<sep[i].size();j++) cout<< sep[i][j]<<"   ";
      cout<<endl;
    }
  }
  cout<<"**************************  DONE!!! "<<endl<<"The size of the set X is : "<<sep.size()<<endl;
  for(size_t i=0; i<sep.size();i++){
    for(size_t j=0; j<sep[i].size();j++) cout<< sep[i][j]<<"   ";
    cout<<endl;
  }
	return true;
}
