//=================================== Factorial.java =================================
package factorial;

import popcorn.*;
import java.math.BigInteger;
import popcorn.benchmark.*;

/**
 ** Computes the factorial of a big integer in parallel.
 **/
public class Factorial {
   static final int STEP = 10;

   public static void main(String[] args) {
      new popcorn.benchmark.DistributerTracer().start();
      long n = Long.parseLong(args[0]);
      SharedVariable product = new SharedVariable(new BigInteger("1"));

      for (int i=1; i<=n; i+=STEP) {
         ComputationPacket packet = new PartialProductPacket(i,Math.min(i+STEP,n),product);
         packet.go();
      }
      Computation.collectAll();
      System.out.println(n+"!="+product);
   }
}

/**
 ** A ComputationPacket that computes a product of the form
 ** <em>a*(a+1)* ... * b</em>.
 **/
class PartialProductPacket extends BenchmarkFilterPacket {
   SharedVariable product;

   public PartialProductPacket(long m1, long m2, SharedVariable product) {
      super(new PartialProductComputelet(m1,m2));
      this.product=product;
   }

   public void completed() {
      super.completed();
      BigInteger partialProduct = (BigInteger) getResult();
      product.lock();
      product.set(((BigInteger)product.value()).multiply(partialProduct));
      product.unlock();
      done();
   }

   public void failed() {
      go();
   }
}

class PartialProductComputelet implements Computelet, java.io.Serializable {
   long m1,m2;

   public PartialProductComputelet(long m1, long m2) {
      this.m1=m1;
      this.m2=m2;
   }

   public Object compute() {
      BigInteger product = new BigInteger("1");
      for (long i=m1; i<=m2; i++)
         product = product.multiply(BigInteger.valueOf(i));
      return product;
   }
}

class SharedVariable implements java.io.Serializable{
   private Object value=null;
   private boolean locked = false;

   public SharedVariable(Object value) {
      this.value=value;
   }

   public final Object value() {
      return value;
   }

   public void set(Object value) {
      this.value=value;
   }

   public synchronized void lock() {
      while(locked) {
         try {
            wait();
         } catch(InterruptedException e) {}
      }
      locked=true;
   }

   public synchronized void unlock() {
      locked=false;
      notify();
   }

   public String toString() {
      return value.toString();
   }
}

