/* ArithmeticDrill: is an interactive drill that ends by reporting the
score and the time taken.  The program begins by asking the pupil a
number of questions in order to set up a drill.  This is a sample of
the start of a session.

Would you like a drill in (a)ddition or (m)ultiplication? m
How many digits do you want? 2
How many questions do you want in the drill? 2 

In this case the program generates 2 questions with two digit integers testing
multiplication.

It handles input numbers out of range with an error message but it
does not a handle non integer when it expected an integer.  

If too many digits are requested their is overflow which produces
negative answers due to two's complement integer format.  */

import java.io.*;
import java.util.*;

class ArithmeticDrill {

  /* main: the only method in ArithmeticDrill
     creates objects of class DigitRange and RandomRange
   */

   public static void main (String[] args) throws IOException {
     DataInputStream in = new DataInputStream(System.in);
     int digits, questions;
     System.out.print("Would you like a drill in (a)ddition or (m)ultiplication? ");
     System.out.flush();
     String drillType = in.readLine();
     String operatorString; // operatorString is either " + " or " - "
     boolean isAddition = drillType.equals("a");
     boolean isMultiplication = drillType.equals("m");
     if (! isAddition && ! isMultiplication) { // ERROR
       System.err.println("ERROR: must be a or m");
       return;
     }
     System.out.print ("How many digits do you want in the numbers? ");
     System.out.flush();
     digits = Integer.parseInt(in.readLine());
     if (digits < 1 || (isAddition && digits > 10 ) || (isMultiplication && digits > 5)) {
       System.err.println("Must enter a positive number"); // <=10 for + and <=5 for *
       return;
     }
     System.out.print ("How many questions do you want? ");
     System.out.flush();
     questions = Integer.parseInt(in.readLine());
     if (questions < 1) {
       System.err.println("Must enter a positive number");
       return;
     }
     DrillQuestion test = new DrillQuestion(isAddition, digits, questions);
     test.startTimer();
     while (test.hasMoreQuestions() ) {
       test.askQuestion();
       test.getAnswer(Integer.parseInt(in.readLine())); // no checking for int
       test.recordScoreRecordError();
       if (test.inError())
	 test.reportError();
     }
     test.stopTimer();
     test.reportScore();
   }
}

/* DrillQuestion: needs documentation
Its use is illustrated by the main method above.
 */
class DrillQuestion {
  private static final String equalsSign = " = ";
  private String op;
  private boolean isAddition;
  private RandomRange rand;
  private long startTimer, stopTimer, elapsed;
  private int first, second;
  private int response, correct;
  private boolean inError;
  private int score = 0, near = 0, questions = 0;
  private int numberOfQuestions;
  private static double nearPercent = .10;

  DrillQuestion (boolean isAddition, int digits, int questions) {
    rand = new RandomRange(new DigitRange(digits));
    this.op = op;
    this.rand = rand;
    numberOfQuestions = questions;
    this.isAddition = isAddition;
    if (isAddition)
      op = " + ";
    else // isMultiplication
      op = " * ";
  }
  public boolean hasMoreQuestions () {
    return questions < numberOfQuestions;
  }
  public DrillQuestion askQuestion () {
    first  = rand.nextInt();
    second = rand.nextInt();
    System.out.print(first + op + second + equalsSign );
    System.out.flush();
    if (isAddition)
      correct = first+second;
    else
      correct = first*second;
    questions = questions + 1;
    return this;
  }
  public DrillQuestion getAnswer(int response) {
    this.response = response;
    return this;
  }
  public DrillQuestion recordScoreReportError() {
    if (correct == response) score++;
    else {
      System.out.println("X " + correct);
      if (Math.abs((double)(correct - response)/correct) < nearPercent) near++;
    }
    return this;
  }
  public DrillQuestion recordScoreRecordError() {
    if (correct == response) {
      score++;
      inError = false;
    }
    else {
      inError = true;
      if (Math.abs((double)(correct - response)/correct) < nearPercent) near++;
    }
    return this;
  }
  public boolean inError () { return inError;}
  public void reportError () {
    if (inError)
      System.out.println("X " + correct);
  }
  public void startTimer () {    startTimer = System.currentTimeMillis();}
  public void stopTimer () {    stopTimer = System.currentTimeMillis();}
  public void reportScore() {
    elapsed = Math.round((stopTimer - startTimer)/1000.0);
    System.out.println("Time elapsed:   " + elapsed + " seconds");
    System.out.println("Your score was: " + score + " out of " + questions);
    System.out.println("You were near: " + near + " out of " + questions);
    if (questions > 0) {
      System.out.println("Seconds/question " + (double)elapsed/questions);
      System.out.println("Percent correct " + (100.*score/questions) + " %");
      System.out.println("Percent Near " + (100.*near/questions) + " %");
    }
  }    
}

/*
DigitRange: constructs a range used by RandomRange
numberOfDigits describes 
              half open range or closed range in RandomRange
                  [lo,  hi)
1	          [ 1,  10)       [ 1,  9]
2                 [10, 100)       [10, 99]
and so on
It fails if numberOfDigits is less than 1.
 */

class DigitRange {
  private int lo, hi;
  DigitRange (int numberOfDigits) {
    int power10 = (int)Math.pow(10,numberOfDigits);
    lo = power10/10;
    hi = power10;
  }
  public int lo () { return lo;}
  public int hi () { return hi;}
}

/* RandomRange: given a DigitRange object
constructs a Random integer generator in a range
DigitRange produces random numbers in the range
[lo(), hi()-1]     [shift, width-1+shift]
It fails if only if DigitRange fails so RandomRange is reliable
*/

class RandomRange {
  private int width;
  private int shift;
  private Random rand = new Random(0);
  RandomRange (DigitRange d) {
    shift = d.lo();
    width = d.hi() - shift;
  }
  /* nextInt: 
     @returns the next integer in the range
     [shift, width-1+shift]
  */
  public int nextInt() {
    return Math.abs(rand.nextInt() % width) + shift;
  }
}

