#include "parser.h"
#include "printer.h"
#include "polynomial.h"
#include "buchberger.h"
#include "reversesearch.h"
#include "termorder.h"
#include "genericwalk.h"
#include "gfanapplication.h"
#include "field_rationalfunctions2.h"
#include "log.h"

#include "division.h"
#include "buchberger.h"

#include <utility>

class HashemiKapurApplication : public GFanApplication
{
public:
  const char *helpText()
  {
    return "This program tests the Hashemi Kapur algorithm for groebner basis conversion.\n";
  }
  HashemiKapurApplication()
  {
    registerOptions();
  }

  const char *name()
  {
    return "_hashemikapur";
  }

  std::pair<vector<Polynomial>,vector<vector<Polynomial>>> extendedBuchberger(PolynomialSet F, TermOrder &order)
		  {
	  	  	  vector<Polynomial> G;

	  	  	  for(auto &f:F)if(!f.isZero())G.push_back(f);else{assert(0);}
	  	  	  for(auto &g:G)g.mark(order);
	  	  	  vector<vector<Polynomial>> C;
	  	  	  for(int i=0;i<G.size();i++)
	  	  	  {
	  	  		  C.push_back(vector<Polynomial>(G.size(),F.getRing()));
	  	  		  C.back()[i]=F.getRing().one();
	  	  	  }

#if 1
	  	  	  auto cmp = [](IntegerVector const &a, IntegerVector const &b)
	  	  			  {
	  	  		  	  	  auto A=a.sum();
	  	  		  	  	  auto B=b.sum();
	  	  		  	  	  if(A<B)return true;
	  	  		  	  	  if(B<A)return false;
	  	  		  	  	  return a<b;
	  	  			  };
	  	  	  multimap<IntegerVector,pair<int,int>,decltype(cmp)> P;
#else
	  	  	  multimap<IntegerVector,pair<int,int>> P;
#endif
	  	  	  for(int i=0;i<G.size();i++)
		  	  	  for(int j=0;j<i;j++)
		  	  		  P.insert(
		  	  				  std::pair<IntegerVector,std::pair<int,int>>(
		  	  						  max(G[i].getMarked().m.exponent,G[j].getMarked().m.exponent),
									  std::pair<int,int>(i,j)
									  )
								);
	  	  	  while(!P.empty())
	  	  	  {
	  	  		  auto p=*P.begin();
	  	  		  P.erase(P.begin());
	  	  		  auto tri=sPolynomialExtended(G[p.second.first],G[p.second.second]);
	  	  		  auto spoly=tri.first;
	  	  		  if(!relativelyPrime(G[p.second.first].getMarked().m.exponent, G[p.second.second].getMarked().m.exponent))
	  	  		  if(!spoly.isZero())
	  	  		  {
	  	  			  PolynomialSet GSet(F.getRing());
	  	  			  for(auto &g:G)GSet.push_back(g);
	  	  			  PolynomialSet qSet(F.getRing());
	  	  			  Polynomial h=division(spoly, GSet, order, &qSet);
	  	  			  if(!h.isZero())
	  	  			  {
	  	  				  h.mark(order);
	  			  	  	  for(int j=0;j<G.size();j++)
	  			  	  		  P.insert(
			  	  				  std::pair<IntegerVector,std::pair<int,int>>(
			  	  						  max(h.getMarked().m.exponent,G[j].getMarked().m.exponent),
										  std::pair<int,int>(G.size(),j)
										  )
									);
	  		  	  		  C.push_back(vector<Polynomial>(G.size(),F.getRing()));
	  			  	  	  G.push_back(h);
	  		  	  		  C.back()[p.second.first]=tri.second.first;
	  		  	  		  C.back()[p.second.second]=tri.second.second;
	  		  	  		  int i=0;
	  		  	  		  for(auto &k:qSet)
	  		  	  		  {
	  		  	  			  C.back()[i]+=k;
	  		  	  			  i++;
	  		  	  		  }
	  	  			  }
	  	  		  }
	  	  	  }
	  	  	  return std::pair<vector<Polynomial>,vector<vector<Polynomial>>>(G,C);


		  }


  Polynomial tp(Polynomial const &p,TermOrder &sigma1, TermOrder &sigma2)
  {
	  Polynomial ret=p.getRing().zero();
	  Polynomial p2=p;
	  p2.mark(sigma1);
	  Term a=p2.getMarked();
//	  pout<<a<<"\n";
	  for(auto i=p.terms.begin();i!=p.terms.end();i++)
	  {
		  if(!sigma2(i->first.exponent,a.m.exponent))
			  ret+=Term(i->second,Monomial(p.getRing(),i->first.exponent));
	  }
	  return ret;
  }
  PolynomialSet tp(PolynomialSet const &g, TermOrder &sigma1, TermOrder &sigma2)
  {
	  PolynomialSet ret(g.getRing());
	  for(auto &a:g)
		  ret.push_back(tp(a,sigma1,sigma2));
	  return ret;
  }

  void print(vector<Polynomial> const &v)
  {
	  pout<<"(\n";
	  for(int i=0;i<v.size();i++)
	  {
		  pout<<i<<":"<<v[i]<<"\n";
	  }
	  pout<<")\n";
  }
  void print(vector<vector<Polynomial>> const &v)
  {
	  pout<<"(\n";
	  for(int i=0;i<v.size();i++)
	  {
		  pout<<i<<":";
		  for(int j=0;j<v[i].size();j++)
			  pout<<v[i][j]<<",";
		  pout<<"\n";
	  }
	  pout<<")\n";
  }

  int main()
  {
/*	 1 1 1 1
	 0 0 0 -1
	 0 0 -1 0
	 0 -1 0 0
	 100000,999999,999900,990000*/
	  TermOrder *myOrder;
    StringParser sp("Q[x1,x2,x3,x4]{x1^4 + x1^2*x3 + x1*x2*x3 + x2^2*x3^2, -x1^3 + x1*x2^2*x3 - x1*x3, x1 + x2^3*x3, x1^2*x2 + x1 + x2*x3, x1 + x4^2}(1000000,999999,999900,990000)(4,4,2,3)(100000,999999,999900,990000)(990000,999900,999999,1000000)(1,1,1,1)");
    PolynomialSet g=sp.parsePolynomialSetWithRing();

    pout<<"Input:\n";
    pout.printPolynomialSet(g);

    IntegerVector w=sp.parseIntegerVector();

    AsciiPrinter(Stdout).printPolynomialRing(g.getRing());
    printf("\n");

    WeightReverseLexicographicTermOrder order1(w);
    LexicographicTermOrder order2;

    pout<<"Lex GB (DESIRED):\n";
	buchberger(&g,order2);
	g.scaleMarkedCoefficientsToOne();
	autoReduce(&g,LexicographicTermOrder());
	auto desired=g;
    AsciiPrinter(Stdout).printPolynomialSet(g);

    pout<<"Graded rev lex GB (START):\n";
    buchberger(&g,order1);
	g.scaleMarkedCoefficientsToOne();
	autoReduce(&g,LexicographicTermOrder());
    AsciiPrinter(Stdout).printPolynomialSet(g);

if(0)    {//testing tp:
        WeightReverseLexicographicTermOrder order1(StringParser("(99,100)").parseIntegerVector());
        LexicographicTermOrder order2;
    	auto f=StringParser("Q[x1,x2]x1^2+x1+x2+x2^2+1").parsePolynomialWithRing();
    	pout<<f<<"\n";
    	pout<<tp(f, order1, order2)<<"\n";
    	pout<<tp(f, order2, order1)<<"\n";
    }

	list<PolynomialSet> monomialIdeals;
{
		auto G1=g;
		auto G2=G1;
		for(int K=0;K<6;K++)
		{
			vector<Polynomial> G2vec;for(auto &f:G2)G2vec.push_back(f);
			pout<<"Iteration "<<K<<" -------------------------\n";
			pout<<"G2:\n";
			print(G2vec);

			auto F=tp(G2,order1,order2);
			pout<<"Marked in target ordering:\n";
			G2.mark(order2);
			pout<<G2;
			monomialIdeals.push_back(G2.markedTermIdeal());
			pout<<"F\n";
			pout<<F;
			auto GC=extendedBuchberger(F,order2);

			pout<<"G:\n";
			print(GC.first);
			pout<<"C:\n";
			print(GC.second);

/*			for(auto &v:GC.second)
			{
				Polynomial p(G1.getRing());
				for(int i=0;i<v.size();i++)
					p+=GC.first[i]*v[i];
				pout<<p<<"\n";
			}*/

			int t=G2vec.size();
			int j=0;
			for(auto &v:GC.second)
			{
				if(j>=t)
					{
						Polynomial p(G1.getRing());

						for(int i=0;i<v.size();i++)
							p+=G2vec[i]*v[i];
						G2vec.push_back(p);
					}
				j++;
			}



//			print(GC.first);
			pout<<"G2(Lift):\n";
			print(G2vec);
			G2=PolynomialSet(G1.getRing());
			for(auto &f:G2vec)
				if(!f.isZero())
					G2.push_back(f);
			G2.mark(order2);
//			autoReduce(&G2,order2);
			autoReduceNonGB(&G2, order2);
			pout<<"G2(Interreduced):\n";
			pout<<G2;
		}
    }

	pout<<"------------------------------\n";
	for(auto &m:monomialIdeals)
	{
		pout<<m;
	}
	pout<<"Desired:\n"<<desired;
    return 0;
  }
};

static HashemiKapurApplication theApplication;
