#!/usr/bin/env perl
use warnings;
use strict;
use Math::Prime::Util ':all';
use Math::BigInt lib=>"GMP,Pari";

if (!Math::Prime::Util::prime_get_config->{gmp}) {
  print "\nYou should install Math::Prime::Util::GMP.\n\n";
}
$|++;

print "random prime proofs: 50, 100, 200, 300, 400 +/- 50 digits\n";
test_proofs(  4, 100, 71, 'mpu');  print "\n";
test_proofs( 50, 150, 71, 'mpu');  print "\n";
test_proofs(150, 250, 71, 'mpu');  print "\n";
test_proofs(250, 350, 71, 'mpu');  print "\n";
test_proofs(350, 450, 71, 'mpu');  print "\n";
#test_proofs(450, 550, 71, 'mpu');  print "\n";

# size: random primes with bit sizes randomly between 4 and this number
# num:  this many tests performed.  71 makes a nice 80-column display
# method: how to generate random primes:
#     Ideally we would use some independent code.
#     Time for one thousand random primes of this many bits:
#            300bits  600bits  which
#               1sec     4sec  mpu random_nbit_prime
#               1sec     5sec  mpu random_shawe_taylor_prime
#               1sec     7sec  mpu random_maurer_prime
#              67sec   240sec  pari
#             150sec   488sec  cpmaurer
#     We don't seem to have any practical choice other than MPU's
#     random_nbit_prime as the other random prime code is just so slow.
sub test_proofs {
  my($minsize, $size, $num, $prime_method) = @_;

  if      ($prime_method eq 'cpmaurer') {
    require Crypt::Primes;
  } elsif ($prime_method eq 'pari') {
    require Math::Pari;
    require Crypt::Random;
  } elsif ($prime_method eq 'mpu') {
    # nothing
  } else {
    die "Unknown random prime generation method\n";
  }

  my @ns;
  print "Generate ";
  $minsize = 4 if $minsize < 4;
  $minsize = $size if $minsize > $size;
  die "invalid size, must be > 4" unless $size > 4;
  foreach my $i (1..$num) {
    my $bits = int(rand($size-$minsize)) + $minsize;
    my $n;
    if      ($prime_method eq 'cpmaurer') {
      $n = Crypt::Primes::maurer(Size=>$bits);
    } elsif ($prime_method eq 'pari') {
      # Note: Pari 2.7 added randomprime which would work spectacularly.
      # But Math::Pari is the ancient version 2.1.5.
      #
      # We could use nextprime for ~4x speedup:
      #    $n = Math::Pari::nextprime( ...makerandom... );
      do { $n = Crypt::Random::makerandom(Size=>$bits,Strength=>0); }
         while !Math::Pari::isprime($n);
    } else {
      # Much faster than the others.
      $n = random_nbit_prime($bits);
    }
    push @ns, Math::BigInt->new("$n");
    # print a number corresponding to hundreds of bits
    print int(3.322*length("$n")/100);
  }
  print "\n";

  my @certs;
  print "Prove    ";
  foreach my $n (@ns) {
    my ($isp,$cert) = is_provable_prime_with_cert($n);
    die "$n is reported as $isp\n" unless $isp == 2;
    push @certs, [$n, $cert];
    print proof_mark($cert);
  }
  print "\n";

  print "Verify   ";
  prime_set_config(verbose=>1);
  foreach my $certn (@certs) {
    my $v = verify_prime($certn->[1]);
    print proof_mark($certn->[1]);
    next if $v;
    print "\n\n$certn->[0] didn't verify!\n\n";
    {
      my $c = $certn->[1];
      $c =~ s/^/  /smg;
      print $c;
    }
    die;
  }
  prime_set_config(verbose=>0);
  print "\n";
}

sub proof_mark {
  my $cert = shift;
  my $type;
  if (ref($cert) eq 'ARRAY') {
    $type = (scalar @$cert == 1) ? "bpsw" : $cert->[1];
    if ($type =~ /n-1/i) {
      $type = ($cert->[2]->[0] eq 'B') ? 'BLS7' : 'BLS5';
    }
  } else {
    return 'E' if $cert =~ /Type\s+ECPP/;
    ($type) = $cert =~ /Type (\S+)/;
  }
  if (!defined $type) { die "\nNo type:\n\n$cert"; }
  if    ($type =~ /bls5/i)      { return '5'; }
  elsif ($type =~ /bls7/i)      { return '7'; }
  if    ($type =~ /bls3/i)      { return '-'; }
  elsif ($type =~ /bls15/i)     { return '+'; }
  elsif ($type =~ /bpsw|small/i){ return '.'; }
  elsif ($type =~ /ecpp|agkm/i) { return 'E'; }
  warn "type: $type\n";
  return '?';
}
