/* Copyright (c) 1997-2005
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
   http://www.math.tu-berlin.de/polymake,  mailto:polymake@math.tu-berlin.de

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version: http://www.gnu.org/licenses/gpl.txt.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
*/

#ident "$Project: polymake $$Id: lrs_interface.cc,v 1.22 2005/11/10 10:25:16 thilosch Exp $"

#if defined(__GNUC__) && __GNUC__==3 && __GNUC_MINOR__<3 && !defined(__APPLE__) && !defined(__COMO__)
#pragma implementation
#endif

#include <lrs_interface.h>
#include <linalg_exceptions.h>
#include <ext/hash_set>
#include <ListMatrix.h>

#define GMP
extern "C" {
  #include <lrslib.h>
}
#undef positive
#undef negative
#undef zero
#undef one
#undef gcd
#undef copy
#undef sign

namespace polymake { namespace polytope { namespace lrs_interface {

class lrs_mp_vector_wrapper {
public:
   explicit lrs_mp_vector_wrapper(int n)
      : d(n-1), ptr(lrs_alloc_mp_vector(d)) {
      if (!ptr) throw std::bad_alloc();
   }

   ~lrs_mp_vector_wrapper() {
      lrs_clear_mp_vector(ptr,d);
   }

   operator lrs_mp_vector() { return ptr; }

   typedef mpz_ptr iterator;
   typedef mpz_srcptr const_iterator;
   typedef MP_INT& reference;
   typedef const MP_INT& const_reference;

   iterator begin() { return *ptr; }
   iterator end() { return begin()+d+1; }
   const_iterator begin() const { return *ptr; }
   const_iterator end() const { return begin()+d+1; }

   int size() const { return d+1; }
   bool empty() const { return d<0; }

   reference front() { return *(ptr[0]); }
   reference back() { return *(ptr[d]); }
   reference operator[] (int i) { return *(ptr[i]); }

   const_reference front() const { return *(ptr[0]); }
   const_reference back() const { return *(ptr[d]); }
   const_reference operator[] (int i) const { return *(ptr[i]); }

   operator Vector<Rational>() const {
      Vector<Rational> V(d+1);
      int first=0;
      // leading zeroes
      while (first<=d && !mpz_sgn(ptr[first])) ++first;
      // the rest is divided thru the first non-zero
      V[first]=1;
      for (int i=first+1; i<=d; ++i)
	 V[i].set(ptr[i],ptr[first]);
      return V;
   }
private:
   int d;
   lrs_mp_vector ptr;
};

class temp_Integer_vector {
public:
   explicit temp_Integer_vector(int n_arg)
      : n(n_arg), ptr(new mpz_t[n]) { }

   ~temp_Integer_vector() {
      delete[] ptr;
   }

   operator lrs_mp_vector() { return ptr; }

   typedef temp_Integer value_type;
   typedef temp_Integer* iterator;
   typedef temp_Integer& reference;
   typedef reference const_reference;

   iterator begin() {
      return reinterpret_cast<iterator>(ptr);
   }
   iterator end() {
      return reinterpret_cast<iterator>(ptr+n);
   }

   int size() const { return n; }
   bool empty() const { return n==0; }
private:
   int n;
   lrs_mp_vector ptr;
};

template <bool oriented>
class temp_Rational_vector : public GenericVector<temp_Rational_vector<oriented>, temp_Rational> {
public:
   temp_Rational_vector(const lrs_mp_vector_wrapper& v)
      : n(v.size()), ptr(new mpq_t[n]) {
      lrs_mp_vector_wrapper::const_iterator first=v.begin(), src_end=v.end();
      mpq_ptr dst=ptr[0];
      int s=0;

      // leading zeroes
      for (; first != src_end && !(s=mpz_sgn(first));  ++first, ++dst) {
	 mpz_init_set_si(mpq_numref(dst),0);
	 mpz_init_set_ui(mpq_denref(dst),1);
      }

      // the rest is divided thru the first non-zero (or its abs if oriented)
      lrs_mp_vector_wrapper::const_iterator src=first;
      mpz_init_set_si(mpq_numref(dst),oriented ? s : 1);
      mpz_init_set_ui(mpq_denref(dst),1);

      mpz_t abs_first;
      if (oriented && s<0) first=Integer::_tmp_negate(abs_first,first);

      for (++src, ++dst;  src != src_end;  ++src, ++dst) {
	 mpz_init_set(mpq_numref(dst),src);
	 mpz_init_set(mpq_denref(dst),first);
	 mpq_canonicalize(dst);
      }
   }

   ~temp_Rational_vector() {
      if (ptr) {
	 for (int i=0; i<n; ++i)
	    mpq_clear(ptr[i]);
	 delete[] ptr;
      }
   }

   temp_Rational_vector(const temp_Rational_vector& v)
      : n(v.n), ptr(v.ptr) {
      v.ptr=0;
   }

   typedef temp_Rational value_type;
   typedef temp_Rational& reference;
   typedef temp_Rational* iterator;
   typedef reference const_reference;
   typedef iterator const_iterator;
   typedef std::reverse_iterator<iterator> reverse_iterator;
   typedef reverse_iterator const_reverse_iterator;

   iterator begin() const {
      if (n>0) n=-n;
      return reinterpret_cast<iterator>(ptr);
   }
   iterator end() const {
      return reinterpret_cast<iterator>(ptr+size());
   }
   reverse_iterator rbegin() const {
      return reverse_iterator(end());
   }
   reverse_iterator rend() const {
      return reverse_iterator(begin());
   }

   mpq_srcptr const_begin() const { return ptr[0]; }
   mpq_srcptr const_end() const { return ptr[size()]; }

   int size() const { return std::abs(n); }
   bool empty() const { return n==0; }

   bool operator== (const temp_Rational_vector& v) const {
      if (size() != v.size()) return false;
      for (mpq_srcptr src1=const_begin(), end1=const_end(), src2=v.const_begin(); 
	   src1 != end1;  ++src1, ++src2) {
	 if (mpq_cmp(src1,src2)) return false;
      }
      return true;
   }
   bool operator!= (const temp_Rational_vector& v) const {
      return !operator==(v);
   }
private:
   mutable int n;
   mutable mpq_t *ptr;
};

} } }

namespace std_ext {
   template <bool oriented>
   struct hash< polymake::polytope::lrs_interface::temp_Rational_vector<oriented> > : hash<MP_RAT> {
      size_t operator() (const polymake::polytope::lrs_interface::temp_Rational_vector<oriented>& v) const {
	 size_t result=0;
	 int i=1;
	 for (mpq_srcptr src=v.const_begin(), src_end=v.const_end();
	      src != src_end;  ++src, ++i)
	    result += i * _do(src);
	 return result;
      }
   };
}

namespace polymake { namespace polytope { namespace lrs_interface {

class temp_Integer_matrix {
public:
   temp_Integer_matrix(lrs_mp_matrix A, int m_arg, int n_arg)
      : ptr(A), m(m_arg), n(n_arg) { }

   ~temp_Integer_matrix() {
      if (ptr) {
	 for (lrs_mp_vector *row=ptr, *row_end=row+m; row!=row_end; ++row)
	    free(*row);
	 free(ptr);
      }
   }

   class it {
   public:
      typedef std::forward_iterator_tag iterator_category;
      typedef temp_Integer value_type;
      typedef value_type* pointer;
      typedef value_type& reference;
      typedef ptrdiff_t difference_type;
      typedef it iterator;
      typedef it const_iterator;

      it() { }
      it(lrs_mp_vector *row_arg, int n_arg)
	 : row(row_arg), i(0), n(n_arg) { }

      reference operator* () const { return reinterpret_cast<reference>(*(*row)[i]); }
      pointer operator-> () const { return &(operator*()); }

      iterator& operator++ () {
	 if (++i==n) i=0, ++row;
	 return *this;
      }
      const iterator operator++ (int) { iterator copy=*this; operator++(); return copy; }

      bool operator== (const iterator& it) const {
	 return row==it.row && i==it.i;
      }
      bool operator!= (const iterator& it) const { return !operator==(it); }
   private:
      lrs_mp_vector *row;
      int i, n;
   };

   typedef it iterator;
   iterator begin() { return iterator(ptr,n); }
   iterator end() { return iterator(ptr+m,n); }
private:
   lrs_mp_matrix ptr;
   int m, n;
};

class solver::initializer {
public:
   initializer() { lrs_mp_init(0,0,0); }
   ~initializer() { lrs_mp_close(); }
};

solver::solver() {
   static initializer init;
}

struct solver::dictionary {
   lrs_dat *Q;
   lrs_dic_struct *P;

   ~dictionary() {
      lrs_free_dic(P,Q);
      lrs_free_dat(Q);
   }

   void set_matrix(const Matrix<Rational>& A, int start_row=0, bool ge=true)
   {
      const int n=A.cols();
      temp_Integer_vector num(n), den(n);
      const Rational *x=concat_rows(A).begin();

      // lrs enumerates rows starting with 1
      for (int r=start_row+1, r_end=r+A.rows(); r != r_end; ++r) {
	 for (mpz_ptr num_x=num.begin(), den_x=den.begin(), num_end=num_x+n;  num_x != num_end;  ++x, ++num_x, ++den_x) {
	    *num_x = *mpq_numref(x->get_rep());
	    *den_x = *mpq_denref(x->get_rep());
	 }
	 lrs_set_row_mp(P,Q,r,num,den,ge);
      }
   }

   void set_obj_vector(const Vector<Rational>& V, bool maximize)
   {
      const int n=V.size();
      DIMENSION_CHECK((n!=Q->n), "lrs_interface::solver - inequalities/objective dimension mismatch");
      temp_Integer_vector num(n), den(n);
      const Rational *x=V.begin();

      for (mpz_ptr num_x=num.begin(), den_x=den.begin(), num_end=num_x+n;
	   num_x != num_end;  ++x, ++num_x, ++den_x) {
	 *num_x = *mpq_numref(x->get_rep());
	 *den_x = *mpq_denref(x->get_rep());
      }
      lrs_set_obj_mp(P,Q,num,den,maximize);
      Q->lponly=1;
   }

   // primal case
   dictionary(const Matrix<Rational>& Points)
   {
      Q=lrs_alloc_dat("");
      if (!Q) throw std::bad_alloc();
      Q->m=Points.rows();
      Q->n=Points.cols();
      Q->hull=1;

      P=lrs_alloc_dic(Q);
      if (!P) {
	 lrs_free_dat(Q);
	 throw std::bad_alloc();
      }
      set_matrix(Points);
   }

   // dual case
   dictionary(const Matrix<Rational>& Inequalities, const Matrix<Rational>& Equations)
   {
      DIMENSION_CHECK((Equations.rows() && Inequalities.cols() != Equations.cols()),
		      "lrs_interface::solver - inequalities/equations dimension mismatch");
      Q=lrs_alloc_dat("");
      if (!Q) throw std::bad_alloc();
      Q->m=Inequalities.rows()+Equations.rows();
      Q->n=Inequalities.cols();
      Q->hull=0;

      P=lrs_alloc_dic(Q);
      if (!P) {
	 lrs_free_dat(Q);
	 throw std::bad_alloc();
      }
      set_matrix(Inequalities);
      set_matrix(Equations,Inequalities.rows(),false);
   }

   enum _filter_nothing { filter_nothing };
   enum _filter_bounded { filter_bounded };
   enum _filter_rays { filter_rays };
   enum _filter_facets { filter_facets };

   Matrix<Rational> get_solution_matrix(_filter_nothing)
   {
      ListMatrix< temp_Rational_vector<true> > facets;

      lrs_mp_vector_wrapper output(Q->n);
      do {
	 for (int col=0; col <= P->d; ++col)
	    if (lrs_getsolution(P, Q, output, col))
	       facets /= temp_Rational_vector<true>(output);
      } while (lrs_getnextbasis (&P, Q, 0));

      return Matrix<Rational>(facets.rows(), facets.cols(), entire(rows(facets)));
   }

   long count_solutions(_filter_nothing)
   {
      long facets=0;

      lrs_mp_vector_wrapper output(Q->n);
      do {
	 for (int col=0; col <= P->d; ++col)
	    if (lrs_getsolution(P, Q, output, col))
	       ++facets;
      } while (lrs_getnextbasis (&P, Q, 0));

      return facets;
   }

   Matrix<Rational> get_solution_matrix(_filter_rays)
   {
      ListMatrix< temp_Rational_vector<false> > vertices;
      std_ext::hash_set< temp_Rational_vector<false> > rays;

      lrs_mp_vector_wrapper output(Q->n);
      do {
	 for (int col=0; col <= P->d; ++col)
	    if (lrs_getsolution(P, Q, output, col)) {
	       if (!mpz_sgn(output.begin())) {
		  rays.insert(temp_Rational_vector<false>(output));
	       } else {
		  vertices /= temp_Rational_vector<false>(output);
	       }
	    }
      } while (lrs_getnextbasis (&P, Q, 0));

      return Matrix<Rational>(rays.size()+vertices.rows(), vertices.cols(), entire(concatenate(rays,rows(vertices))));
   }

   std::pair<long,long> count_solutions(_filter_rays)
   {
      std::pair<long,long> vertices(0,0);
      std_ext::hash_set< temp_Rational_vector<false> > rays;

      lrs_mp_vector_wrapper output(Q->n);
      do {
	 for (int col=0; col <= P->d; ++col)
	    if (lrs_getsolution(P, Q, output, col)) {
	       if (mpz_sgn(output.begin())) {
		  ++vertices.second;
	       } else {
		  rays.insert(temp_Rational_vector<false>(output));
	       }
	    }
      } while (lrs_getnextbasis (&P, Q, 0));

      vertices.first=vertices.second+rays.size();
      return vertices;
   }

   long count_solutions(_filter_bounded)
   {
      long vertices=0;

      lrs_mp_vector_wrapper output(Q->n);
      do {
	 for (int col=0; col <= P->d; ++col)
	    if (lrs_getsolution(P, Q, output, col)) {
	       if (mpz_sgn(output.begin()))
		  ++vertices;
	    }
      } while (lrs_getnextbasis (&P, Q, 0));

      return vertices;
   }

   Matrix<Rational> get_solution_matrix(_filter_facets)
   {
      std_ext::hash_set< temp_Rational_vector<true> > facets(Q->m * Q->n);

      lrs_mp_vector_wrapper output(Q->n);
      do {
	 for (int col=0; col <= P->d; ++col)
	    if (lrs_getsolution(P, Q, output, col))
	       facets.insert(temp_Rational_vector<true>(output));
      } while (lrs_getnextbasis (&P, Q, 0));

      return Matrix<Rational>(facets.size(), Q->n, entire(facets));
   }
};

solver::matrix_pair
solver::enumerate_facets(const Matrix<Rational>& Points)
   throw (std::bad_alloc, infeasible)
{
   dictionary D(Points);
   D.Q->polytope= attach_selector(Points.col(0), operations::logical_not()).empty();

   lrs_mp_matrix Lin=0;
   if (!lrs_getfirstbasis(&D.P, D.Q, &Lin, 1)) throw infeasible();

   const Matrix<Rational> AH(D.Q->nredundcol, D.Q->n, temp_Integer_matrix(Lin, D.Q->nredundcol, D.Q->n).begin()),
                          F= AH.rows()+1==AH.cols()
                             ? Matrix<Rational>()	// lrs does not treate the special case of a single point correctly
                           : D.Q->polytope
                             ? D.get_solution_matrix(dictionary::filter_nothing)
                             : D.get_solution_matrix(dictionary::filter_facets);
   return matrix_pair(F,AH);
}

long
solver::count_facets(const Matrix<Rational>& Points)
   throw (std::bad_alloc, infeasible, unbounded)
{
   dictionary D(Points);
   if (! attach_selector(Points.col(0), operations::logical_not()).empty())
      throw unbounded("count_facets is not applicable to unbounded polyhedra");

   lrs_mp_matrix Lin=0;
   if (!lrs_getfirstbasis(&D.P, D.Q, &Lin, 1)) throw infeasible();

   return D.Q->nredundcol+1==D.Q->n
          ? 0	// lrs does not treate the special case of a single point correctly
          : D.count_solutions(dictionary::filter_nothing);
}

Matrix<Rational>
solver::enumerate_vertices(const Matrix<Rational>& Inequalities, const Matrix<Rational>& Equations, const bool accept_non_pointed)
   throw (std::bad_alloc, infeasible, not_pointed)
{
   dictionary D(Inequalities, Equations);

   lrs_mp_matrix Lin=0;
   if (!lrs_getfirstbasis(&D.P, D.Q, &Lin, 1)) throw infeasible();
   if (Lin) lrs_clear_mp_matrix(Lin, D.Q->nredundcol, D.Q->n);
   if (!accept_non_pointed && D.Q->nredundcol) throw not_pointed();

   return D.get_solution_matrix(dictionary::filter_rays);
}

std::pair<long,long>
solver::count_vertices(const Matrix<Rational>& Inequalities, const Matrix<Rational>& Equations,
		       bool only_bounded)
   throw (std::bad_alloc, infeasible, not_pointed)
{
   dictionary D(Inequalities, Equations);

   lrs_mp_matrix Lin=0;
   if (!lrs_getfirstbasis(&D.P, D.Q, &Lin, 1)) throw infeasible();
   if (Lin) lrs_clear_mp_matrix(Lin, D.Q->nredundcol, D.Q->n);
   if (D.Q->nredundcol) throw not_pointed();

   return only_bounded ? std::pair<long,long>(0, D.count_solutions(dictionary::filter_bounded))
                       : D.count_solutions(dictionary::filter_rays);
}

std::pair< Bitset, Matrix<Rational> >
solver::find_vertices_among_points(const Matrix<Rational>& Points)
   throw (std::bad_alloc, infeasible)
{
   dictionary D(Points);

   lrs_mp_matrix Lin=0;
   if (!lrs_getfirstbasis(&D.P, D.Q, &Lin, 1)) throw infeasible();
   const Matrix<Rational> AH(D.Q->nredundcol, D.Q->n, temp_Integer_matrix(Lin, D.Q->nredundcol, D.Q->n).begin());

   Bitset V(Points.rows());
   for (int index=D.Q->lastdv+1, end=D.P->m+D.P->d; index<=end; ++index)
      if (!checkindex(D.P,D.Q,index))
	 V += D.Q->inequality[index - D.Q->lastdv]-1;

   return std::pair< Bitset, Matrix<Rational> >(V,AH);
}

Vector<Rational>
solver::find_a_vertex(const Matrix<Rational>& Inequalities, const Matrix<Rational>& Equations)
   throw (std::bad_alloc, infeasible, not_pointed)
{
   dictionary D(Inequalities, Equations);

   lrs_mp_matrix Lin=0;
   if (!lrs_getfirstbasis(&D.P, D.Q, &Lin, 1)) throw infeasible();
   if (Lin) lrs_clear_mp_matrix(Lin, D.Q->nredundcol, D.Q->n);
   if (D.Q->nredundcol) throw not_pointed();

   lrs_mp_vector_wrapper output(D.Q->n);
   for (int col=0; col <= D.P->d; ++col)
      if (lrs_getsolution(D.P,D.Q,output,col)) break;

   return output;
}

solver::feasibility_solution
solver::check_feasibility(const Matrix<Rational>& Inequalities, const Matrix<Rational>& Equations)
   throw (std::bad_alloc)
{
   dictionary D(Inequalities, Equations);

   lrs_mp_matrix Lin=0;
   bool feasible=lrs_getfirstbasis(&D.P, D.Q, &Lin, 1);
   if (Lin) lrs_clear_mp_matrix(Lin, D.Q->nredundcol, D.Q->n);

   lrs_mp_vector_wrapper output(D.Q->n);
   for (int col=0; col <= D.P->d; ++col)
      if (lrs_getsolution(D.P,D.Q,output,col)) break;
   return feasibility_solution(feasible, output);
}

solver::lp_solution
solver::solve_lp(const Matrix<Rational>& Inequalities, const Matrix<Rational>& Equations,
		 const Vector<Rational>& Objective, bool maximize)
   throw (std::bad_alloc, infeasible, unbounded)
{
   dictionary D(Inequalities, Equations);
   D.set_obj_vector(Objective, maximize);

   lrs_mp_matrix Lin=0;
   if (!lrs_getfirstbasis(&D.P, D.Q, &Lin, 1)) throw infeasible();
   if (Lin) lrs_clear_mp_matrix(Lin, D.Q->nredundcol, D.Q->n);
   if (D.Q->unbounded) throw unbounded();

   lrs_mp_vector_wrapper output(D.Q->n);
   for (int col=0; col <= D.P->d; ++col)
      if (lrs_getsolution(D.P,D.Q,output,col)) break;

   return lp_solution(Rational(D.Q->objnum, D.Q->objden), output);
}

} } }
