    function gi(id) 
    {
        return document.getElementById(id);
    }
    
    function init()
    {
        var elem = gi('js-text');
        elem.onkeyup = elem.onmouseup = elem.onchange = function() { countAll(true); };
        elem.onmouseup = function() { clearAll(); this.onmouseup = function() { countAll(true); }; };
        
        gi('js-up1').onclick   = function() { numericUpDown('js-updown1',  1, 10, 200); countAll(false); };            
        gi('js-down1').onclick = function() { numericUpDown('js-updown1', -1, 10, 200); countAll(false); };                    
        gi('js-up2').onclick   = function() { numericUpDown('js-updown2',  1, 10, 200); countAll(false); };            
        gi('js-down2').onclick = function() { numericUpDown('js-updown2', -1, 10, 200); countAll(false); };                    
        gi('js-up3').onclick   = function() { numericUpDown('js-updown3',  1, 10, 200); countAll(false); };            
        gi('js-down3').onclick = function() { numericUpDown('js-updown3', -1, 10, 200); countAll(false); };                    
        
        gi('js-clear').onclick   = clearAll;    
        gi('js-refresh').onclick = function() { countAll(true); };
        
        countAll(false);
    }

    function clearAll()
    {
        gi('js-text').value = '';
        countAll(true);
    }
    
    function countAll(reflect)
    {
        var s = gi('js-text').value;
        gi('js-totalcount').innerHTML = countTotalChars(s);
        gi('js-nonwhitecount').innerHTML = countNonwhiteChars(s);        
        gi('js-wordcount').innerHTML = countWords(s);                
        gi('js-linecount').innerHTML = countLines(s);           
        
        var num1 = parseNumericInput(gi('js-updown1').innerHTML);
        var num2 = parseNumericInput(gi('js-updown2').innerHTML);        
        var num3 = parseNumericInput(gi('js-updown3').innerHTML);        
        gi('js-stdlinecount').innerHTML = countStdLines(s, num1);                                
        gi('js-stdpagecount').innerHTML = countStdPages(s, num3, num2);                                        
        if (reflect) {
            gi('js-stdpage').innerHTML = getStdPages(s, num3, num2);                                                
        }
    }

    function countTotalChars(s)
    {
        return s.length;
    }

    function countNonwhiteChars(s)
    {
        return s.replace(/\s/g, '').length;
    }
    
    function countWords(s)
    {
        var a = s.match(/[^\s.:,;!?"'(){}[\]=*+|-]+/g);
        return a ? a.length : 0;
    }

    function countLines(s)
    {
        var a = s.match(/.$/mg);
        return a ? a.length : 0;
    }

    function countStdLines(s, n)
    {
        return Number(Math.floor(s.length/n*10)/10).toLocaleString();
    }

    function countStdPages(s, n, l)
    {
        var re = new RegExp('(.{1,' + n + '})([ -]+|$\n?)|(.{1,' + n + '})',  'mg');
        s = s.replace(re, "$1$3\n");
    
//        s = s.replace(/(.{1,60})([ -]+|$\n?)|(.{1,60})/mg, "$1$3\n");
        return Number(Math.floor(countLines(s)/l*100)/100).toLocaleString();
    }

    function getStdPages(s, n, l)
    {
        var re = new RegExp('(.{1,' + n + '})([ -]+|$\n?)|(.{1,' + n + '})',  'mg');
        s = s.replace(re, "$1$3\n");
//        s = s.replace(/(.{1,60})([ -]+|$\n?)|(.{1,60})/mg, "$1$3\n");        
        s = s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
       return s.replace(/$/mg, '<br>');
    }
    
    
    function numericUpDown(id, step, min, max)
    {
        var elem = gi(id);
        var val  = Math.floor((parseNumericInput(elem.innerHTML) + step) / step) * step;
        elem.innerHTML = Math.min(max, Math.max(min, val));
    }
    
    
    function parseNumericInput(s, minVal, maxVal)
    {
        if (minVal == undefined)
            minVal = 0;
        if (maxVal == undefined)
            maxVal = 10000;
        var n = parseInt('0' + s.replace(/[^0-9]/,''), 10); // avoids NaN
        return n < minVal ? minVal : (n > maxVal ? maxVal : n);
    }
