/* Script by Paul Fontaine and Gilles Van Assche. */

/* Functions of JavaScript part:
* - build repetitive html
* - react to user input changes
* - compute results and insert them in the html
* - control display or display:none, change css class of result
*/

// short for document.getElementById
function $id(idName){
  return document.getElementById(idName);
}

// make html element with id display visible or not
function doVisible(id, visible) {
    $id(id).style.display = (visible ? "" : "none");
}

function displayLargeNumber(logValue, idFamily) {
    var value, log10 = Math.log(10);

    if ((logValue > log10 * 4) || (logValue < -log10)) {
        doVisible(idFamily + '_10', true);
        $id(idFamily + '_pow10').innerHTML = Math.floor(logValue / log10);
        value = Math.exp(logValue - Math.floor(logValue / log10) * log10);
    }
    else {
        doVisible(idFamily + '_10', false);
        $id(idFamily + '_pow10').innerHTML = '';
        value = Math.exp(logValue);
    }
    if (value < 100) {
        $id(idFamily + '_abs').innerHTML = Math.round(value * 10) / 10;
    }
    else {
        $id(idFamily + '_abs').innerHTML = Math.round(value);
    }
}

// suggest parameters and display them
function keccakParameters(in_cr, in_pi) {
    var n, c, r, claimedCR, claimedPI, claimedGen, pct, faster, even, slower, cpb,
        log10, time, time_unit, universe, energy, sun_years, elec_years;

    // choose the parameters
    n = Math.ceil(Math.max(2 * in_cr, in_pi)); // output length
    c = Math.max(2 * in_cr, 2 * in_pi); // capacity
    c = 8 * Math.ceil(c / 8); // capacity must be a multiple of 8
    r = 1600 - c; // rate

    // translate the parameters into a flat sponge claim
    claimedCR = Math.min(c / 2, n / 2);
    claimedPI = Math.min(c / 2, n);
    claimedGen = c / 2;

    // compute the relative performance
    faster = even = slower = false;
    if (r < 1024) {
        pct = (1024 / r - 1) * 100;
        slower = true;
    }
    else if (r > 1024) {
        pct = (r / 1024 - 1) * 100;
        faster = true;
    }
    else {
        pct = 0;
        even = true;
    }
    if (pct < 10) {
        pct = Math.round(pct * 100) / 100;
    }
    else {
        pct = Math.round(pct * 10) / 10;
    }
    cpb = 1616 / r * 8;
    cpb = Math.round(cpb * 10) / 10;

    // insert results into html
    $id('n').innerHTML = n;
    $id('c').innerHTML = c;
    $id('r').innerHTML = r;
    $id('claimedc').innerHTML = c;
    $id('claimedn').innerHTML = n;
    $id('claimedCR').innerHTML = claimedCR;
    $id('claimedPI').innerHTML = claimedPI;
    $id('claimedGen').innerHTML = claimedGen;
    $id('pct').innerHTML = pct + '%';
    $id('cpb').innerHTML = cpb;

    // powers of 2
    if (c / 2 >= 64) {
        log10 = Math.log(10);
        doVisible('powersof2', true);
        $id('powerof2').innerHTML = c / 2;
        $id('pw2time_pw2').innerHTML = c / 2;
        $id('pw2energy_pw2').innerHTML = c / 2;

        time = Math.log(2) * c / 2 - log10 * 18;
        time_unit = "seconds";

        universe = 0;
        if (time > Math.log(60 * 60)) {
            time -= Math.log(60 * 60);
            time_unit = "hours";
            if (time > Math.log(24)) {
                time -= Math.log(24);
                time_unit = "days";
                if (time > Math.log(365.25)) {
                    time -= Math.log(365.25);
                    time_unit = "years";
                    if (time > (Math.log(14) + 9 * log10)) {
                        universe = time - (Math.log(14) + 9 * log10);
                    }
                }
            }
        }

        displayLargeNumber(time, 'pw2time');
        $id('pw2time_unit').innerHTML = time_unit;
        if (universe > 0) {
            doVisible('pw2universe', true);
            displayLargeNumber(universe, 'pw2universe');
        }
        else {
            doVisible('pw2universe', false);
        }

        energy = Math.log(2) * c / 2 + (Math.log(2.617) - log10 * 23);
        if (energy >= log10 * 20) {
            doVisible('pw2energy', true);
            displayLargeNumber(energy, 'pw2joules');

            if (energy >= log10 * 35) {
                doVisible('pw2sun', true);
                doVisible('pw2elec', false);
                sun_years = energy - (Math.log(1.22) + log10 * 34);
                displayLargeNumber(sun_years, 'pw2sun');
            }
            else {
                doVisible('pw2sun', false);
                doVisible('pw2elec', true);
                elec_years = energy - (Math.log(5.67) + log10 * 19);
                displayLargeNumber(elec_years, 'pw2elec');
            }
        }
        else {
            doVisible('pw2energy', false);
        }
    }
    else {
        doVisible('powersof2', false);
    }

    // display selected elements
    doVisible('faster', faster);
    doVisible('even', even);
    doVisible('slower', slower);
    doVisible('pct', faster || slower);
    doVisible('result', (c !== 0));
    doVisible('result0', (c === 0));
    doVisible('instruction', false);
}

// display feedback if incorrect value
function checkValue(in_value, error_id) {
    if (!in_value) {
        doVisible(error_id, false);
        return false;
    }
    else {
        var OK;
        OK = (!isNaN(parseInt(in_value, 10))) && ((in_value >= 0) && (in_value <= 796));
        doVisible(error_id, !OK);
        return OK;
    }
}

function processParameters()
{
    var in_cr, in_pi, OK = true;

    in_cr = $id('dcr').value;
    if (in_cr == "other") {
        in_cr = $id('tcr').value;
    }
    OK = checkValue(in_cr, 'errorcr') && OK;

    in_pi = $id('dpi').value;
    if (in_pi == "other") {
        in_pi = $id('tpi').value;
    }
    OK = checkValue(in_pi, 'errorpi') && OK;

    if (($id('dcr').value != "") && ($id('dcr').options[0].value == "")) {
        $id('dcr').remove(0);
    }
    if (($id('dpi').value != "") && ($id('dpi').options[0].value == "")) {
        $id('dpi').remove(0);
    }

    if (OK) {
        keccakParameters(in_cr, in_pi);
    }
}

function listChanged() {
    var in_cr, in_pi;

    in_cr = $id('dcr').value;
    in_pi = $id('dpi').value;
    doVisible('tcr', (in_cr == 'other'));
    doVisible('tpi', (in_pi == 'other'));
    doVisible('compute', ((in_cr == 'other') || (in_pi == 'other')));
    processParameters();
}

// populate <select> with options
function buildOpt(id) {
    // Don't try this with innerHTML instead. See http://support.microsoft.com/kb/276228
    var vals, ins, i;
    vals = [64, 72, 80, 96, 112, 128, 144, 160, 192, 224, 256, 288, 384, 512]; // list of values to be repeated
    ins = $id(id);
    ins.options[ins.length] = new Option("0 (don't care) ", "0");
    for (i = 0; i < vals.length; i++) {
        ins.options[ins.length] = new Option(vals[i], vals[i]);
    }
    ins.options[ins.length] = new Option("other", "other");
}

// initialize document
function onLoad() {
    buildOpt('dcr');
    buildOpt('dpi');
}

