/* Copyright (c) 1997-2004
   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.
*/

#include <cstdlib>
#include <ctime>
#include <Poly.h>
#include <Array.h>
#include <Vector.h>
#include <Matrix.h>
#include <list>
#include <Fibonacci.h>

namespace polymake
{
   namespace polytope
   {
      /*************************************************************************/
      /*                      k -> cd  transformation                          */
      /*************************************************************************/

      /* class ivec
       * represents a p-tuple (i_1,...,i_p) such that
       * m_{j-1} <= i_j <= m_j - 2  for a given m-vector (m_0,...,m_p)
       * iterates via ++ over all p-tuples with this property
       */
      class ivec
      {
	 private:
	    Array<int> iota;
	    Array<int> mvec;
	    bool is_at_end;

	 public:
	    // (i_1,...,i_p), starts with (m_0,...,m_{p-1})
	    ivec (Array<int> m = Array<int>(1))
	    {
	       mvec = m;
	       iota = Array<int> (mvec.size());
	       reset();
	    }

	    void reset()
	    {
	       iota[0] = 0;
	       for (int i = 1; i < iota.size(); ++i)
		  iota[i] = mvec[i-1];
	       is_at_end = false;
	    }

	    bool at_end () const { return is_at_end; }

	    ivec& operator++ ()
	    {
	       if (!at_end())
		  for (int i = 1; i < iota.size(); ++i)
		     if (iota[i] == mvec[i]-2)
		     {
			if (i == iota.size()-1)
			   is_at_end = true;
			else
			   iota[i] = mvec[i-1];
		     }
		     else
		     {
			++iota[i];
			break;
		     }
	       return *this;
	    }

	    int ksign () const
	    {
	       int exponent = 0;
	       for (int i = 1; i < iota.size(); ++i)
		  exponent += (mvec[i]-iota[i]);
	       return (exponent%2==0 ? 1 : -1);

	       // better (for later):
	       // calculate the starting sign in reset()
	       // and update it with every ++
	    }

	    int to_idx (Fibonacci& Fib) const
	    {
	       int idx = 0;
	       for (int i = 1; i < iota.size(); ++i)
		  idx += Fib.number(iota[i]+1);
	       return idx;
	    }
      };
      /*
       *  end class ivec
       */


      /* calculates the m-vector for a given monomial */
      Array<int> m_vector (int mon, int d, Fibonacci& Fib)
      {
	 std::list<int> n;
	 int no_of_c = 0;
	 while (d > 1)
	 {
	    if (mon >= Fib.number(d-1))
	    {
	       mon -= Fib.number(d-1);
	       d -= 2;
	       n.push_back (no_of_c);
	       no_of_c = 0;
	    }
	    else
	    {
	       --d;
	       ++no_of_c;
	    }
	 }

	 int p = n.size();
	 Array<int> m (p+1); // p+2 ?
	 m[0] = 0;
	 std::list<int>::const_iterator lit = n.begin();
	 for (int i = 1; i <= p; ++i, ++lit)
	    m[i] = m[i-1] + (*lit) + 2;

	 return m;
      }

      /* calculates the row adjoined to the given monomial of the matrix below */
      Vector<int> kvec_combination (int mon, int d, Fibonacci& Fib)
      {
	 Vector<int> r (Fib.number(d));

	 if (mon==0)		// mon is ccc...c
	    r[0] = 1;
	 else			// mon contains at least one d
	 {
	    Array<int> m = m_vector (mon, d, Fib);
	    for (ivec i(m); !i.at_end(); ++i)
	       r[i.to_idx(Fib)] += i.ksign();
	 }

	 return r;
      }

      /*************************************************************************/
      /*                     flag -> k transformation                          */
      /*************************************************************************/

      /* class subset_iterator
       * iterates via ++ over all subsets of a sparse set, given by a sum of Fibonacci numbers
       */
      class subset_iterator
      {
	 private:
	    int spset;
	    int subset;
	    int coeff;
	    int dim;
	    Fibonacci Fib;

	 public:
	    subset_iterator ()
	    {
	       cout << "default constructor -- should NOT be used!\n";
	       spset = subset = coeff = 0;
	       dim = 0;
	       Fib = Fibonacci(0);
	    }

	    subset_iterator (int start_set, int d, Fibonacci& Fibnumbers)
	    {
	       spset = subset = start_set;
	       dim = d;
	       Fib = Fibnumbers;
	       coeff = 1;
	    }

	    bool at_end () const { return (coeff==0); }

	    int operator* () const { return subset; }

	    int coefficient () const { return coeff; }

	    subset_iterator& operator++ ()
	    {
	       if (!at_end())
	       {
		  int d = dim;
		  int spsetcopy = spset;
		  for ( ; d > 0; --d)
		     if (spsetcopy >= Fib.number(d-1))
		     {
			spsetcopy -= Fib.number(d-1);
			if (subset >= Fib.number(d-1))
			   break;
		     }
		  if (d > 0)
		  {
		     subset -= Fib.number(d-1);
		     coeff *= -2;
		     spsetcopy = spset;
		     for (int i = dim; i > d; --i)
			if (spsetcopy >= Fib.number(i-1))
			{
			   subset += Fib.number(i-1);
			   coeff /= -2;
			   spsetcopy -= Fib.number(i-1);
			}
		  }
		  else		// then subset is already 0 (?)
		     coeff = 0;
	       }
	       return *this;
	    }
      };

      Vector<int> flag_combination (int spset, int d, Fibonacci& Fib)
      {
	 Vector<int> r (Fib.number(d));

	 for (subset_iterator sit (spset,d,Fib); !sit.at_end(); ++sit)
	    r[*sit] += sit.coefficient();

	 return r;
      }

      /*************************************************************************/
      /*                     transformation calculations                       */
      /*************************************************************************/

      enum transformation_type { flag_to_kvec, kvec_to_cd };

      /* calculates the transformation matrices for the given dimension;
       * flag-vector -> k-vector  for t == flag_to_kvec,
       * k-vector -> cd-index     for t == kvec_to_cd
       */
      Matrix<int> transformation_matrix (int d, transformation_type t, Fibonacci& Fib)
      {
	 Matrix<int> T(Fib.number(d),Fib.number(d));

	 if (d==0)
	    T(0,0) = 1;
	 else
	 {
	    int objit = 0;
	    for (Rows<Matrix<int> >::iterator rit = rows(T).begin(); !rit.at_end(); ++rit, ++objit)
	       switch (t)
	       {
		  case kvec_to_cd:
		     *rit = kvec_combination (objit, d, Fib);
		     break;
		  case flag_to_kvec:
		     *rit = flag_combination (objit, d, Fib);
		     break;
		  default:
		     throw std::runtime_error(std::string("cd_index: unknown transformation type"));
	       }
	 }

	 return T;
      }

      /*************************************************************************/
      /*                        cd-index calculation                           */
      /*                                                                       */
      /* @see Billera, Ehrenborg, Monotonicity of the cd-index for polytopes,  */
      /*                          Math.Z. 233 (2000), 421-441, section 7       */
      /*************************************************************************/
      void cd_index (Poly& p)
      {
	 int d = p.give("DIM");
	 Vector<int> flag = p.give("FLAG_VECTOR");

	 // precalculate the fibonacci-numbers
	 Fibonacci Fib(d+1);

	 // transform the flag vector into the k-vector
	 Matrix<int> T1 = transformation_matrix (d, flag_to_kvec, Fib);
#ifdef CDINDEX_DEBUG
	 cout << "f->k:\n" << T1 << "\n";
#endif
	 Vector<int> kvec = T1 * flag;

	 // transform the k-vector into the cd-index
	 Matrix<int> T2 = transformation_matrix (d, kvec_to_cd, Fib);
#ifdef CDINDEX_DEBUG
	 cout << "k->cd:\n" << T2 << "\n";
#endif
	 Vector<int> cd = T2 * kvec;

#ifdef CDINDEX_DEBUG
	 cout << "both:\n" << (T2*T1) << "\n";
	 cout << "CD_INDEX_COEFFICIENTS: " << cd << "\n";
#endif
	 p.take("CD_INDEX_COEFFICIENTS") << cd;
      }
   }
}


using namespace polymake;

/** @file cd_index
 *  Calculate the cd-index from (the non-redundant part of) the flag vector of a polytope.
 *
 *  @synopsis cd_index <file>
 *
 *  @reading  DIM, FLAG_VECTOR
 *  @writing  CD_INDEX_COEFFICIENTS
 *
 *  @index clients Combinatorics
 *  @author Axel Werner
 */

int main(int argc, const char *argv[])
{
   if (argc<2 || argc>2) {
      cerr << "usage: " << argv[0] << " <file>" << endl;
      return 1;
   }
   try
   {
      Poly p (argv[1], ios::in | ios::out);
      polytope::cd_index (p);
   }
   catch (const std::exception& e)
   {
      cerr << e.what() << endl;
      return 1;
   }
   return 0;
}
