/****************************************************/
/*  Title      : JavaScript Calculator              */
/*  Course     : CPAN542 Client Server              */
/*  Date       : Oct 5, 2002                        */
/*                                                  */
/*  Programmer : In Chul Koo                        */
/*  E-mail     : ickoo@rogers.com                   */
/*  Web Address: members.rogers.com/ickoo           */
/*               sparcy.humber.org/~knch0013        */
/*                                                  */
/*  Purpose : to calculate complex scientific       */
/*               formulas                           */
/*          : to display the results                */
/****************************************************/
var formExecuted;
var lastResults;

/*****************************/
/*          init             */
/*****************************/
function init(cur)
{ formExecuted = false;
  drgValue = 3;
  lastResults = 0;
  selectDrg(cur, drgValue)
  currMode = "";
  selectMode(cur, "dec");
  sound_mode = "off";
  changeSoundMode(cur);
}

/*****************************/
/*        clearAll           */
/*****************************/
function clearAll(cur)
{ cur.inputWindow.value = '';
  formExecuted = false;
  playSound();
  cur.inputWindow.focus();
}

/*****************************/
/*       inputValue          */
/*****************************/
function inputValue(cur, inVal)
{ if(formExecuted)
  { cur.inputWindow.value = "";
    formExecuted = false;
  }
  if(inVal == "BS")
  { var str = cur.inputWindow.value;
    cur.inputWindow.value = str.substring(0, str.length-1);
  }
  else
  { if(cur.inputWindow.value.length + inVal.length < 72)
      cur.inputWindow.value += inVal;
  }
  playSound();
  cur.inputWindow.focus();
}

/*****************************/
/*       executeForm         */
/*****************************/
function executeForm(cur)
{ playSound();
  cur.inputWindow.focus();

  if(cur.inputWindow.value && !formExecuted)
  { var resultsStr = calculateFormula(cur);
    dispalyResults(cur, resultsStr);
  }
}

/*****************************/
/*     calculateFormula      */
/*****************************/
function calculateFormula(cur)
{ var formulaStr = cur.inputWindow.value;
  var resultsStr;
  
  //validate signs, [--] to [+], [++] to [+]
  formulaStr = validateSigns(formulaStr, "-");
  formulaStr = validateSigns(formulaStr, "+");

  //select mode, [dec] or the others
  if(currMode == "dec")
    resultsStr = calcurateDecimal(formulaStr);
  else
    resultsStr = calcurateNoneDec(formulaStr);
  return resultsStr;
}    

/*****************************/
/*       dispalyResults      */
/*****************************/
function dispalyResults(cur, resultsStr)
{ //dispaly value (align=right)
  var strLen = resultsStr.length;
  for(i=strLen; i<24; i++)
  { var temp = " " + resultsStr;
    resultsStr = temp;
  }
  cur.inputWindow.value += "\n" + resultsStr;
  formExecuted = true;
}

/*****************************/
/*       validateSigns       */
/*****************************/
function validateSigns(formulaStr, sign)
{ // ex. 1--2(3++4)+--5 = 1+2(3+4)+5
  var str = formulaStr;
  for(i=0; i<str.length; i++)
  { if(str.charAt(i) == sign && str.charAt(i+1) == sign)
    { str = str.substring(0, i)
          + "+"
          + str.substring(i+2, str.length);
      i = -1;
    }
  }
  return str;
}

/*****************************/
/*     calcurateNoneDec      */
/*****************************/
function calcurateNoneDec(formulaStr)
{ var resultsStr = "";
  var pos = 0;
  var str = formulaStr;
  var input;
  var formulaStr ="";
  var foundNumber = false;
  var origin = getBaseNumber(currMode);

  // recall Ans (last result)
  str = insertAstskMth(str, 'Ans');
  str = convertOperator(str, 'Ans');

  try
  { var i;
    for(i=0; i < str.length; i++)
    { if(!foundNumber)
      { if((str.charAt(i) >= '0' && str.charAt(i) <= '9' ||
             str.charAt(i) >= 'A' && str.charAt(i) <= 'F' ))
        { pos = i;
          foundNumber = true;
        }
        else
        { formulaStr += str.charAt(i);
        }
      }
      else
      { if(!(str.charAt(i) >= '0' && str.charAt(i) <= '9' ||
             str.charAt(i) >= 'A' && str.charAt(i) <= 'F' ))
        { input = str.substring(pos, i);

          formulaStr += convertNumber(input, origin, 10) + str.charAt(i);
          foundNumber = false;
        }    
      }
      if(i == str.length-1 && foundNumber)
      { input = str.substring(pos, str.length);
        formulaStr += convertNumber(input, origin, 10);
      }    
    }
    // validate division -- ex) 8/2(2)(2) = 1
    formulaStr = convertOperator(formulaStr, '/');
    formulaStr = validateDivision(formulaStr);
    // insert asterisk when needed
    formulaStr = insertAsterisk(formulaStr);

    input = "" + Math.round(eval(formulaStr));
    resultsStr = "" + convertNumber(input, 10, origin);
    lastResults = resultsStr;
  }
  catch(exc)
  { resultsStr = "Syntax Error";
    lastResults = 0;
  }

  return resultsStr;
}

/*****************************/
/*      getBaseNumber        */
/*****************************/
function getBaseNumber(thisMode)
{ var baseNumber;
  switch(thisMode)
  { case 'hex': baseNumber = 16; break;
    case 'dec': baseNumber = 10; break;
    case 'oct': baseNumber =  8; break;
    case 'bin': baseNumber =  2;
  }
  return baseNumber;
}

/*****************************/
/*      convertNumber        */
/*****************************/
function convertNumber(input, origin, dest)
{ var base="0123456789ABCDEF";
  var c=0;
  var result="";
  var sign = "";
  
  if(input.charAt(0) == "-")
  { sign = "-";
    input = input.substring(1, input.length);
  }
  if(input != "0")
  { for(i=1; i<=input.length; i++)
    { var b=base.indexOf(input.substring(i-1,i));
      var n=b*(Math.pow(origin, input.length-i));
      c+=n;
    }
    a=100;
    while (c < Math.pow (dest, a))
    { a--;
    }
    while (a>-1)
    { e=Math.pow (dest, a);
      a--;
      d=((c-(c%e))/e)+1;
      c=c%e;
      ciffer=base.substring(d-1, d);
      result = result + ciffer;
    }
  }
  else
    result = "0";
  result = sign + result;
  return result;
}

/*****************************/
/*     calcurateDecimal      */
/*****************************/
function calcurateDecimal(formulaStr)
{ var resultsStr = "";
  // change asin, acos, atan to asi, aco, ata
  formulaStr = convertOperator(formulaStr, 'asin');
  formulaStr = convertOperator(formulaStr, 'acos');
  formulaStr = convertOperator(formulaStr, 'atan');

  // validate division -- ex) 8/-2(2)(2) = -1
  formulaStr = convertOperator(formulaStr, '/');
  formulaStr = validateDivision(formulaStr);

  // insert asterisk when needed
  formulaStr = insertAsterisk(formulaStr);
  formulaStr = insertAsterisk2(formulaStr);
  // change char [e] to [Math.E]
  formulaStr = convertOperator(formulaStr, 'e');

  // change char [log] to [LOG]: The base 10 logarithm
  formulaStr = convertOperator(formulaStr, 'log');
  // change char [ln] to  [log]: The base  2 logarithm
  formulaStr = convertOperator(formulaStr, 'ln');

  // change char [pi] to [Math.PI]
  formulaStr = convertOperator(formulaStr, 'pi');
  // recall Ans (last result)
  formulaStr = convertOperator(formulaStr, 'Ans');
  // convert & reform [a^b] to [Math.pow(a,b)]
  formulaStr = convertPower(formulaStr);
  formulaStr = convertMath2(formulaStr, 'pwr');

  // reform and eval [sin(a)] to [Math.sin(a)] ( cos, tan, asin, log, etc... )
  formulaStr = evaluateValues(formulaStr);

  // evaluate formula String
  try
  { resultsStr = "" + roundDbl( eval(formulaStr) );
    lastResults = resultsStr;
  }
  catch(exc)
  { resultsStr = "Syntax Error";
    lastResults = 0;
  }
  return resultsStr;
}

/*****************************/
/*     validateDivision      */
/*****************************/
function validateDivision(formulaStr)
{ // ex. 8/-2(2)(2) = 8/(-2(2)(2))
  //     2/(2)pi*cos(30) = 2/(2*pi)*cos(30)
  //     2/2picos(30)*2 = 2/(2*pi*cos(30))*2
  var mthName = 'div';
  while(formulaStr.match(mthName))
  { var tmpStr_val = "";
    var tmpStr_end = "";
    var charNum;
    var str = formulaStr;
    var pos = str.indexOf(mthName);
    formulaStr = str.substring(0, pos) + '/(';
    var startHere=0;
    if(str.charAt(pos+mthName.length) == '+' || str.charAt(pos+mthName.length) == '-')
    { //formulaStr += str.charAt(pos+mthName.length);
      startHere=1;
    }
    var bracket_num = 0;
    var found = false;
    for(i=pos+mthName.length+startHere; i < str.length && !found; i++ )
    { charNum  = (str.charAt(i)).charCodeAt();
      charNum2 = (str.charAt(i+1)).charCodeAt();
      if(charNum==40)
        bracket_num++;
      else if(charNum==41)
        bracket_num--;
    
      if(i == str.length-1)
      { if( charNum == 41 && bracket_num != 0 )
        { tmpStr_val = str.substring(pos+mthName.length, str.length-1);
          tmpStr_end = str.charAt(str.length-1);
        }
        else
        { tmpStr_val = str.substring(pos+mthName.length, str.length);
        }
      }
      else if( bracket_num < 0 || ( bracket_num == 0 && checkOperator(str.charAt(i+1))))
      { tmpStr_val = str.substring(pos+mthName.length, i+1);
        tmpStr_end = str.substring(i+1, str.length);
        found = true;
      }
    }
    formulaStr = formulaStr + tmpStr_val + ')' + tmpStr_end;
  }
  return formulaStr;
}

/*****************************/
/*      insertAsterisk       */
/*****************************/
function insertAsterisk(str)
{ var tmpStr = "";
  // insert '*' in front of '(' when needed
  for(i=0; i< str.length; i++ )
  { var charNum = (str.charAt(i)).charCodeAt();
    var preChar = (str.charAt(i-1)).charCodeAt();
    if( i>0 && charNum==40 &&
        ( preChar == 41  || checkNumber(str.charAt(i-1)) ||
          preChar == 101 || str.substr(i-2,2) == 'pi' ))
    { tmpStr += '*(';
    }
    else
      tmpStr += str.charAt(i);
  }
  return tmpStr;
}

/*****************************/
/*     insertAsterisk 2      */
/*****************************/
function insertAsterisk2(formulaStr)
{ var tmpStr = formulaStr;
  tmpStr = insertAstskMth(tmpStr, 'e');
  tmpStr = insertAstskMth(tmpStr, 'ln');
  tmpStr = insertAstskMth(tmpStr, 'log');
  tmpStr = insertAstskMth(tmpStr, 'sqrt');
  tmpStr = insertAstskMth(tmpStr, 'sin');
  tmpStr = insertAstskMth(tmpStr, 'cos');
  tmpStr = insertAstskMth(tmpStr, 'tan');
  tmpStr = insertAstskMth(tmpStr, 'asi');
  tmpStr = insertAstskMth(tmpStr, 'aco');
  tmpStr = insertAstskMth(tmpStr, 'ata');
  tmpStr = insertAstskMth(tmpStr, 'pi');
  tmpStr = insertAstskMth(tmpStr, 'Ans');
  return tmpStr;
}

function insertAstskMth(tmpStr, mthName)
{
  while(tmpStr.match(mthName))
  { var str = tmpStr;
    var pos = str.indexOf(mthName);
    tmpStr = str.substring(0, pos);
    var charNum = (str.charAt(pos-1)).charCodeAt();
    if( pos > 0 && !checkOperator(str.charAt(pos-1)) && charNum != 40 && charNum != 94)
      tmpStr += '*';
    tmpStr += 'TEMP' + (str.substring(pos+mthName.length, str.length));
  }

  while(tmpStr.match('TEMP'))
  { var str = tmpStr;
    var pos = str.indexOf('TEMP');
    tmpStr = str.substring(0, pos);
    tmpStr += mthName + (str.substring(pos+4, str.length));
  }
  return tmpStr;
}

/*****************************/
/*      checkOperator        */
/*****************************/
function checkOperator(charVal)
{ switch(charVal)
  { case '+':
    case '-':
    case '*':
    case '/': return true; break;
    default : return false;
  }
}

/*****************************/
/*       checkNumber         */
/*****************************/
function checkNumber(ch)
{ if( ( ch >= '0' && ch <= '9' ) || ch == '.' )
    return true;
  else
    return false;
}

/*****************************/
/*      convertOperator      */
/*****************************/
function convertOperator(formulaStr, charVal)
{ while(formulaStr.match(charVal))
  { var str = formulaStr;
    var pos = str.indexOf(charVal);               
    var charNum;

    var formulaStr = str.substring(0, str.indexOf(charVal));
    switch(charVal)
    { case 'e': charNum = (str.charAt(pos+1)).charCodeAt();
                if( !checkOperator(str.charAt(pos+1)) && ( pos+1 < str.length) && charNum != 40 && charNum != 41 && charNum != 94)
                  formulaStr = 'ERR';
                else
                  formulaStr += Math.E;
                break;
      case 'log': formulaStr += 'LOG';
                break;
      case 'ln': formulaStr += 'log';
                break;
      case 'pi': charNum = (str.charAt(pos+2)).charCodeAt();
                if( !checkOperator(str.charAt(pos+2)) && ( pos+2 < str.length) && charNum != 40 && charNum != 41 && charNum != 94)
                  formulaStr = 'ERR';
                else
                  formulaStr += Math.PI;
                break;
      case 'Ans': formulaStr += lastResults;
                break;
      case 'asin': formulaStr += 'asi';
                break;
      case 'acos': formulaStr += 'aco';
                break;
      case 'atan': formulaStr += 'ata';
                break;
      case '/': formulaStr += 'div';
                break;
      default :
    }

    formulaStr += str.substring(str.indexOf(charVal)+charVal.length, str.length);
  }
  return formulaStr;
}

/*****************************/
/*      convertPower         */
/*****************************/
function convertPower(formulaStr)
{ // convert [^] to [pwr] temporary
  var str = formulaStr;
  var formulaStr = "";
  for(i=0; i<str.length; i++)
  { if(str.charAt(i) == '^')
      formulaStr += 'pwr';
    else
      formulaStr += str.charAt(i);
  }
  return formulaStr;
}

/*****************************/
/*       evaluateValues      */
/*****************************/
function evaluateValues(formulaStr)
{ // evaluate sin(),cos(),tan()
  formulaStr = convertMath(formulaStr, 'LOG');
  formulaStr = convertMath(formulaStr, 'log');

  formulaStr = convertMath(formulaStr, 'sqrt');
  formulaStr = convertMath(formulaStr, 'sin');
  formulaStr = convertMath(formulaStr, 'cos');
  formulaStr = convertMath(formulaStr, 'tan');
  formulaStr = convertMath(formulaStr, 'asi');
  formulaStr = convertMath(formulaStr, 'aco');
  formulaStr = convertMath(formulaStr, 'ata');
  
  return formulaStr;
}

/*****************************/
/*       convertMath        */
/*****************************/
function convertMath(formulaStr, mthName)
{ while(formulaStr.match(mthName) && formulaStr.substr(formulaStr.indexOf(mthName)-5, 5)!='Math.' )
  { var left_bracketNum = 0;

    var tmpStr_1 = "";
    var tmpStr_2 = "";
    var tmpStr_3 = "";
    var charNum;
    
    var str = formulaStr;
    var pos = str.indexOf(mthName);
    formulaStr = str.substring(0, pos);

    if(mthName == 'asi' || mthName == 'ata' )
      tmpStr_1 += 'Math.' + mthName + 'n(';
    else if(mthName == 'aco')
      tmpStr_1 += 'Math.' + mthName + 's(';
    else if(mthName == 'LOG')
      tmpStr_1 += 'Math.' + 'log' + '(Math.pow(';
    else
      tmpStr_1 += 'Math.' + mthName + '(';

    charNum = (str.charAt(pos+mthName.length)).charCodeAt();
    if(charNum!=40)
    { formulaStr = 'ERR'; break;
    }
    else
    { left_bracketNum++;
      tmpStr_2 += str.charAt(pos+mthName.length);

      for(i=pos+mthName.length+1; i< str.length && left_bracketNum > 0; i++ )
      { charNum = (str.charAt(i)).charCodeAt();
        if(charNum==40)
          left_bracketNum++;
        else if(charNum==41)
          left_bracketNum--;

        tmpStr_2 += str.charAt(i);
        pos = i+1;
      }
      // select drg
      if(mthName == 'sin' || mthName == 'cos' || mthName == 'tan' )
      { switch(drgValue)
        { case 1: tmpStr_3 += '* Math.PI/180)';
                  break;
          case 2: tmpStr_3 += ')';
                  break;
          case 3: tmpStr_3 += '* Math.PI/200)';
                  break;
          default:
        }
      }
      else if(mthName == 'asi' || mthName == 'aco' || mthName == 'ata' )
      { switch(drgValue)
        { case 1: tmpStr_3 += ')* 180/Math.PI';
                  break;
          case 2: tmpStr_3 += ')';
                  break;
          case 3: tmpStr_3 += ')* 200/Math.PI';
                  break;
          default:
        }
      }
      else if(mthName == 'LOG')
        tmpStr_3 += ',Math.LOG10E))';
      else
        tmpStr_3 += ')';
      try
      { var tmpStr;
        tmpStr_2 = evaluateValues(tmpStr_2);
        tmpStr = eval(tmpStr_1+tmpStr_2+tmpStr_3);
        formulaStr += tmpStr + str.substring(pos, str.length);
      }
      catch(exc)
      { formulaStr = "ERR";
      }
    }
  }
  return formulaStr;
}

/***************************/
/*      convertMath2       */
/***************************/
function convertMath2(formulaStr, mthName)
{ // ex. tan(2+3)^cos(45+2)sin(45) = tan(Math.pow((2+3),cos(45+2)))*sin(45)
  while(formulaStr.match(mthName))
  { var bracket_num  = 0;          
    var tmpStr_srt = "";
    var tmpStr_va1 = "";
    var tmpStr_va2 = "";
    var tmpStr_end = "";
    var charNum;                 
    var str = formulaStr;
    var pos = str.indexOf(mthName);
    var found = false;
    // find left-side values
    for(i=pos-1; i >= 0 && !found; i--)
    { charNum = (str.charAt(i)).charCodeAt();
      if(charNum==41)
        bracket_num++;
      else if(charNum==40)
        bracket_num--;

      if(i == 0)
      { if(checkNumber(str.charAt(0)) || ( charNum == 40 && bracket_num == 0) )
        { tmpStr_va1 = str.substring(0, pos);
        }
        else
        { tmpStr_srt = str.charAt(0);
          tmpStr_va1 = str.substring(1, pos);
        }
      }
      else if( bracket_num == 0 && charNum == 40 )
      { tmpStr_srt = str.substring(0, i);
        tmpStr_va1 = str.substring(i, pos);
        found = true;
      }
      else if( bracket_num <= 0 && !checkNumber(str.charAt(i)) )
      { tmpStr_srt = str.substring(0, i+1);
        tmpStr_va1 = str.substring(i+1, pos);
        found = true;
      }
    }

    // find any following [math](sin, cos, etc) after [^]
    bracket_num = 0;
    found = false;
    var mathLen=0;
    if( (str.charAt(pos+mthName.length) >= "a" && str.charAt(pos+mthName.length) <="z") ||
        (str.charAt(pos+mthName.length) >= "A" && str.charAt(pos+mthName.length) <="Z") )
    { for(i=pos+mthName.length; i < str.length && !found; i++ )
      { if( (str.charAt(i)).charCodeAt() == 40 )
          found = true;
        else
          mathLen++;
      }
    }
    // find right-side values
    found = false;
    for(i=pos+mthName.length + mathLen; i < str.length && !found; i++ )
    { charNum = (str.charAt(i)).charCodeAt();
      
      if(charNum==40)
        bracket_num++;
      else if(charNum==41)
        bracket_num--;

      if(i == str.length-1)
      { if(checkNumber(str.charAt(i)) || ( charNum == 41 && bracket_num == 0) )
        { tmpStr_va2 = str.substring(pos+mthName.length, str.length);
        }
        else
        { tmpStr_va2 = str.substring(pos+mthName.length, str.length-1);
          tmpStr_end = str.charAt(str.length-1);
        }
      }
      else if( bracket_num == 0 && charNum == 41 )
      { tmpStr_va2 = str.substring(pos+mthName.length, i+1);
        tmpStr_end = str.substring(i+1, str.length);
        found = true;
      }
      else if( bracket_num <= 0 && !checkNumber(str.charAt(i)) )
      { tmpStr_va2 = str.substring(pos+mthName.length, i);
        tmpStr_end = str.substring(i, str.length);
        found = true;
      }
    }
    formulaStr = tmpStr_srt + "(Math.pow(" + tmpStr_va1 + "," + tmpStr_va2 + "))" + tmpStr_end;
  }
  return formulaStr;
}

/*****************************/
/*         roundDbl          */
/*****************************/
function roundDbl(anynum)
{ anynum=eval(anynum);
  retval=(Math.round(anynum*1000000000000)/1000000000000);
  return retval;
}
