/*
 *  Copyright(c)2002 Forward Computing and Control Pty. Ltd.
 *  ACN 003 669 994, NSW, Australia      All rights Reserved
 *  
 *   Written by Dr. M.P. Ford
 */
import au.com.forward.threads.*;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Date;
import java.text.SimpleDateFormat;

/**
 *  finds a prime using Java's BigInteger.isProbablePrime() 
 *
 * @author     matthew ford 
 * @created    5 May 2002 
 */
class TestForPrime extends Thread {
  /**
   *  Increment by 2 to skip even numbers
   */
  private static BigInteger two = new BigInteger("2");
  
  /**
   *  The number to test
   */
  private BigInteger number;
  
  /**
   *  The certainty level = 2^-certainty
   */
  private int certainty;
  
  /**
   *  How many numbers to test starting from the first one
   */
  private int numberToTest;


  /**
   *  Constructor for TestForPrime 
   *
   * @param  name          the name of this thread 
   * @param  rangeStart    the starting number to test
   * @param  numberToTest  how many numbers this thread will test starting from the rangeStart
   * @param  certainty     the certainty parameter 
   */
  TestForPrime(String name, BigInteger rangeStart, int numberToTest, int certainty) {
    super(name);
    if (rangeStart.signum() != 1) {
      throw new IllegalArgumentException(rangeStart.toString() + " is not positive.");
    }
    if (rangeStart.mod(two).equals(BigInteger.ZERO)) {
      // make it odd
      rangeStart = rangeStart.add(BigInteger.ONE);
    }
    number = rangeStart;

    if (numberToTest < 1) {
      throw new IllegalArgumentException("Must test at least one number");
    }
    this.numberToTest = numberToTest;

    if (certainty < 1) {
      throw new IllegalArgumentException("Certainty argument must be >= 1");
    }
    this.certainty = certainty;
  }


  /**
   *  The TestForPrime run() method 
   *  Loops testing each number in the range for primality.
   */
  public void run() {
    try {
      System.out.println("TestForPrime:" + Thread.currentThread().getName() + " " + 
          " starting from " + number.toString());

      for (int i = 0; i < numberToTest; i++) {
        System.out.println("TestForPrime:" + Thread.currentThread().getName() + " " + 
            new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + " loop number " + i);
        if (number.isProbablePrime(certainty)) {
          ThreadReturn.save(number);
          return;
        }
        ThreadReturn.ifInterruptedStop(); // have we been stopped
        number = number.add(two);  // else try next one
      }
    } catch (Throwable t) {
      ThreadReturn.save(t);
    }
  }
}



/**
 *  Find the prime 
 *
 * @author     matthew ford 
 * @created    October 21, 2001 
 */
public class Primes implements ThreadListener {


  /**
   *  the newline string for this system 
   */
  private static String nl;


  /**
   * set up the nl string for this system
   */
  static {
    nl = (String) java.security.AccessController.doPrivileged(
        new sun.security.action.GetPropertyAction("line.separator"));
  }


  /**
   *  The main program 
   *
   * @param  args  The command line arguments.
   *               Could be used to pass in the number and the certainty,
   *               but not implemented here.
   */
  public static void main(String[] args) {
    int certainty = 100;
    String numberString = "1224343333333334549999999999999999999999999555555555555555555333333333333333333333333";
    new Primes().findPrime(numberString, certainty);
  }

  
  /**
   *  find the first probable prime greater than or equal to this number 
   *  If the first three threads don't find one, create another three threads and try again.
   *
   * @param  numberString  number to start looking from 
   * @param  certainty     the probability that the number found is a prime is 
   *      2^-certainty 
   */
  public void findPrime(String numberString, int certainty) {
    BigInteger prime = null;  // the result
    TestForPrime t_1=null, t_2=null, t_3=null; // the three threads
        
    System.out.println(
        new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + 
        "  Finding Prime with accuracy of 2^-" + certainty + " starting at ");
    System.out.println(numberString);

    BigInteger number = new BigInteger(numberString);
    
    // have each thread test this many numbers, skipping even numbers
    int increment = 20;
    BigInteger numberIncrement = (new BigInteger("2")).multiply(new BigInteger("" + increment));
    
    int counter = 1; // counter for thread names
    
    try {
      while (prime == null) {
        // loop here until possible prime found
        t_1 = new TestForPrime("Thread_"+counter++, number, increment, certainty);
        number = number.add(numberIncrement);
        t_2 = new TestForPrime("Thread_"+counter++, number, increment, certainty);
        number = number.add(numberIncrement);
        t_3 = new TestForPrime("Thread_"+counter++, number, increment, certainty);
  
        // update for next loop
        number = number.add(numberIncrement);
  
        // add listeners
        ThreadReturn.addListener(t_1, this);
        ThreadReturn.addListener(t_2, this);
        ThreadReturn.addListener(t_3, this);
  
        // start all the threads
        t_1.start();
        t_2.start();
        t_3.start();

        // wait at most 2 seconds for the threads to finish
        TimeOut timeOut = new TimeOut(2000);
        if( (prime = (BigInteger)ThreadReturn.join(t_1, timeOut.timeRemaining())) != null) { break;};
        if( (prime = (BigInteger)ThreadReturn.join(t_2, timeOut.timeRemaining())) != null) { break;};
        if( (prime = (BigInteger)ThreadReturn.join(t_3, timeOut.timeRemaining())) != null) { break;};
      } // while(prime == null)
        
    } catch (TimedOutException toe) {
      System.out.println(" Did not finish in time: " + StackTrace.toString(toe));
    } catch (ThreadInterruptedException tiex) {
      System.out.println("Caught ThreadInterruptedException in findPrime() "
                          +StackTrace.toString(tiex));
    } catch (ThreadException tex) {
      System.out.println("Caught ThreadException in findPrime() "+StackTrace.toString(tex));
    } catch (InterruptedException iex) {
      System.out.println("Caught InterruptedException in findPrime() "+StackTrace.toString(iex));
    } finally {
      // stop all the threads if any one thread finds a probable prime
      // or throws an exception
      t_1.interrupt();
      t_2.interrupt();
      t_3.interrupt();
    }
      
    if (prime != null) {
     System.out.println("findPrime() returns at " + 
         new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + nl +
          "  with prime:" + prime.toString() + nl);
    }      

  }


  
  // ThreadListener methods
  /**
   *  Result listener.
   *  This method is called when the thread finishes normally. 
   *
   * @param  e  ThreadEvent 
   */
  public synchronized void threadResult(ThreadEvent e) {
    Object obj = e.getObject();
    if (obj == null) {
        // thread did not find a prime
      System.out.println(nl+
          new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + 
          " Thread:" + e.getThread().getName() + " did not find probable prime: " + nl); 
    } else {
      System.out.println(nl+
        new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + 
        " Thread:" + e.getThread().getName() + " found prime: " + nl + 
        "  " + ((BigInteger) e.getObject()).toString() + nl);
    }
  }

  /**
   *  Interrupted listener.
   *  This method is called when the thread is stopped due to interrupt
   *
   * @param  e  ThreadEvent 
   */
  public synchronized void threadInterrupted(ThreadEvent e) {
    System.out.println(
        new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + 
        " in threadInterrupted with ThreadEvent:" + nl + e.toString());
  }

  /**
   *  Error listener.
   *  This method is called when the thread is stopped due to other exceptions
   *
   * @param  e  ThreadEvent 
   */
  public synchronized void threadError(ThreadEvent e) {
    System.out.println(
        new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + 
        " in threadError with ThreadEvent:" + nl + e.toString());
  }
}
