import java.util.HashMap;
import java.util.Map;

/**
 * The value of this class lies in its fairly unpredictable call tree.
 *
 * <p>This program expects sequences of 0 and 1 as input. Each such sequence is
 * reduced by the application of the rules:</p>
 *
 * <pre>
 *   Rule 1.   0xyS --> S00
 *   Rule 2.   1xyS --> S1101
 * </pre>
 *
 * <p>where <code>x</code> and <code>y</code> can be any binary digit, and
 * <code>S</code> any binary sequence, including the empty sequence.</p>
 *
 * <p>For example, <code>01011</code> reduces as follows</p>
 *
 * <pre>
 *   01011 --> 1100 --> 01101 --> 0100 --> 000
 *        rule1    rule2     rule1    rule1
 * </pre>
 *
 * <p>Some sequences don't a finite reduction chain.  For example,
 * <code>1001</code> loops indefinitely.</p>
 *
 * <p>Usage:</p>
 *
 * <pre>
 *   $ java PNSystem 0101 01010
 * </pre>
 *
 *
 * @author:  Vadim Nasardinov (vadimn@redhat.com)
 * @since:   2004-02-12
 * @version: $Id: //core-platform/dev/src/com/arsdigita/developersupport/doc-files/PNSystem.java.txt#1 $
 **/
public final class PNSystem implements Runnable {
    private final String m_input;
    private final Map m_visited;
    private int m_rule1Count;
    private int m_rule2Count;

    private PNSystem(String input) {
        m_input = input;
        m_visited = new HashMap();
        m_rule1Count = 0;
        m_rule2Count = 0;
    }

    public static void main(String[] args) {
        if ( args.length==0 ) {
            usage();
            return;
        }

        for (int ii=0; ii<args.length; ii++) {
            Thread tt = new Thread(new PNSystem(args[ii]));
            tt.setName("thread" + ii);
            tt.start();
        }
    }

    private static void log(String str) {
        System.out.println(str);
    }

    private static void usage() {
        log("Usage:");
        log("    java PNSystem [arg1 [arg2 ...]]");
        log("Example:");
        log("    java PNSystem 1000100101 0111");
    }

    public void run() {
        if ( checksOutOK() ) {
            try {
                log(Thread.currentThread().getName() + ": " +
                    m_input + " --> " + reduce(m_input) +
                    " (" + times("Rule 1", m_rule1Count) +
                    ", " + times("Rule 2", m_rule2Count) + ".)");
            } catch (StackOverflowError err) {
                log("stack overflow");
            }
        }
    }

    private static String times(String rule, int count) {
        return rule + " applied " + count +
            (count==1 ? " time" : " times");
    }

    private boolean checksOutOK() {
        String illegal =
            m_input.replace('0', ' ').replace('1', ' ').trim();
        if ("".equals(illegal)) { return true; }

        log("Characters other than 0 or 1 are not allowed: '" +
            illegal + "'");
        return false;
    }

    private String reduce(String str) {
        final int count = m_rule1Count + m_rule2Count;

        if ( m_visited.containsKey(str) ) {
            Integer ordinal = (Integer) m_visited.get(str);
            return "loops starting with " + str + " at position " +
                count + " with a period of " +
                (count - ordinal.intValue() + ".");
        }
        m_visited.put(str, new Integer(count));

        if ( str.length() < 4 ) {
            return str;
        }

        if ( str.charAt(0) == '0' ) {
            return rule1(str);
        } else {
            return rule2(str);
        }
    }

    private String rule1(String str) {
        m_rule1Count++;
        return reduce(str.substring(3) + "00");
    }

    private String rule2(String str) {
        m_rule2Count++;
        return reduce(str.substring(3) + "1101");
    }
}
