/******************************************************************************
 * G L O B A L S **************************************************************
 *****************************************************************************/

/* we define a type for our calculator, numbers 1-200 are reserved for
 * basic vegUI widget so in order to avoid compatibility problems we chose
 * a number above that
 */

var VUI_CALCULATOR = 345;

/* in order for the manager to be able to create our calculator later on
 * it needs to be added to the VEGUIOBJ, we do this by calling the
 * vui_module_add function
 */

vui_module_add(VUI_CALCULATOR, VegUICalculator, 'calculator.class.js');

/******************************************************************************
 * C A L C U L A T O R ********************************************************
 *****************************************************************************/

function VegUICalculator(refName, Parent, Manager) {

  /* our constructor is the VegUIWindow element, doing it this way our
   * calculator will inherit all the properties, methods and child
   * element of the VegUIWindow
   */

  /*
   * constructor
   */

  this.constructor = VegUIWindow;
  this.constructor(refName, Parent, Manager);

  /*
   * properties
   */

  /* the type property holds the object type of our object,
   * in our case VUI_CALCULATOR
   */
  
  this.type = VUI_CALCULATOR;
  
  /* more object specific properties */
  
  this.number = 0;
  this.result = 0;
  this.mod = '';
  this.isFloat = false;

  /*
   * child elements
   */

  /* buttons for the digits */

  this.Btn0 = this.Ui.add_child('Btn0', VUI_BUTTON);
  this.Btn1 = this.Ui.add_child('Btn1', VUI_BUTTON);
  this.Btn2 = this.Ui.add_child('Btn2', VUI_BUTTON);
  this.Btn3 = this.Ui.add_child('Btn3', VUI_BUTTON);
  this.Btn4 = this.Ui.add_child('Btn4', VUI_BUTTON);
  this.Btn5 = this.Ui.add_child('Btn5', VUI_BUTTON);
  this.Btn6 = this.Ui.add_child('Btn6', VUI_BUTTON);
  this.Btn7 = this.Ui.add_child('Btn7', VUI_BUTTON);
  this.Btn8 = this.Ui.add_child('Btn8', VUI_BUTTON);
  this.Btn9 = this.Ui.add_child('Btn9', VUI_BUTTON);

  /* buttons for math operations */

  this.BtnAdd = this.Ui.add_child('BtnAdd', VUI_BUTTON);
  this.BtnSubtract = this.Ui.add_child('BtnSubtract', VUI_BUTTON);
  this.BtnDivide = this.Ui.add_child('BtnDivide', VUI_BUTTON);
  this.BtnMultiply = this.Ui.add_child('BtnMultiply', VUI_BUTTON);
  this.BtnEquals = this.Ui.add_child('BtnEquals', VUI_BUTTON);
  this.BtnInvert = this.Ui.add_child('BtnInvert', VUI_BUTTON);
  this.BtnComma = this.Ui.add_child('BtnComma', VUI_BUTTON);
  this.BtnPI = this.Ui.add_child('BtnPI', VUI_BUTTON);

  /* buttons for calculator operations */

  this.BtnClear = this.Ui.add_child('BtnClear', VUI_BUTTON);
  this.BtnErase = this.Ui.add_child('BtnErase', VUI_BUTTON);

  /* number label */

  this.LblNumber = this.Ui.add_child('LblNumber', VUI_NODE);

  /*
   * methods
   */

  /***************************************************************************/
  /** Method: build_calculator */

  this.build_calculator = this.build = function(toNode) {
    
    /* we want to make sure the calculator is not resizable, so we set
     * those properties right before the element is built so it will
     * overwrite the property if it was set outside. This should be done
     * only if you are sure that you want to prohibit an object from
     * doing something and it goes against the flexibility concept of
     * vegUI 
     */

    this.flags |= VUI_NORESIZE | VUI_NOMAXIMIZE;
    
    /* convert template properties to real properties, this needs
     * to happen before the dock() method is called, preferably
     * even before the element is built
     */

    this.maxLength = this.T.maxLength || 15;
    
    /* build the calculator, we extended the calculator from the
     * VegUIWindow element so we will make sure to call it's build
     * function as well (build_win). Would we have extended the
     * calculator from VegUINode instead we would be calling
     * the build_node function
     */
    
    if(!this.build_win())
      return null;
    
    /* were defining C to point to this, this is so we can use it as
     * a reference to the calculator object within the functions
     * that we set for the buttons
     */
     
    var C = this;

    /* setting the button mouse states */
 
    /*
     * digit buttons
     */

    this.Btn0.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.put_digit('0'); }
    );
   
    this.Btn1.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.put_digit('1'); }
    );

    this.Btn2.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.put_digit('2'); }
    );

    this.Btn3.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.put_digit('3'); }
    );

    this.Btn4.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.put_digit('4'); }
    );

    this.Btn5.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.put_digit('5'); }
    );

    this.Btn6.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.put_digit('6'); }
    );

    this.Btn7.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.put_digit('7'); }
    );

    this.Btn8.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.put_digit('8'); }
    );

    this.Btn9.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.put_digit('9'); }
    );

    /*
     * buttons for calculator operations
     */

    this.BtnClear.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.clear_number(); }
    );

    this.BtnErase.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.erase_digit(); }
    );

    this.BtnAdd.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.set_mod('+'); }
    );

    this.BtnSubtract.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.set_mod('-'); }
    );

    this.BtnDivide.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.set_mod('/'); }
    );

    this.BtnMultiply.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.set_mod('*'); }
    );
   
    this.BtnEquals.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.set_mod(); }
    );
    
    this.BtnInvert.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.invert_number(); }
    );

    this.BtnComma.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.put_comma(); }
    );

    this.BtnPI.States[VUI_MOUSE_DOWN].Scripts.add(
      function() { C.put_number(new String(Math.PI).substr(0,10)); }
    );
    
    /* add a function to the key down state of the calculator
     * that translates certain key presses to calculator operations
     */

    this.States[VUI_KEY_DOWN].Scripts.add(
      function() { C.keypress(); }
    );

    /* the dock function appends the built HTML node to the specified
     * node, if no node is specified then it wont be appended
     */

    this.dock(toNode);
    return 1;
  };
  
  /***************************************************************************/
  /** Method: set_calculator 
    * Calculator specific set method
    *
    * The maxLength argument defines how long the number held by the number
    * label can be before "ERROR" is displayed. 
    */

  this.set_calculator = this.set = function(w,h,x,y,maxLength) {
    this.set_win('Calculator',w,h,x,y);
    if(!isNaN(maxLength))
      this.T.maxLength = maxLength;
  };

  /***************************************************************************/
  /** Method: update_number
    * updates the number label to hold the number stored in this.number unless
    * n is submitted then the label is updated to hold the number in n
    */

  this.update_number = function(n) {

    if(!parseFloat(n))
      var n = this.number;
    
    /* if the amount of digits in the number is greater than max length
     * output error instead of the actual number
     *
     * if the number is a float number then try to lower the precision
     * first if the number is still too long even after the precision cut
     * display "ERROR"
     */

    var strN = new String(n);
 
    if(strN.length > this.maxLength) {
      var c = Math.pow(10, (strN.length - (new String(Math.floor(n)).length+1)) - (strN.length - this.maxLength));
      var n = Math.round(n*c) / c;
      if(new String(n).length > this.maxLength)
        return this.LblNumber.clear(document.createTextNode('ERROR'));
    }
  
    /* output the number if everything is fine */

    this.LblNumber.clear(document.createTextNode(n));
  };

  /***************************************************************************/
  /** Method: put_number
    * sets the number label to the specified number
    */

  this.put_number = function(n) {
    this.number = parseFloat(n);
    this.update_number();
  };

  /***************************************************************************/
  /** Method: put_digit
    * appends a digit to the number being held by the number label
    */

  this.put_digit = function(c) {
   
    /* we append the new digit to the number by making both the digit and
     * the current number a string then appending the digit string
     * to the number string and finally converting everything back to
     * int
     */
    
    this.number = parseFloat(new String(this.number) + (this.isFloat?'.':'') + new String(c));
    this.update_number();
    this.isFloat = false;
  };

  /***************************************************************************/
  /** Method: clear_number
    * resets the number and clears the number label
    */

  this.clear_number = function() {
    this.number = 0;
    this.result = 0;
    this.update_number();
  };

  /***************************************************************************/
  /** Method: set_mod
    * set math mode to add, subtract, multiply or divide
    */

  this.set_mod = function(m) {
    
    /* if the number stored in result is 0 or null, then this is a new
     * calculation simply copy the current number over
     */
  
    if(!this.result)
      this.result = this.number;
    else {
      
      /* this is an ongoing calculation, do the math and then update the
       * label to hold the newly calculated number (stored in result)
       */
    
      switch(this.mod) {
        case '+': 
	  this.result += this.number;
	break;
	case '-':
	  this.result -= this.number;
	break;
	case '*':
	  this.result *= this.number;
	break;
	case '/':
	  this.result /= this.number;
	break;
      }
      this.update_number(this.result);
    }

    /* set this.number to 0 to restart input but do not update the label
     * as we want to display the last entered number until input begins
     * also set this.mod to the specified mod
     */
    
    this.number = 0;
    this.mod = m;
    
  };

  /***************************************************************************/
  /** Method: invert_number
    * Invert the number that is currently stored this.number
    */

  this.invert_number = function() {
    this.number = this.number ^ -2;
    this.update_number();
  };

  /***************************************************************************/
  /** Method: put_comma
    * makes this.number a float number
    */

  this.put_comma = function() {
    this.isFloat = true;
  };
  
  /***************************************************************************/
  /** Method: erase_digit
    * removes the last digit
    */

  this.erase_digit = function() {
    
    /* in order to remove the last digit we need to convert the current number
     * to a string and then remove the last character of that string before
     * we convert it back to a number
     */
    
    var nStr = new String(this.number);
    
    if(nStr.length > 1)
      this.number = parseFloat(nStr.substr(0,nStr.length-1));
    else
      this.number = 0;

    this.update_number();
  };

  /***************************************************************************/
  /** Method: keypress()
    * translate certain numpad keys to actions on the calculator
    */

  this.keypress = function() {
    switch(this.aKey) {
      case 48:
      case 96: this.put_digit('0'); break;
      case 49:
      case 97: this.put_digit(1); break;
      case 50:
      case 98: this.put_digit(2); break;
      case 51:
      case 99: this.put_digit(3); break;
      case 52:
      case 100: this.put_digit(4); break;
      case 53:
      case 101: this.put_digit(5); break;
      case 54:
      case 102: this.put_digit(6); break;
      case 55:
      case 103: this.put_digit(7); break;
      case 56:
      case 104: this.put_digit(8); break;
      case 57:
      case 105: this.put_digit(9); break;
      case 111: this.set_mod('/'); break;
      case 106: this.set_mod('*'); break;
      case 109: this.set_mod('-'); break;
      case 107: this.set_mod('+'); break;
    }
  };

}
VegUICalculator.prototype = VegUIWindow;
