import SimpleOutput;

/**
 * A number of implementations of functions to compute the nth
 * Fibonacci number.  Intended as part of an answer key for
 * HW6 of CS152 99S.
 *
 * This can be used as a utility class (in which case it is recommended
 * that you use fibdp), or it can serve as a tester for the various 
 * Fibonacci methods.  In the latter case, the command line syntax is:
 *   % java Fiboncci i1 i2 ... in
 * (which computes the i1th, i2th, ... inth Fibonacci number using
 * each technique, and reports on the time).
 *
 * @author Samuel A. Rebelsky
 * @version 1.0 of March 1999
 */
public class Fibonacci {
 
  // +--------+--------------------------------------------------
  // | Fields |
  // +--------+

  /**
   * An array of computed Fibonacci numbers.  Used for the
   * dynamic programming version of the function.  Eventually,
   * FIB[n] is the nth Fibonacci number.  When initialized,
   * FIB[n] is 0.
   */
  protected long FIB[];


  // +---------+-------------------------------------------------
  // | Methods |
  // +---------+
   
  /**
   * Compute the nth Fibonacci number using the naive recursive
   *   technique.
   * Pre: n >= 0
   * Pre: the nth Fibonacci number >= Long.MAX_VALUE
   * Post: returns the nth Fibonacci number
   */
  public long fib(long n) {
    // Base case: the 0th Fibonacci number is 0, the 1st
    // Fibonacci number is 1.
    if (n <= 1) return n;
    // Recursive case: the nth Fibonacci number is
    // the n-1st Fibonacci number + the n-2nd Fibonacci number.
    return fib(n-1) + fib(n-2);
  } // fib(long)

  /**
   * Compute the nth Fibonacci number using the naive recursive
   *   technique, supplemented by dynamic programming.
   * Pre: n >= 0
   * Pre: the nth Fibonacci number >= Long.MAX_VALUE
   * Post: returns the nth Fibonacci number
   */
  public long fibdp(long n) {
    // Base case: the 0th Fibonacci number is 0, the 1st
    // Fibonacci number is 1.
    if (n <= 1) return n;
    // Is there a table?  If not, make one.  Takes advantage
    // of the fact that Java uses 0 as the default value for
    // longs (so we'll assume that any 0 entry is not yet
    // filled in).
    if (FIB == null) FIB = new long[(int) n+1];
    // Is the table big enough?  Needs to be checked in case
    // there are independent calls.
    if (FIB.length <= n) {
      // Remember the old table.
      long[] tmp = FIB;
      // Build a new table.
      FIB = new long[(int) n+1];
      // Copy over the elements.
      for (int i = 0; i < tmp.length; ++i) {
        FIB[i] = tmp[i];
      } // for
    } // if the table isn't big enough
    // Does the table have the entry?  A nonzero entry means
    // that we've already computed the nth Fibonacci number.
    if (FIB[(int) n] != 0) return FIB[(int) n];
    // If we've gotten this far, we need to compute the nth
    // Fibonacci number.
    FIB[(int) n] = fibdp(n-1) + fibdp(n-2);
    // And now that we've computed it, we can return it.
    return FIB[(int) n];
  } // fibdp(long)

  /**
   * Compute the nth Fibonacci number using an iterative 
   * technique.  We keep track of the ith and i-1st Fibonacci
   * numbers, use them to generate the i+1st Fibonacci number,
   * and then update i.
   * Pre: n >= 0
   * Pre: the nth Fibonacci number >= Long.MAX_VALUE
   * Post: returns the nth Fibonacci number
   */
  public long fibit(long n) {
    int i;       // Counter variable
    int ithfib;  // The ith Fibonacci number
    int prevfib; // The previous Fibonacci number
    int nextfib; // The next Fibonacci number
    // The technique doesn't work for n = 0, so use the
    // default answer for that particular case.
    if (n == 0) return 0;
    // Initialize everything.
    i = 1;
    ithfib = 1;
    prevfib = 0;
    // Keep going until we reach n.
    while (i < n) {
      nextfib = ithfib + prevfib;
      // Move on to the next i.
      i = i + 1;
      // The old ith Fibonacci number is now the previous
      // Fibonacci number.
      prevfib = ithfib;
      // The old next Fibonacci number is now the new ith
      // Fibonacci number.
      ithfib = nextfib;
    } // while
    // i is now n, so the ith Fibonacci number is the nth Fibonacci
    // number.
    return ithfib;
  } // fibit(long)

  /**
   * Compute the nth Fibonacci number using a recursive algorithm
   * similar to the one used in fibit.
   * Pre: n >= 0
   * Pre: the nth Fibonacci number >= Long.MAX_VALUE
   * Post: returns the nth Fibonacci number
   */
  public long fibrec(long n) {
    if (n == 0) return 0;
    else return fibrec(n, 1, 1, 0);
  } // fibrec
  
  /**
   * Compute the nth Fibonacci number given the ith and i-1st
   * Fibonacci numbers.
   * Pre: n >= 1
   * Pre: the nth Fibonacci number >= Long.MAX_VALUE
   * Post: returns the nth Fibonacci number
   */
  public long fibrec(long n, long i, long ithfib, long prevfib) {
    // Base case: i is n, so the ith Fibonacci number is the
    //   nth Fibonacci number.
    if (i == n) return ithfib;
    // Recursive case
    else return fibrec(n, i+1, ithfib+prevfib, ithfib);
  } // fibrec(long,long,long,long)

  // +------+----------------------------------------------------
  // | Main |
  // +------+

  /**
   * Get some values of N and compute.
   */
  public static void main(String[] args) {
    // Create an array of inputs.
    long[] vals = new long[args.length];
    // Create an array of results.
    long[] results = new long[args.length];
    // Two time values, used to determine how long the various
    // computations take.
    long start;
    long stop;
    // The wonderful thing that does all the computation.
    Fibonacci computer = new Fibonacci();
    // And something for output.
    SimpleOutput out = new SimpleOutput();
    // Fill in the values.  Use 0 if the user gives a bad
    // value.  This is intended for testing only, so the
    // user interface is primitive at best.
    for (int i = 0; i < args.length; ++i) {
      try { vals[i] = Long.parseLong(args[i]); }
      catch (Exception e) { }
    } // for

    // "Prime" the various methods (so that there's not overhead
    // in loading their code, optimizing their code, or whatever
    // else the interpeter decides to do).
    computer.fib(3);
    computer.fibdp(3);
    computer.fibit(3);
    computer.fibrec(3);

    // Determine how long the basic method takes.
    start = System.currentTimeMillis();
    for (int i = 0; i < vals.length; ++i) {
      results[i] = computer.fib(vals[i]);
    } // for
    stop = System.currentTimeMillis();
    // Report
    out.println("Naive recursive method: ");
    for (int i = 0; i < vals.length; ++i) {
      out.println("  fib(" + vals[i] + ") = " + results[i]);
    } // for
    out.println("  COMPUTATION TOOK " + (stop-start) + " MILLISECONDS");

    // Determine how long the dynamic processing technique takes
    start = System.currentTimeMillis();
    for (int i = 0; i < vals.length; ++i) {
      results[i] = computer.fibdp(vals[i]);
    } // for
    stop = System.currentTimeMillis();
    // Report
    out.println("Dynamic programming method: ");
    for (int i = 0; i < vals.length; ++i) {
      out.println("  fibdp(" + vals[i] + ") = " + results[i]);
    } // for
    out.println("  COMPUTATION TOOK " + (stop-start) + " MILLISECONDS");

    // Determine how long the iterative technique takes
    start = System.currentTimeMillis();
    for (int i = 0; i < vals.length; ++i) {
      results[i] = computer.fibit(vals[i]);
    } // for
    stop = System.currentTimeMillis();
    // Report
    out.println("Iterative method: ");
    for (int i = 0; i < vals.length; ++i) {
      out.println("  fibit(" + vals[i] + ") = " + results[i]);
    } // for
    out.println("  COMPUTATION TOOK " + (stop-start) + " MILLISECONDS");

    // Determine how long the other recursive technique takes
    start = System.currentTimeMillis();
    for (int i = 0; i < vals.length; ++i) {
      results[i] = computer.fibrec(vals[i]);
    } // for
    stop = System.currentTimeMillis();
    // Report
    out.println("Other recursive method: ");
    for (int i = 0; i < vals.length; ++i) {
      out.println("  fibrec(" + vals[i] + ") = " + results[i]);
    } // for
    out.println("  COMPUTATION TOOK " + (stop-start) + " MILLISECONDS");
  }
    
} // class Fibonacci

