#include <string.h>
#include <time.h>
#include <limits.h>

#include "../ecm-impl.h"

#include "hecm.h"
#include "auxi.h"
#include "generation.h"
#include "morphismes.h"






void optionsHECM_init (optionsHECM options) {
  mpz_init (options->heightMin);
  mpz_init (options->heightMax);

  options->smallParam = TRUE;
  options->curveSpecified = FALSE;
  options->initialCurveSpecified = FALSE;
  options->verbose = OUTPUT_NORMAL;

  options->nbtests = 1;
  mpz_set_ui (options->heightMin,0);
  mpz_set_ui (options->heightMax,0);
}


void optionsHECM_clear (optionsHECM options) {
  mpz_clear (options->heightMin);
  mpz_clear (options->heightMax);
}


void printCurve (paraGenCurve para,optionsHECM options,mpmod_t n) {
  mpz_t s;
  mpz_init (s);

  mpres_get_z (s ,para->s ,n);

  if (options->smallParam == TRUE) {
    gmp_printf ("s=%Zd/%Zd",para->a,para->b);
  }
  else {
    gmp_printf("s=%Zd",s );
  }

  printf(" and nJacobi=%d\n",para->nJacobi);

  mpz_clear (s);
}



static void 
usage (void)
{
  printf ("Perform ECM with hyperelliptic curves of genus 2. The hyperelliptic curves are\n");
  printf ("(2,2)-decomposable which means that their jacobian is isogenous to the product\n");
  printf ("of two elliptic curves. Therefore one run of HECM with this kind of curves is\n");
  printf ("equivalent to two runs of ECM on the underlying curves.\n");
  printf ("Use Kummer surfaces to speed up the arithmetics in the Jacobians.\n");
  printf ("The use of small parameters makes it quicker than GMP-ECM for numbers of\n");
  printf ("at least 300 digits.\n");


  printf ("\nParameters:\n");
  printf ("-n           number to be factored in base 10\n");
  printf ("-B1          B1 in base 10\n");
  printf ("-B2          B2 in base 10\n");
  printf ("-c           Numbers of tests to do. If unspecified run only once.\n");
  printf ("             If 0 run until it finds a factor.\n");
  printf ("-v           verbose mode\n");

  printf ("-not_low_param Work with normal parameters (not small)\n");


  printf ("-spe         We try to generate the curve with this fixed parameters.\n"); 
  printf ("             One of the following options should have been specified:\n");
  printf ("              -ab if we want small parameters\n");
  printf ("              -s  if we want normal parametrization\n");
  printf ("             If the generated curve is correct we run HECM with it else we print\n");
  printf ("             an error\n");

  printf ("-ab a b      fix the parameter s=a/b. We try to use small parameters if possible\n");
  printf ("-h_min       minimal height of s. If equal to 0 or unspecified, then we begin\n");
  printf ("             by 1\n");
  printf ("-h_max       maximal height of s. If equal to 0 or unspecified, then no limit\n");

  printf ("-nJacobi     For the parametrization, we need to have a point on a Jacobi\n");
  printf ("             curve. We begin by P=(1,*) and we use the point nJacobi*P=(x,y)\n");
  printf ("             If nJacobi<0, we use |nJacobi| (absolute value)\n");
  printf ("             If nJacobi=1, we use nJacobi=2 (first usable point)\n");
  printf ("             If nJacobi=0 or unspecified, and in the case of small parameters,\n");
  printf ("             we use nJacobi=2 and increase first a,b. If we can't increase a,b\n");
  printf ("             we increase nJacobi by 1...\n");
  printf ("             If nJacobi=0 or unspecified, and in the case of normal\n");
  printf ("             parametrization, we take random nJacobi for each run.\n");

  printf ("-s s         fix the parameter s for normal parametrization\n");
  printf ("-seed        fix the seed. Only useful with normal parametrization\n");

 

  printf ("-h, --help   Prints this help and exit.\n");
}




double B1 = 25.0;
char * number = "4816415081";   //      58027 83003 
char * Nsigma = "2";   //      s=2                   
int seed = 0;
char * charB2 = "-1";

int main(int argc, char * argv[]) {


  clock_t
    temps_initial, // initial time in micro-seconds
    temps_final;   // final time in micro-seconds
  double 
    temps_cpu;     // total time in seconds

  temps_cpu=0;
  temps_initial = clock ();


  optionsHECM options;
  optionsHECM_init (options);

  mpmod_t n;
  mpz_t k;
  mpz_t f; // factor of N
  mpz_t s; // The parameter s in mpz_t
  paraGenCurve para;
  curve T1,T2;
  int test,test2;
  unsigned int nb;
  double B2scale = 1.0;
  mpz_t B2;

  char *a, *b, *heightMax, *heightMin;

  para->nJacobi = 2;
  a="1";b="2";
  mpz_init_set_si (B2,ECM_DEFAULT_B2); // set B2


  while ((argc > 1) && (argv[1][0] == '-')) { 
    if  ((argc > 2) && (strcmp (argv[1], "-n")) == 0) {
      number = argv[2];
      argv += 2;
      argc -= 2;
    }
    else if ((argc > 2) && (strcmp (argv[1], "-s")) == 0) {
      Nsigma = argv[2];
      options->initialCurveSpecified = TRUE;
      options->smallParam = FALSE;
      argv += 2;
      argc -= 2;
    }
    else if (strcmp (argv[1], "-h") == 0 || strcmp (argv[1], "--help") == 0) {
      usage ();
      mpz_clear(B2);
      exit (0);
    }
    else if ((argc > 3) && (strcmp (argv[1], "-ab")) == 0) {
      a = argv[2];
      b = argv[3];
      options->initialCurveSpecified = TRUE;
      options->smallParam = TRUE;
      argv += 3;
      argc -= 3;
      }
    else if ((argc > 2) && (strcmp (argv[1], "-nJacobi")) == 0) {
      para->nJacobi = atoi(argv[2]);
      options->initialCurveSpecified = TRUE;
      argv += 2;
      argc -= 2;
    }
    else if ((argc > 2) && (strcmp (argv[1], "-B1")) == 0) {
      B1 = atof(argv[2]);
      argv += 2;
      argc -= 2;
    }
    else if  ((argc > 2) && (strcmp (argv[1], "-B2")) == 0) {
      mpz_set_str (B2,argv[2],10);
      argv += 2;
      argc -= 2;
    }
    else if ((argc > 2) && (strcmp (argv[1], "-seed")) == 0) {
      seed = atoi(argv[2]);
      argv += 2;
      argc -= 2;
    }
    else if (strcmp (argv[1], "-not_low_param") == 0) {
      options->smallParam = FALSE;
      argv ++;
      argc --;
    }
    else if (strcmp (argv[1], "-spe") == 0) {
      options->initialCurveSpecified = TRUE;
      options->curveSpecified = TRUE;
      options->nbtests = 1;
      argv ++;
      argc --;
    }
    else if (strcmp (argv[1], "-v") == 0) {
      options->verbose ++;
      argv ++;
      argc --;
    }
    else if ((argc > 2) && (strcmp (argv[1], "-c")) == 0) {
      options->nbtests = atoi(argv[2]);
      argv += 2;
      argc -= 2;
    }
    else if ((argc > 2) && (strcmp (argv[1], "-h_max")) == 0) {
      heightMax = argv[2];
      mpz_set_str (options->heightMax ,heightMax ,10);
      argv += 2;
      argc -= 2;
    }
    else if ((argc > 2) && (strcmp (argv[1], "-h_min")) == 0) {
      heightMin = argv[2];
      mpz_set_str (options->heightMin ,heightMin ,10);
      argv += 2;
      argc -= 2;
    }
    else {
      fprintf(stderr, "Unexpected option: %s\n", argv[1]);
      mpz_clear(B2);
      exit(1);
    }
  }



  if (seed == 0) {
      srand(time(NULL));
  } else {
      srand(seed);
  }


  mpz_t N;
  mpz_init_set_str (N,number,10); // number in base 10
  gmp_printf ("Input number is %Zd\n",N);
  mpmod_init(n,N,ECM_MOD_DEFAULT);
  mpz_clear (N); 

  


  if ( mpz_sgn (options->heightMax) ==0 ) { // heightMax = 0
    mpz_set (options->heightMax,n->orig_modulus); // heightMax = n
  }
  if ( mpz_cmp (options->heightMin ,options->heightMax) > 0 ) {
    gmp_printf ("We must have h_min <= h_max. Here we have %Zd > %Zd.\n",options->heightMin,options->heightMax);

    optionsHECM_clear (options);
    mpmod_clear(n);
    mpz_clear (B2);
    return 0;
  }





  paraGenCurve_init (para,n);
  mpz_init(s);


  if (options->initialCurveSpecified == TRUE) {

    if  (options->smallParam == TRUE) {
      mpz_set_str (para->a ,a ,10);
      mpz_set_str (para->b ,b ,10);

      if ( mpz_cmp_ui (para->b,0) == 0 ) { // b=0
	printf("We have s=a/b so b must be non zero.\n");

	optionsHECM_clear (options);
	mpz_clear(s);
	paraGenCurve_clear (para,n);
	mpmod_clear(n);
	mpz_clear (B2);
	return 0;
      }

      mpz_t d;
      mpz_init (d);

      mpz_gcd (d ,para->a ,para->b);
      mpz_divexact (para->a ,para->a ,d);
      mpz_divexact (para->b ,para->b ,d);

      mpz_clear (d);

      if ( ( mpz_cmp (para->a,options->heightMax) > 0 ) || ( mpz_cmp (para->b,options->heightMax) > 0 ) ) {
	printf("The height of the initial parameters is too big.\n");

	optionsHECM_clear (options);
	mpz_clear(s);
	paraGenCurve_clear (para,n);
	mpmod_clear(n);
	mpz_clear (B2);
	return 0;
      }


    }
    else {
      mpz_set_str (s,Nsigma,10);
      mpres_set_z (para->s,s,n);

      if ( ( mpz_cmp (s,options->heightMax) > 0 ) ) {
	printf("The height of the initial parameter is too big.\n");


	optionsHECM_clear (options);
	mpz_clear(s);
	paraGenCurve_clear (para,n);
	mpmod_clear(n);
	mpz_clear (B2);
	return 0;
      }

    }
  }
  else {
    if  (options->smallParam == TRUE) {
      if (mpz_cmp_ui (options->heightMax,2) < 0) {
	printf("There is no hyperelliptic curve with parameters smaller than this height.\n");

	optionsHECM_clear (options);
	mpz_clear(s);
	paraGenCurve_clear (para,n);
	mpmod_clear(n);
	mpz_clear (B2);
	return 0;
      }

      if ( mpz_cmp_ui (options->heightMin,2) < 0 ) {
	mpz_set_ui (para->a ,1);
	mpz_set_ui (para->b ,2);
      }
      else {
	mpz_set_ui (para->a ,1);
	mpz_set (para->b ,options->heightMin);
      }
      para->nJacobi = 2;

    }
    else {
      if (mpz_cmp_ui (options->heightMax,3) < 0) {
	printf("There is no hyperelliptic curve with parameters smaller than this height.\n");

	optionsHECM_clear (options);
	mpz_clear(s);
	paraGenCurve_clear (para,n);
	mpmod_clear(n);
	mpz_clear (B2);
	return 0;
      }

      para->nJacobi= (rand() % (N_JACOBI_MAX_p1 - N_JACOBI_MIN)) + N_JACOBI_MIN;
      // TODO
      if ( mpz_cmp_ui (options->heightMin,3) < 0 ) {
	mpres_set_ui (para->s ,3 ,n);
      }
      else {
	mpres_set_z (para->s ,options->heightMin ,n);
      }

    }
  }







  if (options->nbtests <= 0) {
    options->nbtests = UINT_MAX;
  }



  mpres_init (T1.x,n);
  mpres_init (T1.y,n);
  mpres_init (T1.A,n);
  mpres_init (T2.x,n);
  mpres_init (T2.y,n);
  mpres_init (T2.A,n);

  mpz_init(f);



  mpz_init(k);
  prodTreeCalculk(k,B1); // k=lcm(2,3,..,B1)
  getprime_clear ();







  if ( ECM_IS_DEFAULT_B2(B2) ) {    
    mpz_set_d (B2, B2scale * pow (ECM_COST * B1, DEFAULT_B2_EXPONENT));
    gmp_printf ("Using B1=%lu and B2=%Zd\n",(long) B1,B2);
  }






 // loop on hecm
  for(nb=1 ; nb <= options->nbtests ; nb++) {
    printf("Run %u on %u\n",nb,options->nbtests);



    // stage 1
    if ( options->smallParam == TRUE) {
      test = hecm1LowParam (f ,n ,k ,para ,&T1,&T2 ,options);
    }
    else {
      test = hecm1Normal (f ,n ,k ,para ,&T1,&T2 ,options);
    }

    if ( options->verbose >= OUTPUT_VERBOSE ) {
      // we need the parameters of the curve.
      printf ("The curve is given by ");
      printCurve (para,options,n);
    }

    if (test != HECM_NO_FACTOR_FOUND) {
      break;
    }




    // stage 2
    test = hecm2 (f,n,&T1,&T2,B2);
    if (test != HECM_NO_FACTOR_FOUND) {
      break;
    }



    // find parameters for the next curve
    test2 = nextParam (f,n,para,options);
    if (test2 == NEXT_SMALL_PARAM_TOO_BIG) {
      test = HECM_PARAM_TOO_BIG;
      break;
    }
    else if (test2 == NEXT_PARAM_ERROR) {
      test = HECM_ERROR;
      break;
    }

  }




  if (nb>options->nbtests) {
    nb = options->nbtests;
  }

  if ( ( para->nJacobi == 0 ) || ( para->nJacobi == 1 ) ) {
    para->nJacobi = 2;
  }



  if (test == HECM_ERROR) {
    printf("An error occured with the curve:\t" );
    printCurve(para,options,n);
    printf("It could be a multiplication by 0 in the projective space.\n" );
  }
  else if (test == HECM_GENERATION_FAIL) {
    printf("The generation of the curve failled. The parameters were:\t");
    printCurve(para,options,n);
  }
  else if (test == HECM_NO_FACTOR_FOUND) {
    printf("We test %u hyperelliptic curves (i.e. %u elliptic curves)\n",options->nbtests,options->nbtests*2); 
    printf("without finding factor of n.\n" );
  }
  else if (test == HECM_PARAM_TOO_BIG) {
    printf("There is no more curves with low parameters height.\n");
    printf("We test %u hyperelliptic curves (i.e. %u elliptic curves)\n",nb,nb*2); 
    printf("without finding factor of n.\n" );
  }
  else if (test == HECM_FOUND_N ) {
    printf("We divided by n\n");
    printf("We are on the curve of parameters:\t");
    printCurve(para,options,n);
    printf("We test %u hyperelliptic curves (i.e. %u elliptic curves)\n",nb,nb*2); 
  }
  else if (test == HECM_FOUND_ZERO_CURVE_1 ) {
    //TODO in that case, in general, we haven't check if we had the zero of the second curve
    printf ("k*P is the zero of the first elliptic curve modulo all the factors of n\n");
    printf("We are on the hyperelliptic curve of parameters:\t");
    printCurve(para,options,n);
    printf("We test %u hyperelliptic curves (i.e. %u elliptic curves)\n",nb,nb*2); 
  }
  else if (test == HECM_FOUND_ZERO_CURVE_2 ) {
    printf ("k*P is the zero of the second elliptic curve modulo all the factors of n\n");
    printf("We are on the hyperelliptic curve of parameters:\n");
    printf("\t");
    printCurve(para,options,n);
    printf("We test %u hyperelliptic curves (i.e. %u elliptic curves)\n",nb,nb*2); 
  }
  else if (test == HECM_FOUND_ZERO_CURVE_1_AND_2 ) {
    printf ("k*P is the zero of both elliptic curve modulo all the factors of n\n");
    printf("We are on the hyperelliptic curve of parameters:\t");
    printCurve(para,options,n);
    printf("We test %u hyperelliptic curves (i.e. %u elliptic curves)\n",nb,nb*2); 
  }
  else { // We found a real factor of n
    gmp_printf("A factor of n is %Zd\n",f);
    if ( options->verbose >= OUTPUT_VERBOSE ) {
      if (test == HECM_FACTOR_FOUND_MORPHISM) {
	printf("It was found during the computation of the morphisms.\n");
      }
      else if (test == HECM_FACTOR_FOUND_GENERATION) {
	printf("It was found during the generation of the curve.\n");
      }
      else if (test == HECM_FACTOR_FOUND_STEP1) {
	printf("It was found in stage 1.\n");
      }
      else if (test == HECM_FACTOR_FOUND_STEP2) {
	printf("It was found in stage 2.\n");
      }
    }
    printf("It was found by using the parameters:\t");
    printCurve(para,options,n);
    printf("We used %u hyperelliptic curves (i.e. %u elliptic curves).\n",nb,nb*2); 

  }


  optionsHECM_clear (options);
  mpz_clear(s);
  mpz_clear(k);
  mpz_clear(f);
  paraGenCurve_clear (para,n);
  mpres_clear (T1.x,n);
  mpres_clear (T1.y,n);
  mpres_clear (T1.A,n);
  mpres_clear (T2.x,n);
  mpres_clear (T2.y,n);
  mpres_clear (T2.A,n);
  mpmod_clear(n);
  mpz_clear (B2);

  temps_final = clock ();
  temps_cpu = (temps_final - temps_initial); 
  printf("The program took %g seconds.\n",temps_cpu/CLOCKS_PER_SEC);


  return 1;
}
