/*=============================================================================

    This file is part of FLINT.

    FLINT 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 of the License, or
    (at your option) any later version.

    FLINT 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.

    You should have received a copy of the GNU General Public License
    along with FLINT; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

=============================================================================*/
/******************************************************************************

    Copyright (C) 2006, 2011 William Hart

******************************************************************************/

#define ulong ulongxx /* interferes with system includes */
#include <stdio.h>
#undef ulong
#define ulong mp_limb_t

#include <gmp.h>
#include "flint.h"
#include "ulong_extras.h"
#include "qsieve.h"
#include "fmpz.h"

/* 
   Factor n = (hi, lo). Returns a factor of n. 
   Assumes n is not prime and not a perfect power.
   Returns 0 if n is too large to be factored by this function.
*/
mp_limb_t qsieve_ll_factor(mp_limb_t hi, mp_limb_t lo)
{
    qs_t qs_inf;
    mp_limb_t factor = 0, t;
    slong rels_found = 0;
    char * sieve;
    slong ncols, nrows, i, count;
    uint64_t * nullrows;
    uint64_t mask;
    flint_rand_t state;
    fmpz_t X, Y;

    /************************************************************************
        INITIALISATION:
          
        Initialise the qs_t structure. 
    ************************************************************************/
#if QS_DEBUG
    flint_printf("\nStart:\n");
#endif

    qsieve_ll_init(qs_inf, hi, lo);

#if QS_DEBUG /* print some diagnostic information */
    {
       mpz_t _n;
       mpz_init(_n);
       flint_mpz_set_ui(_n, hi);
       mpz_mul_2exp(_n, _n, FLINT_BITS);
       flint_mpz_add_ui(_n, _n, lo);
       gmp_printf("Factoring %Zd of %wd bits\n", _n, qs_inf->bits);
       mpz_clear(_n);
    }
#endif

    /************************************************************************
        KNUTH SCHROEPPEL:
        
        Try to compute a multiplier k such that there are a lot of small primes
        which are quadratic residues modulo kn. If a small factor of n is found
        during this process it is returned.
    ************************************************************************/
#if QS_DEBUG
    flint_printf("\nKnuth-Schroeppel:\n");
#endif

    factor = qsieve_ll_knuth_schroeppel(qs_inf); 
    if (factor) 
	{
#if QS_DEBUG
        flint_printf("Found small factor %wd in Knuth-Schroeppel\n", factor);
#endif
		qsieve_ll_clear(qs_inf);
        return factor;
	}

    /* compute kn */
    fmpz_set_ui(qs_inf->kn, hi);
    fmpz_mul_2exp(qs_inf->kn, qs_inf->kn, FLINT_BITS);
    fmpz_add_ui(qs_inf->kn, qs_inf->kn, lo);
    fmpz_mul_ui(qs_inf->kn, qs_inf->kn, qs_inf->k);

    /* refine qs_inf->bits */
    qs_inf->bits = fmpz_bits(qs_inf->kn);
    if (qs_inf->bits > 2*FLINT_BITS)
    {
		qsieve_ll_clear(qs_inf);
        return 0; /* kn is too large to factor */
    }

    /************************************************************************
        COMPUTE FACTOR BASE:
        
        Try to compute a multiplier k such that there are a lot of small primes
        which are quadratic residues modulo kn. If a small factor of n is found
        during this process it is returned.
    ************************************************************************/
#if QS_DEBUG
    flint_printf("\nCompute factor base:\n");
#endif

    /* compute factor base primes and associated data*/
    factor = qsieve_ll_primes_init(qs_inf);
	if (factor) 
    {
#if QS_DEBUG
        flint_printf("Found small factor %wd whilst generating factor base\n", factor);
#endif
		qsieve_ll_clear(qs_inf);
        return factor;
	}
    
    /************************************************************************
        INITIALISE POLYNOMIAL DATA:
        
        Create space for all the polynomial information
    ************************************************************************/
#if QS_DEBUG
    flint_printf("\nInitialise poly:\n");
#endif

    /* set (hi, lo) to kn */
    umul_ppmm(t, lo, lo, qs_inf->k);
    hi = hi*qs_inf->k + t;
    qs_inf->hi = hi;
    qs_inf->lo = lo;

    qsieve_ll_poly_init(qs_inf);

    /************************************************************************
        INITIALISE RELATION/LINAXYXYXYXY DATA:
        
        Create space for all the relations and matrix information
    ************************************************************************/
#if QS_DEBUG
    flint_printf("\nInitialise relations and linear algebra:\n");
#endif

    qsieve_ll_linalg_init(qs_inf);

    /************************************************************************
        SIEVE:
        
        Sieve for relations
    ************************************************************************/
#if QS_DEBUG
    flint_printf("\nSieve:\n");
#endif

    sieve = flint_malloc(qs_inf->sieve_size + sizeof(ulong));

    while (rels_found < qs_inf->num_primes + qs_inf->extra_rels)
    {
        rels_found += qsieve_ll_collect_relations(qs_inf, sieve);

#if (QS_DEBUG & 128)
        flint_printf("%wd/%wd relations.\n", rels_found, qs_inf->num_primes + qs_inf->extra_rels);
#endif
    }

    flint_free(sieve);

    /************************************************************************
        REDUCE MATRIX:
        
        Perform some light filtering on the matrix
    ************************************************************************/

    ncols = qs_inf->num_primes + qs_inf->extra_rels;
    nrows = qs_inf->num_primes;

#if QS_DEBUG
    flint_printf("Reduce matrix:\n");
#endif

	reduce_matrix(qs_inf, &nrows, &ncols, qs_inf->matrix); 
 
    /************************************************************************
        BLOCK LANCZOS:
        
        Find extra_rels nullspace vectors (if they exist)
    ************************************************************************/

#if QS_DEBUG
    flint_printf("Block lanczos:\n");
#endif

    flint_randinit(state); /* initialise the random generator */
   
    do /* repeat block lanczos until it succeeds */
    {
        nullrows = block_lanczos(state, nrows, 0, ncols, qs_inf->matrix);
    } while (nullrows == NULL); 
        
    for (i = 0, mask = 0; i < ncols; i++) /* create mask of nullspace vectors */
        mask |= nullrows[i];

    for (i = count = 0; i < 64; i++) /* count nullspace vectors found */
    {
	    if (mask & ((uint64_t)(1) << i))
		    count++;
    }

    flint_randclear(state); /* clean up random state */

#if QS_DEBUG
    flint_printf("%wd nullspace vectors found\n", count);
#endif

    /************************************************************************
        SQUARE ROOT:
        
        Compute the square root and take the GCD of X-Y with N
    ************************************************************************/

#if QS_DEBUG
    flint_printf("Square root:\n");
#endif
	
    fmpz_fdiv_q_ui(qs_inf->kn, qs_inf->kn, qs_inf->k); /* divide kn by multiplier */
   
    fmpz_init(X);
    fmpz_init(Y);

    for (count = 0; count < 64; count++)
    {
        if (mask & ((uint64_t)(1) << count))
        {
            qsieve_ll_square_root(X, Y, qs_inf, nullrows, ncols, count, qs_inf->kn); 
            fmpz_sub(X, X, Y);
            fmpz_gcd(X, X, qs_inf->kn);
         
            if (fmpz_cmp(X, qs_inf->kn) != 0 && fmpz_cmp_ui(X, 1) != 0) /* have a factor */
            {
                if (fmpz_size(X)!= 1)
                    fmpz_fdiv_q(X, qs_inf->kn, X); /* take smaller of two factors */
                factor = fmpz_get_ui(X); 
                break;
            }
        }
    }

    fmpz_clear(X);
    fmpz_clear(Y);
    flint_free(nullrows);

    /************************************************************************
        CLEAN UP:
        
        Free all used memory
    ************************************************************************/

#if QS_DEBUG
    flint_printf("\nClean up:\n");
#endif

    qsieve_ll_clear(qs_inf);

#if QS_DEBUG
    flint_printf("\nDone.\n");
#endif

    return factor;
}
