/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */

import java.util.WeakHashMap;

/**
 * Fill up a WeakHashSet with linked list elements.  Kill off every second element
 * and add new elements to the list.  Rinse and repeat.
 */
public class ReferenceStress {

  static boolean verbose = false;
  static int elements = 0;

  private static synchronized void report(int completed) {
    elements += completed;
  }

  /**
   * The element type of the hash table - a simple linked list
   */
  private static class Element {
    Element next;
    Element(Element next) { this.next = next; }
    void deleteNext() { if (next != null) next = next.next; }
    int length() {
      int result = 1;
      Element cursor = next;
      while (cursor != null) {
        cursor = cursor.next;
        result++;
      }
      return result;
    }
  }

  /**
   * The kernel of the test.  Add elements to the linked list/hash table, while
   * killing off half of them.
   *
   * @param liveSize
   * @param iterations
   */
  private static void thrash(int liveSize, int iterations) {
    WeakHashMap<Element,Integer> map = new WeakHashMap<Element,Integer>();
    Element list = new Element(null);
    Integer serial = Integer.valueOf(0);

    for (int i=0; i < liveSize; i++) {
      list = new Element(list);
      map.put(list, serial++);
    }

    for (int j=0; j < iterations; j++) {
      Element cursor = list;
      int inserts = 0;
      while (cursor != null) {
        cursor.deleteNext();
        cursor = cursor.next;
        list = new Element(list);
        map.put(list, serial++);
        inserts++;
      }
      if (verbose) {
        System.out.println("Map size "+map.size()+", list length "+list.length());
      }
      report(inserts);
    }
  }

  /**
   * Print usage and die
   */
  private static void usage() {
    System.err.println("Usage: ReferenceStress [-verbose] [-threads n] [-size s] [-iterations i] [perf|base]");
    System.exit(1);
  }

  public static void main(String[] args) {
    /* Default values of parameters */
    int liveSize = 5000;
    int iterations = 100;
    int threads = 2;

    for (int i=0; i < args.length; i++) {
      if (args[i].charAt(0) == '-') {
        if (args[i].equals("-verbose")) {
          verbose = true;
        } else if (args[i].equals("-threads")) {
          threads = Integer.valueOf(args[++i]);
        } else if (args[i].equals("-iterations")) {
          iterations = Integer.valueOf(args[++i]);
        } else if (args[i].equals("-size")) {
          liveSize = Integer.valueOf(args[++i]);
        } else {
          System.out.println("Unrecognized switch "+args[i]);
          usage();
        }
      } else if (args[i].equals("perf") || args[i].equals("base")) {
        // parameter has no effect
      } else {
        System.out.println("Unrecognized parameter "+args[i]);
        usage();
      }
    }

    System.out.println("Running "+threads+" threads with "+liveSize+" entries for "+iterations+" iterations");

    Thread[] threadTable = new Thread[threads];

    final int finalLiveSize = liveSize;
    final int finalIterations = iterations;

    for (int i=0; i < threads; i++) {
      threadTable[i] = new Thread() {
        public void run() {
          thrash(finalLiveSize, finalIterations);
        }
      };
    }

    long start = System.nanoTime();

    // Start the threads
    for (int i=0; i < threads; i++) {
      threadTable[i].start();
    }

    // Wait for them to complete
    for (int i=0; i < threads; i++) {
      try {
        threadTable[i].join();
      } catch (InterruptedException e) {
      }
    }
    long time = (System.nanoTime() - start)/1000000;
    System.out.println(elements + " references inserted in " + time + "ms.");
    System.out.println("Overall: SUCCESS");
  }
}
