/* emacs edit mode for this file is -*- C++ -*- */

#include <config.h>

#include "assert.h"
#include "debug.h"
#include "timing.h"

#include "cf_defs.h"
#include "cf_algorithm.h"
#include "fac_multivar.h"
#include "fac_univar.h"
#include "cf_reval.h"
#include "cf_map.h"
#include "fac_util.h"
#include "cf_binom.h"
#include "cf_iter.h"
#include "cf_primes.h"
#include "fac_distrib.h"

void out_cf(const char *s1,const CanonicalForm &f,const char *s2);
void out_cff(CFFList &L);

TIMING_DEFINE_PRINT(fac_content)
TIMING_DEFINE_PRINT(fac_findeval)
TIMING_DEFINE_PRINT(fac_distrib)
TIMING_DEFINE_PRINT(fac_hensel)

static CFArray
conv_to_factor_array( const CFFList & L )
{
    int n;
    CFFListIterator I = L;
    bool negate = false;

    if ( ! I.hasItem() )
        n = 0;
    else  if ( I.getItem().factor().inBaseDomain() ) {
        negate = I.getItem().factor().sign() < 0;
        I++;
        n = L.length();
    }
    else
        n = L.length() + 1;
    CFFListIterator J = I;
    while ( J.hasItem() ) {
        n += J.getItem().exp() - 1;
        J++;
    }
    CFArray result( 1, n-1 );
    int i, j, k;
    i = 1;
    while ( I.hasItem() ) {
        k = I.getItem().exp();
        for ( j = 1; j <= k; j++ ) {
            result[i] = I.getItem().factor();
            i++;
        }
        I++;
    }
    if ( negate )
        result[1] = -result[1];
    return result;
}

static modpk
coeffBound ( const CanonicalForm & f, int p )
{
    int * degs = degrees( f );
    int M = 0, i, k = f.level();
    for ( i = 1; i <= k; i++ )
        M += degs[i];
    CanonicalForm b = 2 * maxNorm( f ) * power( CanonicalForm( 3 ), M );
    CanonicalForm B = p;
    k = 1;
    while ( B < b ) {
        B *= p;
        k++;
    }
    return modpk( p, k );
}

// static bool
// nonDivisors ( CanonicalForm omega, CanonicalForm delta, const CFArray & F, CFArray & d )
// {
//     DEBOUTLN( cerr, "nondivisors omega = " << omega );
//     DEBOUTLN( cerr, "nondivisors delta = " << delta );
//     DEBOUTLN( cerr, "nondivisors F = " << F );
//     CanonicalForm q, r;
//     int k = F.size();
//     d = CFArray( 0, k );
//     d[0] = delta * omega;
//     for ( int i = 1; i <= k; i++ ) {
//         q = abs(F[i]);
//         for ( int j = i-1; j >= 0; j-- ) {
//             r = d[j];
//             do {
//                 r = gcd( r, q );
//                 q = q / r;
//             } while ( r != 1 );
//             if ( q == 1 )
//                 return false;
//         }
//         d[i] = q;
//     }
//     return true;
// }

static void
findEvaluation ( const CanonicalForm & U, const CanonicalForm & V, const CanonicalForm & omega, const CFFList & F, Evaluation & A, CanonicalForm & U0, CanonicalForm & delta, CFArray & D, int r )
{
    DEBINCLEVEL( cerr, "findEvaluation" );
    CanonicalForm Vn;
    CFFListIterator I;
    int j;
    bool found = false;
    CFArray FF = CFArray( 1, F.length() );
    if ( r > 0 )
        A.nextpoint();
    while ( ! found )
    {
        Vn = A( V );
        if ( Vn != 0 )
        {
            U0 = A( U );
            DEBOUTLN( cerr, "U0 = " << U0 );
            if ( isSqrFree( U0 ) )
            {
                delta = content( U0 );
                DEBOUTLN( cerr, "content( U0 ) = " << delta );
                for ( I = F, j = 1; I.hasItem(); I++, j++ )
                    FF[j] = A( I.getItem().factor() );
                found = nonDivisors( omega, delta, FF, D );
            }
            else
            {
                DEBOUTLN( cerr, "not sqrfree : " << sqrFree( U0 ) );
            }
        }
        if ( ! found )
            A.nextpoint();
    }
    DEBDECLEVEL( cerr, "findEvaluation" );
}

#ifdef HAVE_NTL
int prime_number=0;
void find_good_prime(const CanonicalForm &f, int &start)
{
  if (! f.inBaseDomain() )
  {
    CFIterator i = f;
    for(;;)
    {
      if  ( i.hasTerms() )
      {
        find_good_prime(i.coeff(),start);
        if (0==cf_getSmallPrime(start)) return;
        if((i.exp()!=0) && ((i.exp() % cf_getSmallPrime(start))==0))
        {
          start++;
          i=f;
        }
        else  i++;
      }
      else break;
    }
  }
  else
  {
    if (f.inZ())
    {
      if (0==cf_getSmallPrime(start)) return;
      while((!f.isZero()) && (mod(f,cf_getSmallPrime(start))==0))
      {
        start++;
        if (0==cf_getSmallPrime(start)) return;
      }
    }
/* should not happen!
    else if (f.inQ())
    {
      while((f.den()!=0) && (mod(f.den(),cf_getSmallPrime(start))==0))
      {
        start++;
      }
      while((f.num()!=0) && (mod(f.num(),cf_getSmallPrime(start))==0))
      {
        start++;
      }
    }
    else
          cout <<"??"<< f <<"\n";
*/
  }
}
#endif

static CFArray ZFactorizeMulti ( const CanonicalForm & arg )
{
    DEBINCLEVEL( cerr, "ZFactorizeMulti" );
    CFMap M;
    CanonicalForm UU, U = compress( arg, M );
    CanonicalForm delta, omega, V = LC( U, 1 );
    int t = U.level();
    CFFList F = factorize( V );
    CFFListIterator I, J;
    CFArray G, lcG, D;
    int i, j, r, maxdeg;
    REvaluation A( 2, t, IntRandom( 50 ) );
    CanonicalForm U0;
    CanonicalForm ft, ut, gt, d;
    modpk b;
    bool negate = false;

    DEBOUTLN( cerr, "-----------------------------------------------------" );
    DEBOUTLN( cerr, "trying to factorize U = " << U );
    DEBOUTLN( cerr, "U is a polynomial of level = " << arg.level() );
    DEBOUTLN( cerr, "U will be factorized with respect to variable " << Variable(1) );
    DEBOUTLN( cerr, "the leading coefficient of U with respect to that variable is " << V );
    DEBOUTLN( cerr, "which is factorized as " << F );

    maxdeg = 0;
    for ( i = 2; i <= t; i++ )
    {
        j = U.degree( Variable( i ) );
        if ( j > maxdeg ) maxdeg = j;
    }

    if ( F.getFirst().factor().inCoeffDomain() )
    {
        omega = F.getFirst().factor();
        F.removeFirst();
        if ( omega < 0 )
        {
            negate = true;
            omega = -omega;
            U = -U;
        }
    }
    else
        omega = 1;

    bool goodeval = false;
    r = 0;

//    for ( i = 0; i < 10*t; i++ )
//        A.nextpoint();

    while ( ! goodeval )
    {
        TIMING_START(fac_findeval);
        findEvaluation( U, V, omega, F, A, U0, delta, D, r );
        TIMING_END(fac_findeval);
        DEBOUTLN( cerr, "the evaluation point to reduce to an univariate problem is " << A );
        DEBOUTLN( cerr, "corresponding delta = " << delta );
        DEBOUTLN( cerr, "              omega = " << omega );
        DEBOUTLN( cerr, "              D     = " << D );
        DEBOUTLN( cerr, "now factorize the univariate polynomial " << U0 );
        G = conv_to_factor_array( factorize( U0, false ) );
        DEBOUTLN( cerr, "which factorizes into " << G );
        #ifdef HAVE_NTL
        {
          int i=prime_number;
          find_good_prime(arg,i);
          find_good_prime(U0,i);
          find_good_prime(U,i);
          int p=cf_getSmallPrime(i);
          //printf("found:p=%d (%d)\n",p,i);
          if (p==0)
          {
            return conv_to_factor_array(CFFactor(arg,1));
            //printf("out of primes - switch ot non-NTL\n");
          }
          else if (((i==0)||(i!=prime_number)))
          {
            b = coeffBound( U, p );
            prime_number=i;
          }
          // p!=0:
          modpk bb=coeffBound(U0,p);
          if (bb.getk() > b.getk() ) b=bb;
          bb=coeffBound(arg,p);
          if (bb.getk() > b.getk() ) b=bb;
        }
        #else
        b = coeffBound( U, getZFacModulus().getp() );
        if ( getZFacModulus().getpk() > b.getpk() )
            b = getZFacModulus();
        #endif
        //printf("p=%d, k=%d\n",b.getp(),b.getk());
        DEBOUTLN( cerr, "the coefficient bound of the factors of U is " << b.getpk() );

        r = G.size();
        lcG = CFArray( 1, r );
        UU = U;
        DEBOUTLN( cerr, "now trying to distribute the leading coefficients ..." );
        TIMING_START(fac_distrib);
        goodeval = distributeLeadingCoeffs( UU, G, lcG, F, D, delta, omega, A, r );
        TIMING_END(fac_distrib);
#ifdef DEBUGOUTPUT
        if ( goodeval )
        {
            DEBOUTLN( cerr, "the univariate factors after distribution are " << G );
            DEBOUTLN( cerr, "the distributed leading coeffs are " << lcG );
            DEBOUTLN( cerr, "U may have changed and is now " << UU );
            DEBOUTLN( cerr, "which has leading coefficient " << LC( UU, Variable(1) ) );

            if ( LC( UU, Variable(1) ) != prod( lcG ) || A(UU) != prod( G ) )
            {
                DEBOUTLN( cerr, "!!! distribution was not correct !!!" );
                DEBOUTLN( cerr, "product of leading coeffs is " << prod( lcG ) );
                DEBOUTLN( cerr, "product of univariate factors is " << prod( G ) );
                DEBOUTLN( cerr, "the new U is evaluated as " << A(UU) );
            }
            else
                DEBOUTLN( cerr, "leading coeffs correct" );
        }
        else
        {
            DEBOUTLN( cerr, "we have found a bad evaluation point" );
        }
#endif
        if ( goodeval )
        {
            TIMING_START(fac_hensel);
            goodeval = Hensel( UU, G, lcG, A, b, Variable(1) );
            TIMING_END(fac_hensel);
        }
    }
    for ( i = 1; i <= r; i++ )
    {
        G[i] /= icontent( G[i] );
        G[i] = M(G[i]);
    }
    // negate noch beachten !
    if ( negate )
        G[1] = -G[1];
    DEBDECLEVEL( cerr, "ZFactorMulti" );
    return G;
}

CFFList ZFactorizeMultivariate ( const CanonicalForm & f, bool issqrfree )
{
    CFFList G, F, R;
    CFArray GG;
    CFFListIterator i, j;
    CFMap M;
    CanonicalForm g, cont;
    Variable v1, vm;
    int k, m, n;

    v1 = Variable(1);
    if ( issqrfree )
        F = CFFactor( f, 1 );
    else
        F = sqrFree( f );

    for ( i = F; i.hasItem(); i++ )
    {
        if ( i.getItem().factor().inCoeffDomain() )
            R.append( CFFactor( i.getItem().factor(), i.getItem().exp() ) );
        else
        {
            TIMING_START(fac_content);
            g = compress( i.getItem().factor(), M );
            // now after compress g contains Variable(1)
            vm = g.mvar();
            g = swapvar( g, v1, vm );
            cont = content( g );
            g = swapvar( g / cont, v1, vm );
            cont = swapvar( cont, v1, vm );
            n = i.getItem().exp();
            TIMING_END(fac_content);
            DEBOUTLN( cerr, "now after content ..." );
            if ( g.isUnivariate() )
            {
                G = factorize( g, true );
                for ( j = G; j.hasItem(); j++ )
                    if ( ! j.getItem().factor().isOne() )
                        R.append( CFFactor( M( j.getItem().factor() ), n ) );
            }
            else
            {
                GG = ZFactorizeMulti( g );
                m = GG.max();
                for ( k = GG.min(); k <= m; k++ )
                    if ( ! GG[k].isOne() )
                        R.append( CFFactor( M( GG[k] ), n ) );
            }
            G = factorize( cont, true );
            for ( j = G; j.hasItem(); j++ )
                if ( ! j.getItem().factor().isOne() )
                    R.append( CFFactor( M( j.getItem().factor() ), n ) );
        }
    }
    return R;
}
