//
// www.calcoolate.com javaScript calculator
//
// Copyright (c) 2006. All rights reserved
//

var setExpression = ""
var letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"

var variables = []

function addError(errors, message, start, length)
{
	var error = new Object()
	error.message = message
	error.start = start
	error.length = length
	errors[errors.length] = error
}
function cloneToken(token)
{
	var clone = new Object()
	clone.type  = token.type
	clone.start = token.start
	clone.text  = token.text
	return clone
}
function tokenize(exp)
{
	var res = new Object()
	var tokens = new Array()
	var errors = new Array()
	res.tokens = tokens
	res.errors = errors

	var currToken
	for (var i = 0; i<exp.length; i++)
	{
		var c = exp.charAt(i)
		if (" \t".indexOf(c) >= 0)
		{
			if (currToken)
			{
				tokens[tokens.length] = currToken
				currToken = null
			}
		}
		else
		if ("*/!%^()[]{}".indexOf(c) >= 0)
		{
			if (currToken)
				tokens[tokens.length] = currToken
			currToken = new Object()
			if ("*/".indexOf(c) >= 0)
				currToken.type = 'op2'
			else
			if ("!%".indexOf(c) >= 0)
				currToken.type = c
			else
			if ("^".indexOf(c) >= 0)
				currToken.type = 'op1'
			else
			if ("([{".indexOf(c) >= 0)
				currToken.type = '('
			else
			if (")]}".indexOf(c) >= 0)
				currToken.type = ')'
			currToken.start=i
			currToken.text = c
			tokens[tokens.length] = currToken
			currToken = null
		}
		else
		if (currToken && currToken.type == 'number')
		{
			if ("0123456789".indexOf(c) >= 0)
				currToken.text += c
			else
			if (c == '.' || c == ',')
			{
				if (currToken.text.indexOf('.') >= 0)
				{
					addError(errors, 'More than one decimal point in a number', i, 1)
					c = '0' // Prevent more errors
				}
				else
				if (currToken.text.indexOf('e') >= 0 || currToken.text.indexOf('x') >= 0 || currToken.text.indexOf('b') >= 0)
				{
					addError(errors, 'Invalid position of decimal point', i, 1)
					c = '0'
				}
				if (c == '.')
					prefs.dot = "dot"
				else
					prefs.dot = "comma"
				currToken.text += '.'
			}
			else
			if (c == 'x' || c == 'X')
			{
				if (currToken.text != '0')
				{
					addError(errors, 'Invalid number. For hexadecimal constants use 0x1f', i, 1)
					c = '0'
				}
				else c = 'x'
				currToken.text += c
			}
			else
			if ((c == 'b' || c == 'B') && !(currToken.text.length >=2 && currToken.text.charAt(0)=='0' && currToken.text.charAt(1)=='x'))
			{
				if (currToken.text != '0')
				{
					addError(errors, 'Invalid number. For binary constants use 0b0010', i, 1)
					c = '0'
				}
				else c = 'b'
				currToken.text += c
			}
			else
			if ("abcdefABCDEF".indexOf(c) >= 0 && currToken.text.length>=2 && currToken.text.charAt(0) == '0' && currToken.text.charAt(1) == 'x')
			{
				currToken.text += c.toLowerCase();
			}
			else
			if (c == 'e' || c == 'E')
			{
				if (currToken.text.indexOf('x') >= 0 || currToken.text.indexOf('e') >= 0)
				{
					addError(errors, 'Invalid number. For exponents use 12e4, 12e+4 or 12e-4', i, 1)
					c = '0'
				}
				else c = 'e'
				currToken.text += c
			}
			else
			if ("+-".indexOf(c) >= 0)
			{
				if (currToken.text.charAt(currToken.text.length-1) != 'e')
				{
					tokens[tokens.length] = currToken
					currToken = new Object()
					currToken.type = 'op3'
					currToken.start=i
					currToken.text = c
					tokens[tokens.length] = currToken
					currToken = null
				}
				else currToken.text += c;
			}
			else
			{
				tokens[tokens.length] = currToken
				currToken = null
				addError(errors, 'Invalid character within a number. valid numbers are in the form 123, 123.45, 12e3, 0x1f, 0b1101', i, 1)
			}
		}
		else
		if (currToken && currToken.type == 'name')
		{
			if ("0123456789.".indexOf(c) >= 0 || letters.indexOf(c) >= 0)
				currToken.text += c;
			else
			if ("+-".indexOf(c) >= 0) // Special case as these can be in a number
			{
				tokens[tokens.length] = currToken
				currToken = new Object()
				currToken.type = 'op3'
				currToken.start=i
				currToken.text = c
				tokens[tokens.length] = currToken
				currToken = null
			}
			else
			{
				tokens[tokens.length] = currToken
				currToken = null
				addError(errors, 'Invalid character. Function names can have only English letters or underscoresr', i, 1)
			}
		}
		else
		if ("+-".indexOf(c) >= 0) // Special case as these can be in a number
		{
			if (currToken)
				tokens[tokens.length] = currToken
			currToken = new Object()
			currToken.type = 'op3'
			currToken.start=i
			currToken.text = c
			tokens[tokens.length] = currToken
			currToken = null
		}
		else
		if (!currToken)
		{
			currToken = new Object()
			currToken.text = c
			currToken.start = i
			if ("0123456789.,".indexOf(c) >= 0)
				currToken.type = 'number'
			else
			if (letters.indexOf(c) >= 0)
				currToken.type = 'name'
			else
			{
				addError(errors, 'This character is not supported', i, 1)
				currToken = null
			}
		}
	}
	// Handle last pending token
	if (currToken)
		tokens[tokens.length] = currToken

	return res
}

function parseAndEvaluate(tokens, errors)
{
	var root = applyAllRules(tokens, errors)
	if (errors.length == 0)
	{
		root.value = fixRoundingError(root.value)
		if (root.displayHex)
			root.value=hex(root.value)
		if (root.displayBin)
			root.value=bin(root.value)
	}
	return root
}

function onkey(e)
{
	var k = e.keyCode
	var passEvent = true
	var evaluate = false
	if (((k == 187 || k == 61) && !e.shiftKey) || k == 13)
	{
		e.returnValue = false
		e.cancelBubble = true
		passEvent = false
		evaluate = true
		if (k != 13)
		{
			// Ignore mixup of = and +
			var v = document.getElementById('exp').value
			if (v.length > 1 && (v.charAt(v.length-1) == '+' || (v.charAt(v.length-2) == '+' && v.charAt(v.length-1) == '=')))
				evaluate = false
		}
	}
	expset(evaluate)
	return passEvent
}
function expFocus()
{
	document.getElementById('exp').focus()
}
function setexp(expression, evaluate)
{
	document.getElementById('exp').value = expression
	expset(evaluate)
}
function expset(evaluate)
{
	var expression = document.getElementById('exp').value
	while (expression.length > 0 && expression.indexOf('=') >= 0)
	{
		var ie = expression.indexOf('=')
		expression = expression.substring(0, ie) + expression.substring(ie+1)
		document.getElementById('exp').value = expression
	}
	if (expression != setExpression || evaluate)
	{
		var currWidth = parseInt(document.getElementById('exp').style.width, 10)
		var reqWidth = (expression.length+2) * charWidth()
		if (reqWidth > currWidth)
			document.getElementById('exp').style.width = reqWidth+"px"

		var te = tokenize(expression)
		var errors = te.errors
		if (evaluate && errors.length == 0)
		{
			var result = parseAndEvaluate(te.tokens, errors)
			if (errors.length == 0)
			{
				var v = result.value
				if (prefs.dot == "comma")
				{
					v = ""+v
					var idp = v.indexOf('.')
					if (idp >= 0)
						v = v.substring(0,idp)+','+v.substring(idp+1)
				}
				document.getElementById('exp').value = v
				document.getElementById('exp').style.width = "296px" // Restore
				logAdd(expression, v, null)
				return expset(false)
			}
		}
		showErrors(expression, errors, evaluate)
		showNesting(expression)
		setExpression = expression
	}
}


var rules = new Array(
	new Array('NumberToExp', new Array('number') ),

	new Array('CallNoParm', new Array( 'name', '(' , ')' ) ),
	new Array('CallStart', new Array( 'name', '(' ) ),
	//new Array('CallParm', new Array( 'call', 'exp', ',' ) ),
	new Array('CallLast', new Array( 'call', 'exp', ')' ) ),
	new Array('NameToExp', new Array( 'name' ) ),

	new Array('Op', new Array( 'exp', 'op1', 'exp') ),

	new Array('Unari1', new Array( 'start', 'op3', 'exp' ) ),
	new Array('Unari1', new Array( '(', 'op3', 'exp' ) ),
	new Array('Unari1', new Array( 'call', 'op3', 'exp' ) ),
	new Array('Unari1', new Array( 'op1' , 'op3', 'exp' ) ),
	new Array('Unari1', new Array( 'op2' , 'op3', 'exp' ) ),
	new Array('Unari1', new Array( 'op3' , 'op3', 'exp' ) ),

	new Array('BrExpBr', new Array( '(', 'exp', ')' ) ),
	new Array('BrExpOpExpBr', new Array( '(', 'exp', 'op1', 'exp', ')' ) ),
	new Array('BrExpOpExpBr', new Array( '(', 'exp', 'op2', 'exp', ')' ) ),
	new Array('BrExpOpExpBr', new Array( '(', 'exp', 'op3', 'exp', ')' ) ),
	new Array('Pct', new Array( 'exp', '%') ),
	new Array('Fact', new Array( 'exp', '!') ),
	new Array('Op', new Array( 'exp', 'op2', 'exp') ),
	new Array('Op', new Array( 'exp', 'op3', 'exp') ),

	new Array('last', new Array('?') )
)
function applyAllRules(tokens, errors)
{
	// Add start token
	var startToken = new Object()
	startToken.type='start'
	var t = new Array()
	t[0] = startToken
	append(tokens, t, 0, tokens.length-1)
	tokens=t

	var hasMatchingRule = true
	while (hasMatchingRule && errors.length == 0)
	{
		var foundMatchingRule = false
		for (var r in rules)
		{
			var rule = rules[r]
			var handler = rule[0]
			var types = rule[1]
			var matchedPos = -1
			for (var i=0; matchedPos < 0 && i<=tokens.length - types.length; i++)
			{
				var matched = true
				for (var j = 0; matched && j < types.length; j++)
					matched = (types[j] == tokens[i+j].type)
				if (matched)
				{
					matchedPos = i
					tokens = eval("rule"+handler+"(tokens, i, errors)")
				}
			}
			if (matchedPos >= 0)
			{
				foundMatchingRule = true
				break
			}
		}
		hasMatchingRule = foundMatchingRule
	}
	if (errors.length == 0 && tokens.length != 2)
		analyzeFinalSyntaxErrors(tokens, errors)
	return tokens[1]
}



// Find best matching error message
function analyzeFinalSyntaxErrors(tokens, errors)
{
	var token
	var message
	for (var i=0; i<tokens.length-1; i++)
	{
		if (tokens[i].type == 'exp' && tokens[i+1].type == 'exp')
		{
			token = tokens[i+1]
			message = "Expecting an operation before value"
			break
		}
		if (tokens[i].type.indexOf('op')==0 && tokens[i+1].type.indexOf('op') == 0)
		{
			token = tokens[i+1]
			message = "Wrong position of operator"
			if (tokens[i].text=='*' && tokens[i+1].text=='*')
				message +=". To computer power, use 2^3"
			break
		}
	}

	// Check parenthesis nesting
	if (!token)
	{
		var brstack = new Array()
		var brpos = -1
		for (var i=0; !token && i<tokens.length; i++)
		{
			if (tokens[i].type == '(' || tokens[i].type == 'call')
				brstack[++brpos] = i
			else
			if (tokens[i].type == ')')
			{
				if (--brpos)
				{
					token = tokens[i]
					message = "Close parenthesis does not have a matching open"
				}
			}
		}
		if (!token && brpos >= 0)
		{
			token = tokens[brstack[brpos]]
			if (token.type == '(')
				message = "Missing close of parenthesis"
			else
				message = "Missing close of function call"
		}
	}

	// Check if ends with an op
	if (!token && tokens[tokens.length-1].type.indexOf('op')==0)
	{
		token = tokens[tokens.length-1]
		message = "Operator is not match with operands"
	}

	if (!token)
	{
		token = tokens[1] // Default error position
		message = "Invalid syntax of expression"
	}
	var start
	while (true)
	{
		if (token.start)
			start = token.start
		if (token.left)
			token = token.left
		else
			break
	}
	var pos = token.start ? token.start : 0
	addError(errors, message, pos, 1)
}
function append(source, target, sourceFromIndex, sourceToIndex)
{
	for (var i=sourceFromIndex; i<=sourceToIndex; i++)
		target[target.length] = source[i]
}
function ruleNumberToExp(tokens, pos, errors)
{
	var oldToken = tokens[pos]
	var newToken = new Object()
	newToken.type='exp'
	if (oldToken.text.substring(0,2).toLowerCase() == '0x')
		newToken.value = parseInt(oldToken.text)
	else
	if (oldToken.text.indexOf('.') >= 0 || oldToken.text.indexOf('e') > 0)
		newToken.value = parseFloat(oldToken.text)
	else
	if (oldToken.text.substring(0,2).toLowerCase() == '0b')
		newToken.value = parseBinary(oldToken, errors)
	else
		newToken.value = parseInt(oldToken.text, 10) // Ignore octal
	newToken.left = oldToken
	tokens[pos] = newToken
	return tokens
}
function ruleNameToExp(tokens, pos, errors)
{
	var oldToken = tokens[pos]
	var newToken = new Object()
	newToken.type='exp'
	var name = oldToken.text.toLowerCase()
	var value = 0
	if (name == 'pi')
		value = Math.PI
	else
	if (name == 'e')
		value = Math.E
	else
	if (variables[name])
	{
		var varval = variables[name]
		var idp = varval.indexOf(',')
		if (idp >= 0)
			varval = varval.substring(0,idp)+'.'+varval.substring(idp+1)
		if (varval.substring(0,2).toLowerCase() == '0x')
			value  = parseInt(varval)
		else
		if (varval.indexOf('.') >= 0 || varval.indexOf('e') > 0)
			value = parseFloat(varval)
		else
		if (varval.substring(0,2).toLowerCase() == '0b')
			value = parseBinary(varval, errors)
		else
			value = parseInt(varval, 10)
	}
	else
	{
		addError(errors, "Un-supported variable name ["+name+"]", oldToken.start, oldToken.text.length)
	}

	newToken.value=value
	newToken.left = oldToken
	tokens[pos] = newToken
	return tokens
}
function ruleBrExpBr(tokens, pos, errors)
{
	var newTokens = new Array()
	append(tokens, newTokens, 0, pos-1)

	var newToken = new Object()
	newToken.type='exp'
	newToken.value=tokens[pos+1].value
	newToken.left = tokens[pos+1]
	newTokens[newTokens.length] = newToken

	append(tokens, newTokens, pos+3, tokens.length-1)
	return newTokens
}
function ruleBrExpOpExpBr(tokens, pos, errors)
{
	var newTokens = new Array()
	append(tokens, newTokens, 0, pos-1)
	append(tokens, newTokens, pos+1, pos+3)
	append(tokens, newTokens, pos+5, tokens.length-1)
	return ruleOp(newTokens, pos, errors)
}
function ruleOp(tokens, pos, errors)
{
	var newTokens = new Array()
	append(tokens, newTokens, 0, pos-1)

	var result = tokens[pos].value
	var opToken = tokens[pos+1]
	if (opToken.text == '+')
		result += tokens[pos+2].value
	if (opToken.text == '-')
		result -= tokens[pos+2].value
	if (opToken.text == '*')
		result *= tokens[pos+2].value
	if (opToken.text == '/')
	{
		result /= tokens[pos+2].value
		if (result=="Infinity")
			addError(errors, "Divide by zero is not allowed", opToken.start, 1)
	}
	if (opToken.text == '^')
		result = Math.pow(result, tokens[pos+2].value)
	var newToken = new Object()
	newToken.type='exp'
	newToken.value=result
	newTokens[newTokens.length] = newToken
	newToken.left = opToken
	opToken.left = tokens[pos]
	opToken.right = tokens[pos+2]

	append(tokens, newTokens, pos+3, tokens.length-1)
	return newTokens
}
function rulePct(tokens, pos, errors)
{
	var newTokens = new Array()
	append(tokens, newTokens, 0, pos-1)

	var result = tokens[pos].value / 100
	var opToken = tokens[pos+1]
	var newToken = new Object()
	newToken.type='exp'
	newToken.value=result
	newTokens[newTokens.length] = newToken
	newToken.left = opToken
	opToken.left = tokens[pos]

	append(tokens, newTokens, pos+2, tokens.length-1)
	return newTokens
}
function ruleFact(tokens, pos, errors)
{
	var newTokens = new Array()
	append(tokens, newTokens, 0, pos-1)

	fact = tokens[pos].value
	if (fact <= 0 || fact != Math.floor(fact))
		addError(errors, "Factorial is allowed only on positive integers", tokens[pos].start, 1)
	var result =  1;
	for (var i=2; i<=fact; i++)
		result *= i
	var opToken = tokens[pos+1]
	var newToken = new Object()
	newToken.type='exp'
	newToken.value=result
	newTokens[newTokens.length] = newToken
	newToken.left = opToken
	opToken.left = tokens[pos]

	append(tokens, newTokens, pos+2, tokens.length-1)
	return newTokens
}
function ruleUnari1(tokens, pos, errors)
{
	var newTokens = new Array()
	append(tokens, newTokens, 0, pos)

	var result = tokens[pos+2].value
	var opToken = tokens[pos+1]
	if (opToken.text == '-')
		result = -result
	var newToken = new Object()
	newToken.type='exp'
	newToken.value=result
	newTokens[newTokens.length] = newToken
	newToken.left = opToken
	opToken.left = tokens[pos]
	opToken.right = tokens[pos+2]

	append(tokens, newTokens, pos+3, tokens.length-1)
	return newTokens
}
function ruleCallNoParm(tokens, pos, errors)
{
	var newTokens = new Array()
	append(tokens, newTokens, 0, pos-1)
	var nameToken = tokens[pos]
	var callToken = new Object()
	callToken.type='call'
	callToken.name = nameToken.text.toLowerCase()
	callToken.start = nameToken.start
	callToken.left = nameToken
	callToken.parms=new Array()
	var callResult = processCall(callToken, errors)
	newTokens[newTokens.length] = callResult
	append(tokens, newTokens, pos+3, tokens.length-1)
	return newTokens
}
function ruleCallStart(tokens, pos, errors)
{
	var newTokens = new Array()
	append(tokens, newTokens, 0, pos-1)
	var nameToken = tokens[pos]
	var newToken = new Object()
	newToken.type = 'call'
	newToken.name = nameToken.text.toLowerCase()
	newToken.start = nameToken.start
	newToken.left = nameToken
	newToken.parms = new Array()
	newTokens[newTokens.length] = newToken
	append(tokens, newTokens, pos+2, tokens.length-1)
	return newTokens
}
function ruleCallParm(tokens, pos, errors)
{
	var newTokens = new Array()
	append(tokens, newTokens, 0, pos)
	var callToken = tokens[pos]
	callToken.parms[callToken.parms.length]=tokens[pos+1]
	append(tokens, newTokens, pos+3, tokens.length-1)
	return newTokens
}
function ruleCallLast(tokens, pos, errors)
{
	var newTokens = new Array()
	append(tokens, newTokens, 0, pos-1)
	var callToken = tokens[pos]
	callToken.parms[callToken.parms.length]=tokens[pos+1]
	var callResult = processCall(callToken, errors)
	newTokens[newTokens.length] = callResult
	append(tokens, newTokens, pos+3, tokens.length-1)
	return newTokens
}

function validateFunctionParms(func, parms, errors, minParms, maxParms)
{
	var ok = true
	if (parms.length < minParms || parms.length > maxParms)
	{
		ok = false
		if (minParms == maxParms)
			addError(errors, "Function "+func.name+" expects "+minParms+(minParms == 1 ? " Parameter" : " Parameters"), func.start, func.name.length)
		else
			addError(errors, "Function "+func.name+" expects "+minParms+" to "+maxParms+" Parameters", func.start, func.name.length)
	}
	return ok
}
function processCall(token, errors)
{
	var result = new Object()
	result.type = 'exp'
	result.left = token
	var func = token.name
	var parms = token.parms
	if (func == 'ln')
		func = 'log'

	if (func == 'sqrt' || func=='abs' || func=='ceil' || func=='floor' || func=='log' || func=='round')
	{
		if (validateFunctionParms(token, parms, errors, 1, 1))
			result.value = eval('Math.'+func+'('+parms[0].value+')')
	}
	else
	if (func == 'sin' || func == 'cos'|| func == 'tan')
	{
		if (validateFunctionParms(token, parms, errors, 1, 1))
		{
			var v = parms[0].value
			if (prefs.angle == 'deg')
				v = v * Math.PI / 180
			v = eval('Math.'+func+'('+v+')')
			result.value = v
		}
	}
	else
	if (func == 'asin' || func == 'acos'|| func == 'atan')
	{
		if (validateFunctionParms(token, parms, errors, 1, 1))
		{
			var v = parms[0].value
			v = eval('Math.'+func+'('+v+')')
			if (prefs.angle == 'deg')
				v = v * 180 / Math.PI
			result.value = v
		}
	}
	else
	if (func == 'log10')
	{
		if (validateFunctionParms(token, parms, errors, 1, 1))
		{
			result.value = eval('Math.log('+parms[0].value+')/Math.log(10)')
		}
	}
	else
	if (func == 'hex')
	{
		if (validateFunctionParms(token, parms, errors, 1, 1))
		{
			result.value = parms[0].value
			result.displayHex = true
		}
	}
	else
	if (func == 'bin')
	{
		if (validateFunctionParms(token, parms, errors, 1, 1))
		{
			result.value = parms[0].value
			result.displayBin = true
		}
	}
	else
		addError(errors, "Un-supported function ["+func+"]", token.start, func.length)

	if (isNaN(result.value) || result.value == "Infinity")
		addError(errors, "Function result for given parameters is undefined", token.start, func.length)

	return result
}
function fixRoundingError(value)
{
	var sv = ""+value
	if (sv.indexOf('.') < 0)
		return value
	// TODO Handle "e" notation, leave only 12 digits after point
	var s = sv.substring(sv.indexOf('.'))
	var l = s.length
	if (l < 10)
		return value
	var l5 = s.substring(l - 5)
	var l5m1 = l5.substring(0, 4)
	if (l5m1 == '9999')
	{
		var i9 = s.length-3
		while (s.charAt(i9-1) == '9')
			i9--
		s = s.substring(0,i9+1)
		var d = '.000000000000000000000000000000000'.substring(0,s.length-1)+'1'
		var w = sv.substring(0, sv.indexOf('.'))
		if (w == '')
			w = '0'
		var op = '+'
		if (value < 0)
			op = '-'
		value = eval(w+op+'('+s+'+'+d+')')
	}
	else
	if (l5m1 == '0000')
	{
		value = eval(sv.substring(0, sv.length-4))
	}
	return value
}
function hex(value)
{
	var s = ""
	var v = Math.round(value)
	if (v < 0)
		v = Math.abs(0x01000000000000 + v)
	while (v != 0)
	{
		var p = v % 16
		if (p < 0)
			p = 16 + p
		s = "0123456789abcdef".charAt(p) + s
		v = Math.floor(v / 16)
	}
	if (s == "")
		s = "0"
	s = "0x"+s
	return s
}
function bin(value)
{
	var s = hex(value)
	var bindig = ['0000','0001','0010','0011','0100','0101','0110','0111','1000','1001','1010','1011','1100','1101','1110','1111']
	var hexdig = '0123456789abcdef'
	var res = '0b'
	for (var i = 2; i<s.length; i++)
		res += bindig[hexdig.indexOf(s.charAt(i))]
	return res
}
function parseBinary(token, errors)
{
	var r = 0
	var v = token.text // 0b0100
	for (var i = 2; i<v.length; i++)
	{
		r *= 2
		if (v.charAt(i) == '1')
			r++
		else
		if (v.charAt(i) != '0')
		{
			addError(errors, "Binary values can have only digits 0 or 1", token.start+i, 1)
			return 0
		}
	}
	return r
}


//
// Log (history) functions
//
var log = new Array()
function logAdd(exp, value, name)
{
	if (exp != value)
	{
		if (log.length == 0 || log[log.length-1].expression != exp)
		{
			var entry = new Object()
			entry.expression = exp
			entry.result = value
			if (name != null)
				entry.name = name
			log[log.length] = entry
			logShow()
		}
	}
}
function logShow()
{
	var str = ""
	var opts = ""
	if (log.length > 0)
	{
		str += "<span class='logtitle'>History</span><br />"
		opts += "<a href='javascript:logRemove()'>Remove checked lines</a>&nbsp&nbsp;"
		opts += "<a href='javascript:logCheckAll()'>Check all</a>&nbsp;&nbsp;"
		opts += "<a href='javascript:logClear()'>Clear history</a>&nbsp;&nbsp;"
		opts += "<a href='javascript:copyLogToClipoard()' title='Copy all history into the clipboard in a spreadsheet format'>Copy all history</a>"
		opts += "<br /><br />"
		str += opts

		str += "<table border='0' cellpadding='2' cellspacing='1' bgcolor='#c0c0c0'>"

		str += "<tr class='loghdr'><td>&nbsp;</td><td>Expression</td><td>Result</td><td>Name</td></tr>"
		for (var i=log.length-1; i>=0; i--)
		{
			var entry = log[i]
			if (entry != null)
			{
				str += "<tr bgcolor='#ffffff'>"
				str += "<td><input type='checkbox' id='log"+i+"' /></td>"
				str += "<td><a class='logval' title='Copy to calculator' href='javascript:logSet(&quot;"+entry.expression+"&quot;)'>"+entry.expression+"</a></td>"
				str += "<td><a class='logval' title='Copy to calculator' href='javascript:logSet(&quot;"+entry.result+"&quot;)'>"+entry.result+"</a></td>"
				if (!entry.name)
					str += "<td id='nameit"+i+"'><a href='javascript:nameit("+i+")' title='Define a name for this value'>name it</a></td>"
				else
					str += "<td>"+entry.name+"</td>"
				str += "</tr>"
			}
		}
		str += "</table>"
		str += opts
	}
	document.getElementById('log').innerHTML = str

	if (prefs.saveHistory)
		saveHistory();
}
function logSet(exp)
{
	var v = document.getElementById('exp').value
	var tv = v
	while (tv.length > 0 && tv.charAt(tv.length-1) == ' ')
		tv = tv.substring(0, tv.length-1)
	if (tv.length > 0 && "+-*/^,(".indexOf(tv.charAt(tv.length-1)) >= 0)
		exp = v+exp // Concat when ends with op
	setexp(exp, false)
	expFocus()
}
function logClear()
{
	log = new Array()
	logShow()
	expFocus()
}
function logRemove()
{
	var newLog = new Array()
	for (var i=0; i<log.length; i++)
	{
		var chkbox = document.getElementById('log'+i)
		if (chkbox && !chkbox.checked)
			newLog[newLog.length] = log[i]
	}
	log = newLog
	logShow()
	expFocus()
}
function logCheckAll()
{
	for (var i=0; i<log.length; i++)
	{
		var chkbox = document.getElementById('log'+i)
		if (chkbox)
			chkbox.checked = true
	}
	expFocus()
}

var nameitInd
function nameit(i)
{
	document.getElementById('nameit').value = ""
	var pos = position('nameit'+i)
	setTimeout("document.getElementById('nameit').focus()", 200)
	nameitInd = i
	var form = document.getElementById('nameitf')
	form.style.top = pos.top+"px"
	form.style.left = (pos.left+40)+"px"
	form.style.display='block'
}
function nameitset()
{
	var name = document.getElementById('nameit').value
	if (name.length > 0)
	{
		log[nameitInd].name=name
		variables[name] =''+log[nameitInd].result
		logShow()
	}
}

//
// Button handlers
//
function btn(b)
{
	var e = document.getElementById('exp').value
	if (b == 'clear')
		setexp('', false)
	else
		setexp(b+'('+e+')', true)

	expFocus()
}

//
// Display functions
//
var expCharWidth
function charWidth()
{
	if (expCharWidth)
		return expCharWidth
	var s = "8888888888"
	document.getElementById('charws').innerHTML=s
	expCharWidth = (document.getElementById('charwe').offsetLeft-document.getElementById('charws').offsetLeft) / s.length
	document.getElementById('charws').innerHTML=''
	return expCharWidth
}

function showErrors(expression, errors, showMessageText)
{
	var errind = ""
	errind += "<table border='0' cellpadding='0' cellspacing='0'><tr>"
	errind += "<td><img src='s.png' width='2' height='3' /></td>"
	if (errors.length > 0)
	{
		var colErrors = new Array()
		for (var i=0; i<errors.length; i++)
		{
			var error = errors[i]
			for (var c=0; c<error.length; c++)
			{
				var pos = error.start+c
				if (!colErrors[pos])
					colErrors[pos] = error.message
			}
		}
		var cw = charWidth()
		for (var i=0; i<expression.length; i++)
		{
			errind += "<td width='"+cw+"'>"
			if (colErrors[i])
				errind += "<img src='errdec.png' width='"+cw+"' height='3' title='"+colErrors[i]+"' />"
			else
				errind += "<img src='s.png' width='"+cw+"' height='3' />"
			errind += "</td>"
		}
	}
	errind += "</tr></table>"
	if (showMessageText && errors.length > 0)
	{
		var error = errors[0]
		var cw = charWidth()

		var pw = error.start*cw
		errind += "<table border='0' cellpadding='0' cellspacing='0'><tr>"
		errind += "<td><img src='s.png' width='"+pw+"' height='6' /></td>"
		errind += "<td><img src='errind.png' width='10' height='6' /></td>"
		errind += "</tr></table>"

		errind += "<table border='0' cellpadding='2' cellspacing='0'><tr>"
		var w = Math.max(400, pw+2*cw)
		errind += "<td nowrap='true' width='"+w+"' class='errmsg'>"
		errind += error.message
		errind += "</td></tr></table>"
	}
	document.getElementById('errors').innerHTML = errind
}
function showNesting(expression)
{
	var bkrind = ""
	var cw = charWidth()
	var maxLevel = 0
	var level = 0
	var levels = new Array()
	for (var i=0; i<expression.length; i++)
	{
		var c = expression.charAt(i)
		if ("([{".indexOf(c) >= 0)
		{
			if (++level > maxLevel)
				maxLevel = level
			levels[i] = level
		}
		else
		if (")]}".indexOf(c) >= 0 && level > 0)
		{
			levels[i] = level
			level--
		}
		else
			levels[i] = level
	}


	if (maxLevel > 0)
	{
		bkrind += "<table border='0' cellpadding='0' cellspacing='0'>"
		for (var level = maxLevel; level > 0; level--)
		{
			bkrind += "<tr><td><img src='s.png' width='2' height='3' /></td>"
			for (var i=0; i<expression.length; i++)
			{
				bkrind += "<td width='"+cw+"'>"
				var img = "s"
				if (levels[i] == level)
				{
					if (i == 0 || levels[i-1] < level)
						img = "ns"
					else
					if (")]}".indexOf(expression.charAt(i)) >= 0)
						img = "ne"
					else
						img = "nh"
				}
				else
				if (levels[i] > level)
				{
					img = "nh"
				}
				else
				if (levels[i] > 0 && "()[]{}".indexOf(expression.charAt(i)) >= 0)
				{
					img = "nv"
				}

				bkrind += "<img src='"+img+".png' width='"+cw+"' height='3' />"

				bkrind += "</td>"
			}
			bkrind += "</tr>"
		}
		bkrind += "</table>"
	}

	document.getElementById('nesting').innerHTML = bkrind
}

function copyLogToClipoard()
{
	var text = "Expression\tValue\n"

	for (var i=log.length-1; i>=0; i--)
	{
		var entry = log[i]
		if (entry != null)
		{
			text += entry.expression+"\t"+entry.result+"\n"
		}
	}


	if (window.clipboardData)
	{
		window.clipboardData.setData("Text", text)
	}
	else
	{
		alert('This feature is currently supported only for Internet Explorer')
	}
}

// History management in cookie
var historyCookieName = "calcHistory"

function saveHistory()
{
	var value = "";
	var passedMaxSize = false

	for (var i=log.length-1; i >=0 && !passedMaxSize;i--)
	{
		var entry = log[i]
		if (entry != null)
		{
			var newValue = "//"+entry.expression+"=="+entry.result
			if (entry.name)
				newValue+="::"+entry.name
			if (value.length+newValue.length<4096)
				value=newValue+value
			else
				passedMaxSize=true
		}
	}
	createCookie(historyCookieName, value, 365*10)
}

function readHistory()
{
	if (prefs.saveHistory == false)
		eraseCookie(historyCookieName)
	else
	{
		var value = readCookie(historyCookieName)
		log = new Array()

		while (value != null && value.indexOf("//")>=0) {
			var resultIndex = value.indexOf("==")
			var nextExpIndex = value.indexOf("//", 2)
			var exp = value.substring(2,resultIndex)
			if (nextExpIndex != -1) {
				var result = value.substring(resultIndex+2, nextExpIndex)
				value = value.substring(nextExpIndex)
			} else {
				var result = value.substring(resultIndex+2)
				value = ""
			}
			var nameIndex = result.indexOf("::")
			var name = null
			if (nameIndex>-1) {
				name = result.substring(nameIndex+2)
				result = result.substring(0, nameIndex)
				variables[name] =''+result
			}
			logAdd(exp, result, name)
		}
	}
}

function position(id)
{
	var pos = {left:0,top:0}
	var item = document.getElementById(id)
	while (item)
	{
		if (item.style.position == 'absolute')
			break

		pos.left += item.offsetLeft
		pos.top  += item.offsetTop
		item= item.offsetParent
	}

	return pos
}
