function Calculator() {
    this.result = 0;
    this.operand = 0;
    this.operation = '';
    this.mem = 0;
    this.unit = '';
    this.scientific = false;
    this.conversion = undefined;
    this.appendMode = false;
}

Calculator.prototype.appendDigit = function(digit) {
    if (!this.appendMode) {
        this.operand = 0;
    }
    this.appendMode = true;

    switch (digit) {
        case '.':
            if (String(this.operand).indexOf('.') != -1) {
                return;
            }
            break;
        case '0':
            if (this.operand == 0 && String(this.operand).indexOf('.') == -1) {
                return;
            }
        default:
            if (this.operand == 0 && String(this.operand).indexOf('.') == -1) {
                this.operand = '';
            }
            break;
    }
    this.operand += digit;
};

Calculator.prototype.eraseDigit = function() {
    if (this.operand.length) {
        this.operand = this.operand.substr(0, this.operand.length - 1);
    }
};

Calculator.prototype.changeOperation = function(op) {
    this.executeOperation();
    this.result = eval(this.operand);
    this.operand = 0;
    this.operation = op;
};

Calculator.prototype.executeOperation = function() {
    if (this.operation) {
        switch (this.operation) {
            case 'exp':
                this.operand = eval('(' + this.result + ') * Math.pow(10, (' + this.operand + '))');
                break;
            case 'pow':
                this.operand = eval('Math.pow((' + this.result + '), (' + this.operand + '))');
                break;
            case 'root':
                this.operand = eval('Math.pow((' + this.operand + '), 1 / (' + this.result + '))');
                break;
            case '/':
                if (!this.operand) {
                    return;
                }
            default:
                this.operand = eval('(' + this.result + ')' + this.operation + '(' + this.operand + ')');
                break;
        }
    }
    this.operation = '';
    this.result = 0;
    this.appendMode = false;
};

Calculator.prototype.clear = function() {
    if (this.operand) {
        this.operand = 0;
        if (!this.operation) {
            this.unit = '';
        }
    } else {
        this.operation = '';
        this.result = '';
        this.unit = '';
    }
};

Calculator.prototype.opposite = function() {
    this.operand = -this.operand;
};

Calculator.prototype.inverse = function() {
    this.operand = 1 / this.operand;
};

Calculator.prototype.pcent = function() {
    this.operand /= 100;
    this.changeOperation('=');
};

Calculator.prototype.sqrt = function() {
    this.operand = Math.pow(this.operand, 0.5);
};

Calculator.prototype.cubrt = function() {
    this.operand = Math.pow(this.operand, 1 / 3);
};

Calculator.prototype.madd = function() {
    this.executeOperation();
    this.mem += this.operand;
};

Calculator.prototype.msub = function() {
    this.executeOperation();
    this.mem -= this.operand;
};

Calculator.prototype.mread = function() {
    this.operand = '';
    this.appendDigit(this.mem);
};

Calculator.prototype.mclear = function() {
    this.mem = 0;
};

Calculator.prototype.sin = function() {
    this.operand = Math.sin(this.operand);
};

Calculator.prototype.cos = function() {
    this.operand = Math.cos(this.operand);
};

Calculator.prototype.tan = function() {
    this.operand = Math.tan(this.operand);
};

Calculator.prototype.log = function() {
    this.operand = Math.log(this.operand) / Math.log(10);
};

Calculator.prototype.ln = function() {
    this.operand = Math.log(this.operand);
};

Calculator.prototype.factorial = function(n) {
    if (n == 1) {
        return 1;
    }
    return n * this.factorial(n - 1);
};

Calculator.prototype.fac = function() {
    this.operand = this.factorial(this.operand);
};

Calculator.prototype.sqr = function() {
    this.operand = Math.pow(this.operand, 2);
};

Calculator.prototype.cub = function() {
    this.operand = Math.pow(this.operand, 3);
};

Calculator.prototype.changeConversion = function(conversion) {
    if (this.conversion == conversion) {
        this.conversion = '';
        this.unit = '';
    } else {
        this.conversion = conversion;
        this.unit = '';
    }
};

Calculator.prototype.changeUnit = function(unit) {
    this.executeOperation();
    if (!this.unit) {
        this.unit = unit;
        return;
    }
    this.operand = parseFloat(this.operand);
    this.operand += parseFloat(this.conversions[this.conversion][this.unit]['inc']);
    this.operand *= this.conversions[this.conversion][this.unit]['const'];
    this.unit = unit;
    this.operand /= this.conversions[this.conversion][this.unit]['const'];
    this.operand -= this.conversions[this.conversion][this.unit]['inc'];
};

function CalculatorView(model) {
    this.model = model;
    this.skin = 'pale';
}

CalculatorView.prototype.update = function() {
    var display = '';
    if (this.model.mem) {
        display += ' <div style="float:left">M</div>';
    }
    if (this.model.result) {
        var result = this.model.result;
        if (this.model.conversion == 'currency') {
            result = Math.round(result * 10000) / 10000;
        } else {
            var rounded = Math.round(result * 10000) / 10000;
            if (rounded != result) {
                var addDots = true;
            }
            result = rounded;
        }
        display += this.formatNumber(result);
        if (addDots) {
            display += '...';
        }
    }
    if (this.model.operation && this.model.operation != '=') {
        var op = this.model.operation;
        switch (op) {
            case '*':
                op = ' &#215 ';
                break;
            case '/':
                op = ' &#247 ';
                break;
            case 'exp':
                op = ' &#215 10e ';
                break;
            case 'pow':
                op = ' ^ ';
                break;
            case 'root':
                op = '&radic; ';
                break;
            default:
                op = ' ' + op + ' ';
                break;
        }
        display += op;
    }
    var operand = this.model.operand;
    addDots = false;
    if (!this.model.appendMode) {
        if (this.model.conversion == 'currency') {
            operand = Math.round(operand * 10000) / 10000;
        } else {
            var rounded = Math.round(operand * 10000) / 10000;
            if (rounded != operand) {
                var addDots = true;
            }
            operand = rounded;
        }
    }
    operand = operand ? operand : '0';
    display += this.formatNumber(operand);
    if (addDots) {
        display += '...';
    }

    if (this.model.unit) {
        var num = 'sing';
        if (this.model.operand >= 2) {
            num = 'plur';
        }
        display += ' ' + this.model.conversions[this.model.conversion][this.model.unit][num];
    }

    //display = this.model.mem + '(' + this.model.result + ')(' + this.model.operation + ')(' + this.model.operand + ')' + this.model.unit;
    $("#display").get(0).innerHTML = display; // .html(val) crashes safari 2

    this.updateFraction();
};

CalculatorView.prototype.updateFraction = function() {
    var val = this.model.operand;
    var intPart = val > 0 ? Math.floor(val) : 0 - Math.floor(0 - val);
    var decPart = val - intPart;
    if (intPart) {
        decPart = Math.abs(decPart);
    }
    var fracNum = Math.round(decPart * 10000);
    var fracDen = 10000;
    var pgcd = this.pgcd(fracNum, fracDen);
    fracNum /= pgcd;
    fracDen /= pgcd;
    if (fracDen > 8) {
        var tries = Array(2, 3, 4, 8);
        var nearest = tries[tries.length - 1];
        var nearestVal = -1;
        var curVal = 0;
        for (var j in tries) {
            i = tries[j];
            curTry = Math.abs(decPart - Math.round(fracNum / (fracDen / i)) / i);
            if (nearestVal == -1 || curTry < nearestVal) {
                nearest = i;
                nearestVal = curTry;
            }
        }
        fracNum /= fracDen / nearest;
        fracNum = Math.round(fracNum);
        fracDen = nearest;
        pgcd = this.pgcd(fracNum, fracDen);
        fracNum /= pgcd;
        fracDen /= pgcd;
    }
    if (fracNum == fracDen) {
        fracNum = 0;
        fracDen = 0;
        intPart++;
    }
    var approx = '';
    approx += intPart && intPart != val ? '<span style="font-size:16px">' + this.formatNumber(intPart) + '</span> ' : '';
    approx += fracNum ? fracNum + '/' + fracDen : '';
    approx += approx && this.model.unit ? ' ' + this.model.unit : '';
    if (intPart + fracNum / fracDen != val && approx) {
        approx = 'approx.: ' + approx;
    }
    $("#approx").get(0).innerHTML = approx ? approx : '&nbsp;'; // .html(val) crashes safari 2

};

CalculatorView.prototype.pgcd = function(a, b) {
    return (a % b == 0) ? Math.abs(b) : this.pgcd(b, a % b);
};

CalculatorView.prototype.formatNumber = function(num) {
    var num = String(num);
    var neg = false;
    if (num[0] == '-') {
        neg = true;
        num = num.substr(1);
    }
    num = num.split('.');
    var fnum = '';
    for (var i = 0; i < num[0].length; i++) {
        if (i && !(i % 3)) {
            fnum = " " + fnum;
        }
        fnum = num[0].charAt(num[0].length - i - 1) + fnum;
    }
    if (num[1]) {
        fnum += '.' + num[1];
    }
    return (neg ? '-' : '') + fnum;
};

CalculatorView.prototype.updateScientificPad = function() {
    if (this.model.scientific) {
        $(".scientificPad").show();
    } else {
        $(".scientificPad").hide();
    }
};

CalculatorView.prototype.updateConversion = function() {
    $("div.conversion").hide();
    if (this.model.conversion) {
        $("#" + this.model.conversion).show();
    }
};

CalculatorView.prototype.updateSkin = function() {
    this.applySkin(this.skin);
};

CalculatorView.prototype.applySkin = function(skin) {
    for (var selector in this.skins[skin]) {
        for (var css in this.skins[skin][selector]) {
            $(selector).css(css, this.skins[skin][selector][css]);
        }
    }
};

CalculatorView.prototype.skins = {
    'pale' : {
        '#calculator' : {'background' : '#fff', 'border' : '#bbb solid 2px'},
        '#calculator td.btn' : {'color' : '#00aad4', 'background' : '#ddd'},
        '#calculator td.display' : {'color' : '#f60', 'background' : '#ddd'},
        '#calculator td.num' : {'color' : '#f60', 'background' : '#ddd'},
        '#logo' : {'background' : "url('" + paleLogoUrl + "')"}
    },
    'dark' : {
        '#calculator' : {'background' : '#000', 'border' : '#888 solid 2px'},
        '#calculator td.btn' : {'color' : '#00aad4', 'background' : '#000'},
        '#calculator td.display' : {'color' : '#f60', 'background' : '#555'},
        '#calculator td.num' : {'color' : '#f60', 'background' : '#000'},
        '#logo' : {'background' : "url('" + darkLogoUrl + "')"}
    }
};

function CalculatorController(model, view) {
    this.model = model;
    this.view = view;

}

CalculatorController.prototype.bindToHtml = function() {
    $(".pressable").mousedown(function() { controller.press($(this).attr("x-val")); });
    $(".unit").mousedown(function() { controller.pressUnit($(this).attr("x-val")); });
    $(".skin").mousedown(function() { controller.pressSkin($(this).attr("x-val")); });
    $(document).keydown(function(e) { return controller.keyDown(e.keyCode); });
    $(document).keypress(function(e) { return controller.keyPress(e.which); });
};
CalculatorController.prototype.keyDown = function(code) {
    switch (code){
        case 8:
            this.press("era");
            break;
        case 46:
            this.press("clr");
            break;
        case 190:
            this.press("dot");
            break;
        default:
            return true;
            break;
    }
    return false;
};
CalculatorController.prototype.keyPress = function(code) {
    switch (code) {
        case 13:
            this.press("eq");
            break;
        default:
            if (code > 32) {
                this.press(String.fromCharCode(code));
            }
            break;
    }
};
CalculatorController.prototype.pressUnit = function(val) {
    this.model.changeUnit(val);
    this.view.update();
};
CalculatorController.prototype.press = function(val) {
    if (val == 'mrc') {
        if (this.memRead) {
            this.model.mclear();
        } else {
            this.memRead = true;
            this.model.mread();
        }
    } else {
        this.memRead = false;
    }
    switch (val) {
        case "0":
            case "1":
            case "2":
            case "3":
            case "4":
            case "5":
            case "6":
            case "7":
            case "8":
            case "9":
            this.model.appendDigit(val);
        break;
        case "dot":
            case ".":
            this.model.appendDigit('.');
        break;
        case "add":
            case "+":
            this.model.changeOperation('+');
        break;
        case "sub":
            case "-":
            this.model.changeOperation('-');
        break;
        case "mul":
            case "*":
            case "x":
            case "X":
            this.model.changeOperation('*');
        break;
        case "div":
            case "/":
            this.model.changeOperation('/');
        break;
        case "eq":
            case "=":
            case "\n":
            this.model.executeOperation();
        break;
        case "opp":
            this.model.opposite();
        break;
        case "inv":
            this.model.inverse();
        break;
        case "pcent":
            this.model.pcent();
        break;
        case "sqrt":
            this.model.sqrt();
        break;
        case "clr":
            this.model.clear();
        break;
        case "era":
            this.model.eraseDigit();
            break;
        case "madd":
            this.model.madd();
        break;
        case "msub":
            this.model.msub();
        break;
        case "togsci":
            this.model.scientific = !this.model.scientific;
        this.view.updateScientificPad();
        break;
        case "sin":
            this.model.sin();
        break;
        case "cos":
            this.model.cos();
        break;
        case "tan":
            this.model.tan();
        break;
        case "log":
            this.model.log();
        break;
        case "ln":
            this.model.ln();
        break;
        case "fac":
            this.model.fac();
        break;
        case "exp":
            this.model.changeOperation('exp');
        break;
        case "sqr":
            this.model.sqr();
        break;
        case "cub":
            this.model.cub();
        break;
        case "pow":
            this.model.changeOperation('pow');
        break;
        case "cubrt":
            this.model.cubrt();
        break;
        case "root":
            this.model.changeOperation('root');
        break;
        case "toglen":
            this.model.changeConversion('length');
        this.view.updateConversion();
        break;
        case "togvol":
            this.model.changeConversion('volume');
        this.view.updateConversion();
        break;
        case "togmas":
            this.model.changeConversion('mass');
        this.view.updateConversion();
        break;
        case "togtem":
            this.model.changeConversion('temperature');
        this.view.updateConversion();
        break;
        case "togcur":
            this.model.changeConversion('currency');
        this.view.updateConversion();
        break;
    }
    this.view.update();
};

CalculatorController.prototype.pressSkin = function(skin) {
    this.view.skin = skin;
    this.view.updateSkin();
};

var calc = new Calculator();
var view = new CalculatorView(calc);
var controller = new CalculatorController(calc, view);
$(function () {
    controller.bindToHtml();
    view.update();
    view.updateSkin();
});

