/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.ode.jacob.examples.eratosthenes;

import org.apache.ode.jacob.JacobRunnable;
import org.apache.ode.jacob.SynchChannel;
import org.apache.ode.jacob.SynchChannelListener;
import org.apache.ode.jacob.vpu.ExecutionQueueImpl;
import org.apache.ode.jacob.vpu.JacobVPU;

/**
 * Sieve of Eratosthenes prime number generator.
 * This class represents the following process term:
 * <pre><em>
 * Sieve :=
 *  (v integers)(v primes) Counter(integer,2) | Head(integer, primes) | Print(primes)
 * </em></pre>
 *
 * <p>Created on Feb 12, 2004 at 6:32:49 PM.</p>
 *
 * @author Maciej Szefler <a href="mailto:mbs@fivesight.com">mbs</a>
 */
public class Sieve extends JacobRunnable {
  private static final long serialVersionUID = -1303509567096202776L;

  private static int _cnt = 0;
  private static int _last = 0;

  public void run() {
    NaturalNumberStreamChannel integers =  newChannel(NaturalNumberStreamChannel.class);
    NaturalNumberStreamChannel primes =  newChannel(NaturalNumberStreamChannel.class);
    instance(new Counter(integers,2));
    instance(new Head(integers,primes));
    instance(new Print(primes));
  }

  public class Foo {}

  /**
   * A counter, aka integer stream generator; represents the following process term:
   * <pre><em>
   *  Counter(out, n) := out.val(n) | Counter(out, n+1)
   * </em></pre>
   */
  private static class Counter extends JacobRunnable {
    private static final long serialVersionUID = 4739323750438991003L;

    private NaturalNumberStreamChannel _out;
    private int _n;

    public Counter(NaturalNumberStreamChannel out, int n) {
      _out = out;
      _n = n;
    }

    public void run() {
      _out.val(_n, object(new SynchChannelListener(newChannel(SynchChannel.class)) {
        private static final long serialVersionUID = -4336285925619915276L;

        public void ret() {
          instance(new Counter(_out, _n+1));
        }
      }));
    }
  }

  /**
   * Head extractor, takes the first element from a stream; this represent the process
   * term:
   * <pre><em>
   * Head(in, primes) :=
   *  in ? [val(n)={primes.val(n) | (v x) PrimeFilter(n, in, x) | Head(x,primes)}]
   * </em></pre>
   *
   *
   */
  private static final class Head extends JacobRunnable {
    private static final long serialVersionUID = 1791641314141082728L;

    NaturalNumberStreamChannel _in;
    NaturalNumberStreamChannel _primes;

    public Head(NaturalNumberStreamChannel in, NaturalNumberStreamChannel primes) {
      _in = in;
      _primes = primes;
    }

    public void run() {
      object(new NaturalNumberStreamChannelListener(_in) {
        private static final long serialVersionUID = -2145752474431263689L;

        public void val(final int n, final SynchChannel ret) {
          _primes.val(n, object(new SynchChannelListener(newChannel(SynchChannel.class)) {
            private static final long serialVersionUID = -3009595654233593893L;

            public void ret() {
              NaturalNumberStreamChannel x = newChannel(NaturalNumberStreamChannel.class);
              instance(new PrimeFilter(n, _in, x));
              instance(new Head(x, _primes));
              ret.ret();
            }
          }));
       }
      });
    }
  }

  private static final class Print extends JacobRunnable {
    private static final long serialVersionUID = -3134193737519487672L;

    private NaturalNumberStreamChannel _in;
    public Print(NaturalNumberStreamChannel in) {
      _in = in;
    }
    public void run() {
      object(true, new NaturalNumberStreamChannelListener(_in){
        private static final long serialVersionUID = 7671019806323866866L;

        public void val(int n, SynchChannel ret) {
          _cnt ++;
          _last = n;
          System.out.println("PRIME: " + n);
          ret.ret();
        }
      });
    }
  }

  /**
   * A prime filter, filters out the nubmer in an input stream that are multiple of a
   * prime. This represents the following process term:
   * <pre><em>
   * PrimeFilter(prime, in, out) :=
   *     ! in ? [val(n)={ if(n mod prime <> 0) out.val(n) }
   * </em></prime>
   */
  private static class PrimeFilter extends JacobRunnable {
    private static final long serialVersionUID = 1569523200422202448L;

    private int _prime;
    private NaturalNumberStreamChannel _in;
    private NaturalNumberStreamChannel _out;

    public PrimeFilter(int prime, NaturalNumberStreamChannel in, NaturalNumberStreamChannel out) {
      _prime = prime;
      _in = in;
      _out = out;
    }
    public void run() {
       object(true, new NaturalNumberStreamChannelListener(_in) {
        private static final long serialVersionUID = 6625386475773075604L;

        public void val(int n, final SynchChannel ret) {
           if (n % _prime != 0)
             _out.val(n, object(new SynchChannelListener(newChannel(SynchChannel.class)) {
              private static final long serialVersionUID = 2523405590764193613L;

              public void ret() {
                 ret.ret();
               }
             }));
           else
             ret.ret();
         }
       });
    }
  }


  public static void main(String args[]) {
    if (args.length != 1) {
      System.err.println("JACOB Sieve of Eratosthenes Prime Number Generator Demonstration");
      System.err.println("usage: java " + Sieve.class.getName() + " requested-prime");
      System.err.println("  requested-prime = which prime to show (0->inf)");
      System.exit(1);
    } else {
      int request = Integer.parseInt(args[0]);
      JacobVPU vpu = new JacobVPU();
      vpu.setContext(new ExecutionQueueImpl(null));
      vpu.inject(new Sieve());
      while (_cnt != request) {
        vpu.execute();
      }
      System.err.println("The " + _cnt + "th prime is " + _last);
    }


  }
}
