/* gen-testdata.c
 * Daniel S. Roche, January 2009
 * http://www.cs.uwaterloo.ca/~droche/
 *
 * Program to generate test data sets for multiplication algorithm
 * comparison. The program takes a single command-line argument, for the
 * name of the output file.
 * The parameters are prompted for and read from stdin.
 * Printed to the output file are one line containing all the
 * parameters, followed by pairs of polynomials, one per line,
 * formatted as [a0 a1 a2 ... am] where a0..am are the coefficients.
 *
 * See LICENSE.txt for copyright and permissions.
 */

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <ctype.h>

// Source of random data
FILE *randIn;

void getLine (char *line, int maxlen) {
   fflush (stdout);
   line[maxlen-2] = '\0';
   if (!fgets (line, maxlen, stdin)) {
      puts ("Error reading from stdin; terminating.");
      exit(4);
   }
   if ((line[maxlen-2] != '\0') && (line[maxlen-2] != '\n')) {
      puts ("Line too long; terminating.");
      exit(4);
   }
}

char* getLong (char *source, long *x) {
   long i = 0;
   char *next;
   while (isspace (source[i])) ++i;
   if (!source[i]) return (source+i);
   *x = strtol (source+i, &next, 0);
   return next;
}

unsigned long getMask( long maxval ) {
   unsigned long mask = ~(0L);
   while (maxval) {
      mask <<= 1;
      maxval >>= 1;
   }
   return ~mask;
}

long randGen
   (long min, long max, unsigned long mask) 
{
   long val;

   do {
      while (fread(&val,sizeof(long),1,randIn) != 1);
      val &= mask;
   } while(val+min > max);

   return val + min;
}

int main(int argc, char **argv) {
   const int MAX_LINE = 1024;
   char line[MAX_LINE], *midline, c;
   int samedeg, complete=0;
   long t, min1, max1, min2, max2, p, k, w, deg, i, j, x; 
   unsigned long mask1, mask2, mask3;
   FILE *out;

   line[MAX_LINE-2] = '\0';

   if (argc != 2) {
      printf("Usage: %s output_file\n", argv[0]);
      return 1;
   }

   randIn = fopen("/dev/urandom","r");
   if (!randIn) {
      printf("Error opening /dev/urandom; terminating\n");
      return 2;
   }

   out = fopen(argv[1],"w");
   if (!out) {
      printf("Error opening %s; terminating\n", argv[1]);
      return 3;
   }

   while(1) {
      p = 167772161;
      printf ("Modulus to use [%ld]: ", p);
      getLine (line, MAX_LINE);
      getLong (line, &p);
      if (p >= 2) break;
      puts ("Modulus must be at least 2.");
   }

   while (1) {
      if (p == 167772161) { k=25; w=17; }
      else { k=1; w=p-1; }
      printf ("k and 2^k-PRU modulo %ld [%ld %ld]: ", p, k, w);
      getLine (line, MAX_LINE);
      midline = getLong (line, &k);
      getLong (midline, &w);

      if (k<0 || ((p-1)>>k)==0)
         printf ("k must satisfy 1 <= 2^k < %ld.\n", p);
      else if (w < 1 || w >= p)
         printf ("The PRU must be at least 1 and less than %ld.\n", p);
      else if (k==0 && w==1) break;
      else {
         x = w;
         for (i=1; i<k; ++i) {
            j = (long) (((double)x) * ((double)x) / ((double)p));
            x = x*x - j*p;
            x %= p;
         }
         if (x == p-1) break;
         printf ("%ld is not a 2^%ld-PRU modulo %ld.\n",w,k,p);
      }
   }

   while (1) {
      min1 = 10; max1 = 500;
      printf ("Size range for first polynomial [%ld %ld]: ", min1, max1);
      getLine (line, MAX_LINE);
      midline = getLong (line, &min1);
      if (*midline == '\0') break;
      max1 = min1;
      getLong (midline, &max1);
      if (0<min1 && min1<=max1) break;
      printf ("Size range must satisfy 0 < min <= max.\n");
   }

   while (1) {
      printf ("Make second polynomial in each pair the same size "
              "as the first? [y]: ");
      do { c = getchar(); } while (isspace(c) && c != '\n');
      if (c == '\n') c = 'y';
      else while (getchar() != '\n');
      if (c == 'y' || c == 'n') break;
      printf ("Please type 'y' or 'n'.\n");
   }
   samedeg = (c == 'y');

   min2 = min1; max2 = max1;
   while (!samedeg) {
      printf ("Size range for second polynomial [%ld %ld]: ", min2, max2);
      getLine (line, MAX_LINE);
      midline = getLong (line, &min2);
      if (*midline == '\0') break;
      max2 = min2;
      getLong (midline, &max2);
      if (0<min2 && min2<=max2) break;
      printf ("Size range must satisfy 0 < min <= max.\n");
      min2 = min1; max2 = max1;
   }

   while(1) {
      t = 1000;
      printf ("Number of test cases [%ld]: ", t);
      getLine (line, MAX_LINE);
      getLong (line, &t);
      if (t >= 1) break;
      puts ("Must perform at least one test!");
   }

   fprintf(out, "%lu %lu %lu %lu %lu %lu\n", 
      t, max1, max2, p, k, w);

   mask1 = getMask(max1-min1);
   mask2 = getMask(max2-min2);
   mask3 = getMask(p-1);

   printf ("Generating test cases.....  0%%");
   fflush(stdout);
   for(i=0; i<t; ++i) {
      if ((i*100)/t > complete) {
         complete = (int) ((i*100)/t);
         printf ("\b\b\b%2d%%", complete);
         fflush(stdout);
      }
      putc('[',out);
      deg = randGen(min1,max1,mask1);
      for(j=0; j<deg-1; ++j)
         fprintf(out,"%lu ", randGen(0,p-1,mask3));
      fprintf(out,"%lu]\n", randGen(1,p-1,mask3));

      putc('\t',out);
      putc('[',out);
      if (!samedeg) deg = (long)randGen(min2,max2,mask2);
      for(j=0; j<deg-1; ++j)
         fprintf(out,"%lu ", randGen(0,p-1,mask3));
      fprintf(out,"%lu]\n", randGen(1,p-1,mask3));
   }
   printf ("\b\b\b\b100%%\n");

   fclose(out);
   fclose(randIn);
   return 0;
}
