
var logging=true;
function log(message) {}

var nbsp = "\u00A0";

var LEFT_ARROW_KEY = 37;
var RIGHT_ARROW_KEY = 39;
var BACKSPACE_KEY = 8;
var DELETE_KEY = 46;
var ZERO_KEY = 48;
var NINE_KEY = 57;

//================================================================================

function randomIntLimitedBy (n) {
  return Math.max (0, Math.min (n-1, Math.floor (n*Math.random())));
}

function cube(n) {
  return n*n*n;
}

function insertCommas (numberString) {
  var firstPos = numberString.length % 3;
  log ("numberString = " + numberString);
  log ("firstPos = " + firstPos);
  var result = "";
  var startPos = 0;
  var pos = firstPos;
  while (pos <= numberString.length) {
    if (result.length > 0) result = result + ",";
    var portion = numberString.substring (startPos, pos);
    log ("extract from " + startPos + " to " + pos + " = #" + portion + "#");
    result = result + portion;
    startPos = pos;
    pos = pos+3;
  }
  return result;
} 

function mod11Expression (digitsString) {
  var len = digitsString.length;
  log ("mod11Expression of #" + digitsString + "# with len " + len);
  var result = digitsString.substring (len-1, len);
  var subtract = true;
  for (var i = len-2; i >= 0; i--) {
    var digit = digitsString.substring (i, i+1);
    result = result + (subtract ? " - " : " + ") + digit;
    subtract = !subtract;
  }
  return result;
}


//================================================================================
function CubeRootPractice (view) {
  log ("new CubeRootPractice");
  this.view = view;
  view.model = this;
  this.initCube();
}

CubeRootPractice.prototype = {
  
  initCube: function () {
    this.digits = new Array(3);
    this.digits[0] = 1 + randomIntLimitedBy (9);
    this.digits[1] = randomIntLimitedBy (10);
    this.digits[2] = randomIntLimitedBy (10);
    this.guessed = new Array(3);
    this.cubeRoot =  this.digits[0]*100 + this.digits[1]*10 + this.digits[2];
    log ("cubeRoot = " + this.cubeRoot);
    this.cube = cube (this.cubeRoot);
    log ("cube = " + this.cube);
    this.cubeString = "" + this.cube;
    this.cubeStringWithCommas = insertCommas (this.cubeString);
    this.numCubeDigits = this.cubeString.length;
    log ("numCubeDigits = " + this.numCubeDigits);
    this.view.setCube (this.cubeStringWithCommas);
    this.focus = null;
    this.view.clearFocus();
    this.view.clearExplanation();
    for (var i = 0; i<3; i++) {
      this.clearGuessAtPos (i);
    }
  }, 
  
  clickOnDigit: function (pos) {
    log ("clicked on digit pos=" + pos);
    this.setFocusToDigit (pos);
  }, 
  
  handleLeftArrow: function() {
    log ("left arrow");
    if (this.focus != null && this.focus > 0) {
      this.setFocusToDigit (this.focus - 1);
    }
  }, 
  
  handleRightArrow: function() {
    log ("right arrow");
    if (this.focus != null && this.focus < 2) {
      this.setFocusToDigit (this.focus + 1);
    }
  }, 
  
  explain: function (explanation) {
    this.view.explain (explanation);
  }, 
  
  clearExplanation: function() {
    this.view.clearExplanation();
  }, 
  
  setFocusToDigit: function (pos) {
    this.focus = pos;
    this.view.setFocusToDigit (pos);
  }, 
  
  handleDelete: function() {
    log ("CubeRootPractice.handleDelete ...");
    if (this.focus != null) {
      this.clearGuessAtPos (this.focus);
      this.clearExplanation();
    }
  }, 
  
  handleDigit: function (digit) {
    log ("CubeRootPractice.handleDigit, digit = " + digit);
    if (this.focus != null) {
      this.setGuessAtPos (this.focus, digit);
      if (this.guessed[0] != null && this.guessed[1] != null && this.guessed[2] != null) {
        this.checkAnswer();
      }
    }
  }, 
  
  checkAnswer: function () {
    var guessCubeRoot = 100*this.guessed[0] + 10*this.guessed[1] + this.guessed[2];
    if (guessCubeRoot == this.cubeRoot) {
      this.explain ("CONGRATULATIONS! You got it right!");
    }
    else {
      var explanationRoot = ("<p>SORRY, WRONG! The cube root of " + this.cubeStringWithCommas + 
              " is <b>" + this.cubeRoot + "</b>.</p>\n");
      var explanation0 = "";
      var digit0Wrong = this.digits[0] != this.guessed[0];
      if (digit0Wrong) {
        explanation0 = "<p>" + this.getDigit0Explanation(this.digits[0], this.guessed[0]) + "</p>\n";
        log ("explanation0 = " + explanation0);
      }
      var explanation2 = "";
      var digit2Wrong = this.digits[2] != this.guessed[2];
      if (digit2Wrong) {
        explanation2 = "<p>" + this.getDigit2Explanation(this.digits[2], this.guessed[2]) + "</p>\n";
        log ("explanation2 = " + explanation2);
      }
      var explanation1 = "";
      if (digit0Wrong || digit2Wrong) {
        explanation1 = "<p>Because you got " + 
                (digit0Wrong ? (digit2Wrong ? "both the first and third digits" : "the first digit") : 
                "the third digit") +
                " wrong, there is no point explaining the correct calculation of the second digit.</p>\n";
      }
      else if (this.digits[1] != this.guessed[1]) {
        if (this.digits[2] == 0) {
          explanation1 = "<p>" + this.getDigit1ZeroDigit2Explanation (this.digits[1], this.guessed[1]) + "</p>\n";
        }
        else {
          explanation1 = "<p>" + this.getDigit1Explanation(this.digits[1], this.guessed[1]) + "</p>\n";
        }
        log ("explanation1 = " + explanation1);
      }
      this.explain (explanationRoot + explanation0 + explanation2 + explanation1);
    }
  }, 
  
  firstPartExplanations: [null, "first digit", "first two digits", "first three digits"], 
  
  getDigit0Explanation: function (digit0, guess0) {
    var firstPartLength = this.numCubeDigits - 6;
    var firstPart = Math.floor (this.cube / 1000000);
    var lowerCube = cube (digit0);
    var upperCube = cube (digit0+1);
    return "You guessed " + guess0 + " for the first digit. But you should have determined that it was <b>" + digit0 + 
    "</b> because " + firstPart +
    " (the " + this.firstPartExplanations[firstPartLength] + " of " + this.cubeStringWithCommas + ") is " + 
    (firstPart == lowerCube ? 
            "equal to " + lowerCube + " ( = " + digit0 + "<sup>3</sup>)" :
            "greater than " + lowerCube + " ( = <b>" + digit0 + "</b><sup>3</sup>) and less than " + 
            upperCube + " ( = " + (digit0+1) + "<sup>3</sup>)") + ".";
  }, 
  
  getDigit2Explanation: function (digit2, guess2) {
    var lastDigit = this.cube % 10;
    var cubeLastDigit = "" + cube (lastDigit);
    var cubeLastDigitBoldedAtEnd = cubeLastDigit.substring (0, cubeLastDigit.length-1) + 
    "<b>" + cubeLastDigit.substring (cubeLastDigit.length-1) + "</b>";
    
    return "You guessed " + guess2 + " for the third digit. But you should have determined that it was <b>" + digit2 + 
    "</b> by seeing that the last digit of " + this.cubeStringWithCommas + " is " + lastDigit + 
    ", which is equal to the last digit of " + cube (digit2) + 
    " ( = <b>" + digit2 + "</b><sup>3</sup>). Alternatively, because the cube function is its own inverse mod 10, " +
    "you could have cubed the last digit to determine that the last digit of " +
    "the cube root is " + lastDigit + "<sup>3</sup> = " + 
    cubeLastDigitBoldedAtEnd + " = <b>" + digit2 + "</b> (mod 10).";
  }, 
  
  // assumes first and third digits correct
  getDigit1ZeroDigit2Explanation: function (digit1, guess1) {
    var reducedCubeStringWithCommas = insertCommas ("" + this.cube / 1000);
    return "You guessed " + guess1 + " for the second digit. Because the third digit is zero, this problem " + 
    "is extra easy &ndash; all you need to do is calculate the last digit of the cube root of " + 
    reducedCubeStringWithCommas + ", which is <b>" + digit1 + "</b> because <b>" + digit1 + "</b><sup>3</sup> = " + 
    cube (digit1) + " = " + reducedCubeStringWithCommas.substring (reducedCubeStringWithCommas.length-1) + " (mod 10).";
  }, 
  
  // assumes first and third digits correct
  getDigit1Explanation: function (digit1, guess1) {
    var expression = mod11Expression (this.cubeString);
    var expressionResult = eval (expression);
    var mod11 = this.cube % 11;
    var mod11Root = this.cubeRoot % 11;
    var diff = (mod11 - expressionResult) / 11;
    var start = "You guessed " + guess1 + " for the second digit. But you should have determined that it was <b>" + 
    digit1 + "</b>, as follows:" + "</p>" + 
    "<p><ul><li>" +  this.cubeStringWithCommas + " mod 11 = " + mod11 + 
    ". This can be calculated by alternately adding and subtracting the digits of " + this.cubeStringWithCommas + 
    " starting with the rightmost digit and going left, i.e. " + expression + " = " + expressionResult + 
    (diff != 0 ? (" = " + mod11 + " (mod 11)") : "") + ".</li>";
    var cubeMod11Root = cube (mod11Root);
    var cubeMod11RootMod11Expr = mod11Expression ("" + cubeMod11Root);
    var digit0 = this.digits[0];
    var digit2 = this.digits[2];
    var cubeRootMod11Explanation = "<li>The cube root of " + mod11 + " (mod 11) is " + mod11Root + 
    ",  because " + mod11Root + "<sup>3</sup> = " + cubeMod11Root + " = " + cubeMod11RootMod11Expr + " (mod 11) = " + 
    mod11 + " (mod 11).</li><li>So the cube root that we are looking for must be equal to " + mod11Root + " (mod 11).</li>" + 
    "<li>This implies that " + digit2 + " - <i><b>x</b></i> + " + digit0 + " = " + mod11Root + 
    ", which has the solution " +
    "<i><b>x</b></i> = " + digit2 + " + " + digit0 + " - " + mod11Root + " = " + 
    (digit2 + digit0 - mod11Root) + " = <b>" + 
    digit1 + "</b> (mod 11).</li>";
    return start + cubeRootMod11Explanation + "</ul>";
  }, 
  
  clearGuessAtPos: function (pos) {
    this.guessed[pos] = null;
    this.view.clearGuessAtPos (pos);
  }, 
  
  setGuessAtPos: function (pos, digit) {
    this.guessed[pos] = digit;
    this.view.setGuessAtPos (pos, digit);
  }
};

//================================================================================
function CubeRootPracticeView (document) {
  log ("new CubeRootPracticeView");
  this.cubeSpan = document.getElementById ("cube");
  this.digits = [document.getElementById ("digit1"), document.getElementById ("digit2"),
          document.getElementById ("digit3")];
  for (var i=0; i<3; i++) {
    var digitView = this.digits[i];
    digitView.parentView = this;
    digitView.pos = i;
    digitView.onclick=function() {this.parentView.model.clickOnDigit(this.pos)};
  }
  var view = this;
  document.onkeypress = function(event) {
    if (!event) event = window.event;
    if (event.altKey || event.ctrlKey) {
      return true; 
    }
    else {
      var code = event.which || event.keyCode;
      return view.handleKeyPress (code);
    }
  }
  document.onkeydown = function(event) {
    if (!event) event = window.event;
    if (event.altKey || event.ctrlKey) {
      return true; 
    }
    else {
      var code = event.which || event.keyCode;
      return view.handleKeyDown (code);
    }
  }
  this.explanation = document.getElementById ("explanation");
  this.newNumberButton = document.getElementById ("newNumber");
  this.newNumberButton.onclick = function() {view.model.initCube(); }
}

CubeRootPracticeView.prototype = {
  
  nonfocusBorderColor: "#c0c0c0", 
  focusBorderColor: "#ff0000", 
  
  setCube: function (cubeString) {
    this.cubeSpan.firstChild.nodeValue = cubeString;
  }, 
  
  clearFocus: function() {
    if (this.focus != null) this.clearDigitFocus (this.focus);
    this.focus = null;
  }, 
  
  clearDigitFocus: function (i) {
    this.digits[i].style.borderColor = this.nonfocusBorderColor;
  }, 
  
  setDigitFocus: function (i) {
    this.digits[i].style.borderColor = this.focusBorderColor;
  }, 
  
  setFocusToDigit: function (i) {
    if (i != this.focus) {
      if (this.focus != null) this.clearDigitFocus (this.focus);
    }
    this.setDigitFocus (i);
    this.focus = i;
  }, 
  
  handleKeyDown: function (code) {
    log ("You pressed key " + code);
    if (code == BACKSPACE_KEY || code == DELETE_KEY) {
      this.model.handleDelete();
      return false;
    }
    else if (code == LEFT_ARROW_KEY) {
      this.model.handleLeftArrow();
      return false;
    }
    else if (code == RIGHT_ARROW_KEY) {
      this.model.handleRightArrow();
      return false;
    }
    else {
      return true;
    }
  }, 
  
  handleKeyPress: function (code) {
    log ("You pressed key " + code);
    if (code >= ZERO_KEY && code <= NINE_KEY) {
      this.model.handleDigit (code-ZERO_KEY);
      return false;
    }
    else {
      return false;
    }
  }, 
  
  clearGuessAtPos: function (pos) {
    this.digits[pos].firstChild.nodeValue = nbsp;
  }, 
  
  setGuessAtPos: function (pos, digit) {
    this.digits[pos].firstChild.nodeValue = "" + digit;
  }, 
  
  explain: function (explanation) {
    this.explanation.innerHTML = explanation;
  }, 
  
  clearExplanation: function() {
    this.explanation.innerHTML = "&nbsp;";
  }
 
}

//================================================================================
function setup() {
  log ("setup");
  var cubeRootPracticeView = new CubeRootPracticeView (document);
  var cubeRootPractice = new CubeRootPractice (cubeRootPracticeView);
}


