/*
 * $Id: TermOrderOptimization.java 3831 2011-12-18 20:54:12Z kredel $
 */

package edu.jas.poly;

import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Arrays;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.log4j.Logger;

import edu.jas.structure.RingElem;

import edu.jas.arith.BigInteger;

import edu.jas.poly.GenPolynomial;
import edu.jas.poly.GenPolynomialRing;

import edu.jas.vector.BasicLinAlg;


/**
 * Term order optimization.
 * See mas10/maspoly/DIPTOO.m{di}.
 * @author Heinz Kredel
 */

public class TermOrderOptimization {


    private static final Logger logger = Logger.getLogger(TermOrderOptimization.class);
    //private static boolean debug = logger.isDebugEnabled();


    /**
     * Degree matrix.
     * @param A polynomial to be considered.
     * @return degree matrix.
     */
    public static <C extends RingElem<C>> 
        List<GenPolynomial<BigInteger>> degreeMatrix( GenPolynomial<C> A ) {

        List<GenPolynomial<BigInteger>> dem = null;
        if ( A == null ) {
           return dem;
        }

        BigInteger cfac = new BigInteger(); 
        GenPolynomialRing<BigInteger> ufac 
            = new GenPolynomialRing<BigInteger>(cfac,1);

        int nvar = A.numberOfVariables();
        dem = new ArrayList<GenPolynomial<BigInteger>>( nvar );

        for ( int i = 0; i < nvar; i++ ) {
            dem.add( ufac.getZERO() );
        }
        if ( A.isZERO() ) {
           return dem;
        }

        for ( ExpVector e: A.getMap().keySet() ) {
            dem = expVectorAdd(dem, e );
        }
        return dem;
    }


    /**
     * Degree matrix exponent vector add.
     * @param dm degree matrix.
     * @param e exponent vector.
     * @return degree matrix + e.
     */
    public static 
       List<GenPolynomial<BigInteger>> expVectorAdd(List<GenPolynomial<BigInteger>> dm, ExpVector e) {
       for ( int i = 0; i < dm.size() && i < e.length(); i++ ) {
           GenPolynomial<BigInteger> p = dm.get(i);
           long u = e.getVal(i);
           ExpVector f = ExpVector.create(1,0,u);
           p = p.sum( p.ring.getONECoefficient(), f );
           dm.set(i,p);
       }
       return dm;
    }


    /**
     * Degree matrix of coefficient polynomials.
     * @param A polynomial to be considered.
     * @return degree matrix for the coeficients.
     */
    public static <C extends RingElem<C>> 
        List<GenPolynomial<BigInteger>> degreeMatrixOfCoefficients( GenPolynomial<GenPolynomial<C>> A ) {
        if ( A == null ) {
           throw new IllegalArgumentException("polynomial must not be null");
        }
        return degreeMatrix( A.getMap().values() );
    }


    /**
     * Degree matrix.
     * @param L list of polynomial to be considered.
     * @return degree matrix.
     */
    public static <C extends RingElem<C>> 
       List<GenPolynomial<BigInteger>> degreeMatrix( Collection<GenPolynomial<C>> L ) {
       if ( L == null ) {
          throw new IllegalArgumentException("list must be non null");
       }
       BasicLinAlg<GenPolynomial<BigInteger>> blas = new BasicLinAlg<GenPolynomial<BigInteger>>();
       List<GenPolynomial<BigInteger>> dem = null;
       for ( GenPolynomial<C> p : L ) {
           List<GenPolynomial<BigInteger>> dm = degreeMatrix( p );
           if ( dem == null ) {
              dem = dm;
           } else {
              dem = blas.vectorAdd( dem, dm );
           }
       }
       return dem;
    }


    /**
     * Degree matrix of coefficient polynomials.
     * @param L list of polynomial to be considered.
     * @return degree matrix for the coeficients.
     */
    public static <C extends RingElem<C>> 
        List<GenPolynomial<BigInteger>> 
        degreeMatrixOfCoefficients( Collection<GenPolynomial<GenPolynomial<C>>> L ) {
        if ( L == null ) {
           throw new IllegalArgumentException("list must not be null");
        }
        BasicLinAlg<GenPolynomial<BigInteger>> blas = new BasicLinAlg<GenPolynomial<BigInteger>>();
        List<GenPolynomial<BigInteger>> dem = null;
        for ( GenPolynomial<GenPolynomial<C>> p : L ) {
            List<GenPolynomial<BigInteger>> dm = degreeMatrixOfCoefficients( p );
            if ( dem == null ) {
               dem = dm;
            } else {
               dem = blas.vectorAdd( dem, dm );
            }
        }
        return dem;
    }


    /**
     * Optimal permutation for the Degree matrix.
     * @param D degree matrix.
     * @return optimal permutation for D.
     */
    public static 
       List<Integer> optimalPermutation( List<GenPolynomial<BigInteger>> D ) {
       if ( D == null ) {
          throw new IllegalArgumentException("list must be non null");
       }
       List<Integer> P = new ArrayList<Integer>( D.size() ); 
       if ( D.size() == 0 ) {
          return P;
       }
       if ( D.size() == 1 ) {
          P.add(0);
          return P;
       }
       SortedMap< GenPolynomial<BigInteger>, List<Integer> > map 
           = new TreeMap<GenPolynomial<BigInteger>,List<Integer>>(); 
       int i = 0;
       for ( GenPolynomial<BigInteger> p : D ) {
           List<Integer> il = map.get(p);
           if ( il == null ) {
              il = new ArrayList<Integer>(3);
           }
           il.add( i );
           map.put( p, il );
           i++;
       }
       List<List<Integer>> V = new ArrayList<List<Integer>>( map.values() ); 
       //System.out.println("V = " + V);
       //for ( int j = V.size()-1; j >= 0; j-- ) {
       for ( int j = 0; j < V.size(); j++ ) {
           List<Integer> v = V.get(j); 
           for ( Integer k : v ) {
               P.add( k );
           }
       }
       return P;
    }


    /**
     * Inverse of a permutation.
     * @param P permutation.
     * @return S with S*P = id.
     */
    public static List<Integer> inversePermutation( List<Integer> P ) {
        if ( P == null || P.size() <= 1 ) {
           return P;
        }
        List<Integer> ip = new ArrayList<Integer>(P); // ensure size and content
        for ( int i = 0; i < P.size(); i++ ) {
            ip.set(P.get(i),i); // inverse
        }
        return ip;
    }


    /**
     * Test for identity permutation.
     * @param P permutation.
     * @return true , if P = id, else false.
     */
    public static boolean isIdentityPermutation( List<Integer> P ) {
        if ( P == null || P.size() <= 1 ) {
           return true;
        }
        for ( int i = 0; i < P.size(); i++ ) {
            if ( P.get(i).intValue() != i ) {
                return false;
            }
        }
        return true;
    }


    /**
     * Multiplication permutations.
     * @param P permutation.
     * @param S permutation.
     * @return P*S.
     */
    public static List<Integer> multiplyPermutation( List<Integer> P, List<Integer> S ) {
        if ( P == null || S == null ) {
           return null;
        }
        if ( P.size() != S.size() ) {
            throw new IllegalArgumentException("#P != #S: P =" + P + ", S = " + S);
        }
        List<Integer> ip = new ArrayList<Integer>(P); // ensure size and content
        for ( int i = 0; i < P.size(); i++ ) {
            ip.set(i,S.get(P.get(i))); 
        }
        return ip;
    }


    /**
     * Permutation of a list.
     * @param L list.
     * @param P permutation.
     * @return P(L).
     */
    public static <T> List<T> listPermutation( List<Integer> P, List<T> L ) {
        if ( L == null || L.size() <= 1 ) {
           return L;
        }
        List<T> pl = new ArrayList<T>( L.size() );
        for ( Integer i : P ) {
            pl.add( L.get( (int)i ) );
        }
        return pl;
    }


    /**
     * Permutation of an array.
     * Compiles, but does not work, requires JDK 1.6 to work.
     * @param a array.
     * @param P permutation.
     * @return P(a).
     */
    @SuppressWarnings("unchecked") 
    public static <T> T[] arrayPermutation( List<Integer> P, T[] a ) {
        if ( a == null || a.length <= 1 ) {
           return a;
        }
        T[] b = (T[]) new Object[a.length];    // jdk 1.5 
        //T[] b = Arrays.<T>copyOf( a, a.length ); // jdk 1.6, works
        int j = 0;
        for ( Integer i : P ) {
            b[j] = a[ (int)i ];
            j++;
        }
        return b;
    }


    /**
     * Permutation of an array.
     * @param a array.
     * @param P permutation.
     * @return P(a).
     */
    public static String[] stringArrayPermutation( List<Integer> P, String[] a ) {
        if ( a == null || a.length <= 1 ) {
           return a;
        }
        String[] b = new String[a.length];    // jdk 1.5
        //T[] b = Arrays.<T>copyOf( a, a.length ); // jdk 1.6
        int j = 0;
        for ( Integer i : P ) {
            b[j] = a[ (int)i ];
            j++;
        }
        return b;
    }


    /**
     * Permutation of a long array.
     * @param a array of long.
     * @param P permutation.
     * @return P(a).
     */
    public static long[] longArrayPermutation( List<Integer> P, long[] a ) {
        if ( a == null || a.length <= 1 ) {
           return a;
        }
        long[] b = new long[ a.length ];
        int j = 0;
        for ( Integer i : P ) {
            b[j] = a[ (int)i ];
            j++;
        }
        return b;
    }


    /**
     * Permutation of an exponent vector.
     * @param e exponent vector.
     * @param P permutation.
     * @return P(e).
     */
    public static
        ExpVector permutation( List<Integer> P, ExpVector e ) {
        if ( e == null ) {
           return e;
        }
        long[] u = longArrayPermutation( P, e.getVal() );
        ExpVector f = ExpVector.create( u );
        return f;
    }


    /**
     * Permutation of polynomial exponent vectors.
     * @param A polynomial.
     * @param R polynomial ring.
     * @param P permutation.
     * @return P(A).
     */
    public static <C extends RingElem<C>> 
        GenPolynomial<C> permutation( List<Integer> P, GenPolynomialRing<C> R, GenPolynomial<C> A ) {
        if ( A == null ) {
           return A;
        }
        GenPolynomial<C> B = R.getZERO().clone();
        Map<ExpVector,C> Bv = B.val; //getMap();
        for ( Map.Entry<ExpVector,C> y: A.getMap().entrySet() ) {
            ExpVector e = y.getKey();
            C a = y.getValue();
            //System.out.println("e = " + e);
            ExpVector f = permutation(P,e);
            //System.out.println("f = " + f);
            Bv.put( f, a ); // assert f not in Bv
        }
        return B;
    }


    /**
     * Permutation of polynomial exponent vectors.
     * @param L list of polynomials.
     * @param R polynomial ring.
     * @param P permutation.
     * @return P(L).
     */
    public static <C extends RingElem<C>> 
      List<GenPolynomial<C>> 
      permutation( List<Integer> P, GenPolynomialRing<C> R, List<GenPolynomial<C>> L ) {
        if ( L == null || L.size() == 0 ) {
           return L;
        }
        List<GenPolynomial<C>> K = new ArrayList<GenPolynomial<C>>( L.size() );
        for ( GenPolynomial<C> a: L ) {
            GenPolynomial<C> b = permutation( P, R, a );
            K.add( b );
        }
        return K;
    }


    /**
     * Permutation of polynomial exponent vectors of coefficient polynomials.
     * @param A polynomial.
     * @param R polynomial ring.
     * @param P permutation.
     * @return P(A).
     */
    public static <C extends RingElem<C>> 
       GenPolynomial<GenPolynomial<C>> 
       permutationOnCoefficients( List<Integer> P, 
                                  GenPolynomialRing<GenPolynomial<C>> R, 
                                  GenPolynomial<GenPolynomial<C>> A ) {
        if ( A == null ) {
           return A;
        }
        GenPolynomial<GenPolynomial<C>> B = R.getZERO().clone();
        GenPolynomialRing<C> cf = (GenPolynomialRing<C>) R.coFac;
        Map<ExpVector,GenPolynomial<C>> Bv = B.val; //getMap();
        for ( Map.Entry<ExpVector,GenPolynomial<C>> y: A.getMap().entrySet() ) {
            ExpVector e = y.getKey();
            GenPolynomial<C> a = y.getValue();
            //System.out.println("e = " + e);
            GenPolynomial<C> b = permutation(P,cf,a);
            //System.out.println("b = " + b);
            Bv.put( e, b ); // assert e not in Bv
        }
        return B;
    }


    /**
     * Permutation of polynomial exponent vectors of coefficients.
     * @param L list of polynomials.
     * @param R polynomial ring.
     * @param P permutation.
     * @return P(L).
     */
    public static <C extends RingElem<C>> 
       List<GenPolynomial<GenPolynomial<C>>> 
       permutationOnCoefficients( List<Integer> P, 
                                  GenPolynomialRing<GenPolynomial<C>> R, 
                                  List<GenPolynomial<GenPolynomial<C>>> L ) {
        if ( L == null || L.size() == 0 ) {
           return L;
        }
        List<GenPolynomial<GenPolynomial<C>>> K 
            = new ArrayList<GenPolynomial<GenPolynomial<C>>>( L.size() );
        for ( GenPolynomial<GenPolynomial<C>> a: L ) {
            GenPolynomial<GenPolynomial<C>> b = permutationOnCoefficients( P, R, a );
            K.add( b );
        }
        return K;
    }


    /**
     * Permutation of polynomial ring variables.
     * @param R polynomial ring.
     * @param P permutation.
     * @return P(R).
     */
    public static <C extends RingElem<C>> 
       GenPolynomialRing<C>
       permutation( List<Integer> P, GenPolynomialRing<C> R ) {
        if ( R == null ) {
           return R;
        }
        if ( R.vars == null || R.nvar <= 1 ) {
           return R;
        }
        GenPolynomialRing<C> S;
        TermOrder tord = R.tord;
        if ( tord.getEvord2() != 0 ) {
            //throw new IllegalArgumentException("split term orders not permutable");
            tord = new TermOrder( tord.getEvord2() );
            logger.warn("split term order '" + R.tord + "' not permutable, resetting to most base term order " + tord);
        }
        long[][] weight = tord.getWeight();
        if ( weight != null ) {
           long[][] w = new long[ weight.length ][];
           for ( int i = 0; i < weight.length; i++ ) {
               w[i] = longArrayPermutation(P,weight[i]);
           }
           tord = new TermOrder( w );
        }
        String[] v1 = new String[R.vars.length];
        for ( int i = 0; i < v1.length; i++ ) {
            v1[i] = R.vars[ v1.length-1 - i ];
        }
        String[] vars = stringArrayPermutation( P, v1 );
        String[] v2 = new String[R.vars.length];
        for ( int i = 0; i < v1.length; i++ ) {
            v2[i] = vars[ v2.length-1 - i ];
        }
        S = new GenPolynomialRing<C>( R.coFac, R.nvar, tord, v2 );
        return S;
    }


    /**
     * Optimize variable order.
     * @param R polynomial ring.
     * @param L list of polynomials.
     * @return optimized polynomial list.
     */
    public static <C extends RingElem<C>> 
       OptimizedPolynomialList<C>
      optimizeTermOrder( GenPolynomialRing<C> R, List<GenPolynomial<C>> L ) {
       List<Integer> perm = optimalPermutation( degreeMatrix(L) );
       GenPolynomialRing<C> pring = TermOrderOptimization.<C>permutation( perm, R );
       List<GenPolynomial<C>> ppolys = TermOrderOptimization.<C>permutation( perm, pring, L );
       OptimizedPolynomialList<C> op = new OptimizedPolynomialList<C>(perm,pring,ppolys);
       return op;
    }


    /**
     * Optimize variable order.
     * @param P polynomial list.
     * @return optimized polynomial list.
     */
    public static <C extends RingElem<C>> 
       OptimizedPolynomialList<C> optimizeTermOrder( PolynomialList<C> P ) {
       if ( P == null ) {
          return null;
       }
       return optimizeTermOrder( P.ring, P.list );
    }


    /**
     * Optimize variable order on coefficients.
     * @param P polynomial list.
     * @return optimized polynomial list.
     */
    public static <C extends RingElem<C>> 
       OptimizedPolynomialList<GenPolynomial<C>>
       optimizeTermOrderOnCoefficients( PolynomialList<GenPolynomial<C>> P ) {
       if ( P == null ) {
          return null;
       }
       List<Integer> perm = optimalPermutation( degreeMatrixOfCoefficients( P.list ) );

       GenPolynomialRing<GenPolynomial<C>> ring = P.ring;
       GenPolynomialRing<C> coFac = (GenPolynomialRing<C>) ring.coFac;
       GenPolynomialRing<C> pFac;
       pFac = TermOrderOptimization.<C>permutation( perm, coFac );

       GenPolynomialRing<GenPolynomial<C>> pring;
       pring = new GenPolynomialRing<GenPolynomial<C>>(pFac, ring.nvar, ring.tord, ring.vars);

       List<GenPolynomial<GenPolynomial<C>>> ppolys;
       ppolys = TermOrderOptimization.<C>permutationOnCoefficients( perm, pring, P.list );

       OptimizedPolynomialList<GenPolynomial<C>> op 
           = new OptimizedPolynomialList<GenPolynomial<C>>(perm,pring,ppolys);
       return op;
    }

}
