<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">var heatNumber;
var autoget = false;
var timestamp = 0;
var last_row = 0;
var targetStatus = [];
var targetTimers = [];
var guid;
var confirmedTimers;
var datamode;               // training or competition data
var sleeptime = 0;

$(document).ready(function()
{
    //time($('#time').html());
    //getData();
    //setInterval( getData, 500 );
});

// Hakee taulun ammuntarivit
// Author:      Janne Mustonen
// Created:     11.3.2020
// Modified:    17.12.2020 - Janne Mustonen
function getTargetRows( target , mode )
{
    var url = "json/getTargetRows/"+guid+"/"+last_row;
    if ( typeof( target ) !== "undefined" ) url += "/"+target;
    if ( typeof( mode != "undefined" ) ){ url+="?mode="+mode; }
    
    return $.ajax({
        url: base_url + url,
        dataType: 'json',
        async: false,
        success: function( jsondata ) {
            return( jsondata );
        }
    });
}

// PiirtÃ¤Ã¤ ammuntarivit, jotka haettu manuaalisesti
// Author:      Janne Mustonen
// Created:     23.7.2020
function drawTargetRows( jsondata )
{
    $.each( jsondata.data , function( index, json ) {
        drawTargetRow( json ); 
        last_row = json.id;
    });
}

// Hakee kaikkien ratojen HCP-viestit
// Author:      Janne Mustonen
// Created:     24.7.2020
function updateHCP( targetNumber , mode )
{
    
    var url = "json/allhcp/"+guid;
    
    if ( targetNumber !== undefined ){
        url += "/"+targetNumber;
    }
    
    if ( typeof( mode != "undefined" ) )
    {
        url+="?mode="+mode;
    }
    
    var html = post( url , null , "get" );
    var hcpdata = html.responseText;
    
    // Jos HCP-dataa lÃ¶ytyi..
    if( hcpdata.status ){
        
        $.each( hcpdata.data , function( index, hcp ) {
            // Init targetStatus array
            if ( typeof(targetStatus[hcp.targetNumber]) === 'undefined' ){
                //targetStatus[hcp.targetNumber] = { finished: false };
                targetStatus[hcp.targetNumber] = [];
            }
            targetStatus[hcp.targetNumber][hcp.heatNumber] = hcp.numberOfShots;
            
        });
        
    }
}

// Hakee kaikkien ratojen pÃ¤Ã¤tÃ¶sajat
// Author:      Janne Mustonen
// Created:     16.12.2020
function confirmedTimers()
{
    var url = "json/endingTimes/"+guid;
    var json = post( url , null , "get" ).responseText;
    if ( json.status )
    {
        confirmedTimers = json.data;
    }
    
    //console.log( confirmedTimers );
    
}

// PiirtÃ¤Ã¤ SSE:n eventistÃ¤ saapuvat ammuntarivit
// Author:  Janne Mustonen
// Created: 27.3.2020
function drawTargetRowsSSE( jsondata )
{
    if ( jsondata.status ){
        $.each( jsondata.data , function( index, json ) {
            drawTargetRow( json );
        });
    }
}

// PiirtÃ¤Ã¤ SSE:n eventistÃ¤ saadun osumat tauluun
// Author:  Janne Mustonen
// Created: 27.3.2020
function drawSingleTargetRowsSSE( jsondata )
{
    //console.log( jsondata );
    if ( jsondata.status ){
        
        $.each( jsondata.data , function( index, json ) {
            drawTargetRow( json );
        });
    }
}

// PiirtÃ¤Ã¤ ammuntarivit
// Author:      Janne Mustonen
// Created:     11.3.2020
// Modified:    29.8.2021
function drawTargetRow( json )
{
    if ( json.status == false ){ return; }

    // TODO: Jos taulu jostain syystÃ¤ puuttuu, piirretÃ¤Ã¤n taulu
    
    var stationNumber = json.stationNumber;
    var timer_length = parseInt( json.secondsFromHeatStart / competition.secondsPerHeat * 100 );
    var target = $( '[data-stationNumber="'+stationNumber+'"]' );
    var hitarea = target.find( '.hitarea' );
    var milliseconds = parseTimestamp( json.timestamp );
    
    // Alusta targetTimers-arrayhin radan tiedot
    if ( targetTimers[stationNumber] === undefined ){
        targetTimers[stationNumber] = [];
        targetTimers[stationNumber]['status'] = false;                          // Target timer status
        targetTimers[stationNumber]['seconds'] = competition.secondsPerHeat;
        targetTimers[stationNumber]['heat'] = 0;                                // Radan tÃ¤mÃ¤nhetkinen kierros
        targetTimers[stationNumber]['round'] = 0;
        targetTimers[stationNumber]['lastHitNum'] = 0;
        targetTimers[stationNumber]['firstTimestamp'] = milliseconds;           // First competition TAP timestamp (milliseconds)
        targetTimers[stationNumber]['heatTimestamp'] = 0;                       // Current heat start timestamp (milliseconds)
        targetTimers[stationNumber]['heatElapsed'] = 0;                         // Seconds from start of current heat
        targetTimers[stationNumber]['prev'] = milliseconds;                     // Previous TAP or HCP timestamp (milliseconds)
        targetTimers[stationNumber]['total'] = 0;                               // Target total milliseconds

        // Init targetStatus array
        if ( typeof(targetStatus[stationNumber]) === 'undefined' ){
            targetStatus[stationNumber] = { finished: false };
        }
    }
    
    targetStatus[stationNumber]['heat'] = json.heatNumber;
    
    // EnsimmÃ¤isen laukaus
    if ( json.heatShotNumber == 1 )
    {
        
        targetTimers[stationNumber]['heatTimestamp'] = milliseconds;
        
        // Jos ollaan competition moodissa
        if ( parseInt( competition.mode ) &gt;= 1 ){
            
            // Kaikkia edellisen kierroksen osumia ei ole tullut, eli aika loppunut kesken. Haetaan aika edellisen kierroksen HCP-viestistÃ¤.
            if ( json.heatNumber &gt; 1 &amp;&amp; targetTimers[stationNumber]['lastHitNum'] &lt; competition.shotsPerHeat ){
                
                var hcp = { stationNumber: stationNumber, heatNumber: json.heatNumber-1 };
                hcp = getHCP( hcp );
                addResults( hcp );
                
            }

            // PÃ¤ivitÃ¤ juoksuaika
            var runningtime = ( milliseconds - targetTimers[stationNumber]['prev'] ) / 1000;
            if ( runningtime &gt; 0 ){
                targetTimers[stationNumber]['total'] += runningtime;
                target.find( 'div.results_wrapper').find( 'div.runningtime' ).last().html( formatStopwatch( runningtime ) );
            }

            // JÃ¤rjestÃ¤ tulokset
            updateTotalTimeHtml(stationNumber);
            //sortResults();

            // Jos ammuttu enemmÃ¤n kuin kierroksia ja kilpailu on kÃ¤ynnissÃ¤, ei edetÃ¤ pidemmÃ¤lle
            if ( json.heatNumber &gt; competition.rounds &amp;&amp; competition.mode &gt; 0 ){

                // PÃ¤ivitetÃ¤Ã¤n ampujan totaltime
                /*
                var postdata = { totalTime: targetTimers[stationNumber]['total'], totalTime_stopwatch: formatStopwatch( targetTimers[stationNumber]['total'] ) };
                post( '/json/update/totaltime/'+guid+'/'+json.stationNumber , postdata );

                $( 'table.resultBoard tr[data-stationnumber="'+json.stationNumber+'"]' ).find( 'div.finalrun' ).removeClass( 'finalrun' );

                // LisÃ¤tÃ¤Ã¤n nÃ¤kymÃ¤n perÃ¤Ã¤n kokonaissekunnit
                confirmedTotalTimeDifference( stationNumber );
                var results = $( 'table.resultBoard tr[data-stationnumber="'+json.stationNumber+'"]' ).find( 'div.results_wrapper' );
                var text = "&lt;div class='finaltime'&gt;"+formatStopwatch( targetTimers[stationNumber]['total'] )+"&lt;/div&gt;";
                results.append( text );
                
                if ( typeof( confirmedTimers[json.stationNumber] ) != 'undefined' &amp;&amp; confirmedTimers[json.stationNumber] != "" ){  target.find( '.finaltime' ).prepend( '&lt;span class="ui-icon ui-icon-clock"&gt;&lt;/span&gt; ' ); }
                console.log( confirmedTimers[json.stationNumber] );
                
                moveToResultsBoard( json.stationNumber );
                sortResults();
                */
                //finishShooting( stationNumber );
                return;
            }
        }
        
        targetTimers[stationNumber]['heatElapsed'] = 0;
        
        target.find( 'div.timer' ).find( 'span' ).remove();
        hitarea.find( "span" ).removeClass().addClass( 'miss' );
        hitarea.show();
        target.find( 'div.results' ).hide().html( '' );

        target.data( 'seconds' , competition.secondsPerHeat );
        targetTimers[stationNumber]['seconds'] = parseInt( competition.secondsPerHeat );
        targetTimers[stationNumber]['heat'] = json.heatNumber;
        targetTimers[stationNumber]['round'] += 1;

        // KÃ¤ynnistetÃ¤Ã¤n radan timer
        if ( targetTimers[stationNumber]['status'] === false ){
            targetTimers[stationNumber]['status'] = true;
            targetTimers[stationNumber]['elapsed'] = milliseconds - targetTimers[stationNumber]['heatTimestamp'];
            targetTimers[stationNumber]['timer'] = setInterval( function(){ updateTargetTimer( stationNumber ); } , 250 );
        }

    }
    else if ( json.heatNumber &gt; competition.rounds &amp;&amp; parseInt( competition.mode ) &gt;= 1 )
    {
        // Kierrokset ammuttu loppuun, ei edetÃ¤
        return;
    }
    
    targetTimers[stationNumber]['lastHitNum'] = json.heatShotHit;
    
    // PiirrÃ¤ osumat ja hudit ajastimeen
    var seconds = ( milliseconds - targetTimers[stationNumber]['heatTimestamp'] ) / 1000;
    var position = 96 - parseInt( seconds / competition.secondsPerHeat * 100 );
    if ( json.shotHit == 1 ){
        // Osuma
        for (i = 0; i &lt; json.heatShotHit; i++) {
            hitarea.find( "span:eq("+i+")" ).removeClass().addClass( 'hit' );
        }
        var shot = "&lt;span class='hit' style='left: "+position+"%'&gt;â—&lt;/span&gt;";
        target.find( 'div.timer' ).append( shot );
    }else{
        // Huti
        var shot = "&lt;span class='miss' style='left: "+position+"%'&gt;â—&lt;/span&gt;";
    }
    target.find( 'div.timer' ).append( shot );
    
    // Sarjan viimeinen laukaus..
    if ( json.heatShotHit == competition.shotsPerHeat )
    {
        // Onko kyseessÃ¤ kierroksen viimeinen laukaus
        clearInterval(targetTimers[stationNumber]['timer']);
        targetTimers[stationNumber]['status'] = false;
        targetTimers[stationNumber]['prev'] = milliseconds;
        targetTimers[stationNumber]['elapsed'] = seconds;
        updateTargetTimer( stationNumber );
        showResults( json , target );
        
    }
    else if ( json.heatShotNumber == targetStatus[stationNumber][json.heatNumber] )
    {
        // Onko kyseessÃ¤ viimeisellÃ¤ kierroksella kesken loppunut aika?
        clearInterval(targetTimers[stationNumber]['timer']);
        targetTimers[stationNumber]['status'] = false;
        targetTimers[stationNumber]['elapsed'] = competition.secondsPerHeat;
        updateTargetTimer( stationNumber );
        showResults( json , target );
    }
    else
    {
        targetTimers[stationNumber]['elapsed'] = seconds;
    }
    
    // Viimeisen kierroksen viimeinen laukaus, ammunta on radan osalta pÃ¤Ã¤ttynyt
    // Modified: 29.8.2021
    if ( json.heatNumber == competition.rounds &amp;&amp; json.heatShotHit == competition.shotsPerHeat )
    {
        //targetStatus[stationNumber].finished = true;        
        //finishShooting( stationNumber );
        //return;
        finishShooting( stationNumber );
    }

    target.find( 'div.round' ).html( json.heatNumber );
    
}

// PÃ¤Ã¤ttÃ¤Ã¤ ammunnan radalta
// Created:     29.8.2021
// Author:      Janne Mustonen
function finishShooting( stationNumber )
{
    // Ei tehdÃ¤ training tilassa
    if ( parseInt( competition.mode ) == 0 )
    {
        return;
    }
    
    var target = $( '[data-stationNumber="'+stationNumber+'"]' );
    
    // PÃ¤ivitetÃ¤Ã¤n ampujan totaltime
    var postdata = { totalTime: targetTimers[stationNumber]['total'], totalTime_stopwatch: formatStopwatch( targetTimers[stationNumber]['total'] ) };
    post( 'json/update/totaltime/'+guid+'/'+stationNumber , postdata );
    
    $( 'table.resultBoard tr[data-stationnumber="'+stationNumber+'"]' ).find( 'div.finalrun' ).removeClass( 'finalrun' );

    // LisÃ¤tÃ¤Ã¤n nÃ¤kymÃ¤n perÃ¤Ã¤n kokonaissekunnit
    confirmedTotalTimeDifference( stationNumber );
    var results = $( 'table.resultBoard tr[data-stationnumber="'+stationNumber+'"]' ).find( 'div.results_wrapper' );
    

    if ( typeof( confirmedTimers[stationNumber] ) != 'undefined' &amp;&amp; confirmedTimers[stationNumber] != "" )
    {
        var text = "&lt;div class='finaltime'&gt;"+formatStopwatch( targetTimers[stationNumber]['total'] )+"&lt;/div&gt;";
        results.append( text );
        target.find( '.finaltime' ).prepend( '&lt;span class="ui-icon ui-icon-clock"&gt;&lt;/span&gt; ' );
    }
    else
    {
        var text = "&lt;div class='finaltime_unconfirmed'&gt;"+formatStopwatch( targetTimers[stationNumber]['total'] )+"&lt;/div&gt;";
        results.append( text );
        target.find( '.finaltime' ).prepend( '&lt;span class="ui-icon ui-icon-clock"&gt;&lt;/span&gt; ' );
    }

    // Sort ja nosto vain finished tilassa
    if ( parseInt( competition.mode ) == 2 )
    {
        moveToResultsBoard( stationNumber );
        sortResults();
    }
    
}

// Vertaa vahvistettua lopetusaikaa koneelliseen ja palauttaa erotuksenÂ¨
// Author:      Janne Mustonen
// Created:     16.12.2020
function confirmedTotalTimeDifference( stationNumber )
{
    if ( typeof( confirmedTimers[stationNumber] ) !== "undefined" &amp;&amp; confirmedTimers[stationNumber] != "" )
    {
        var system = targetTimers[stationNumber]['total'];
        var stopWatch = stopWatchToSeconds( confirmedTimers[stationNumber] );
        var difference = stopWatch - system;

        // PÃ¤ivitÃ¤ laskettu aika vahvistettuun
        targetTimers[stationNumber]['total'] = stopWatch;
        //console.log( targetTimers[stationNumber]['total'] );
        
        // SiirrÃ¤ erotus viimeiseen juoksuun
        if ( $('#singleTargetWrapper').length &gt; 0 )
        {
            var finalrunobj = $( '#singleTargetWrapper section.singletarget_wrapper:last div.runningtime' );
        }
        else
        {
            var finalrunobj = $( 'table.resultBoard tr[data-stationnumber="'+stationNumber+'"]' ).find( 'div.runningtime:last' );
        }
        
        var finalrun = finalrunobj.html();
        finalrun = stopWatchToSeconds( finalrun ) + difference;
        finalrunobj.html( formatStopwatch( finalrun ) );
        
        updateTotalTimeHtml( stationNumber );
        
        return difference;
    }
    
    return false;
}

function reduceTargetTimer( stationNumber )
{
    if ( targetTimers[stationNumber]['status'] === false ){ return; }
    
    targetTimers[stationNumber]['seconds'] -= 0.25;
    
    var target = $( '[data-stationNumber="'+stationNumber+'"]' );
    var seconds = targetTimers[stationNumber]['seconds'];
    var timer_length = parseInt( seconds / competition.secondsPerHeat * 100 ) - 2;
    var milliseconds = targetTimers[stationNumber]['prev'];
    
    if ( timer_length &gt;= 0 ){
        target.find( 'div.bar' ).css( 'width' , timer_length+"%" );
    }else{
        clearInterval(targetTimers[stationNumber]['timer']);
        targetTimers[stationNumber]['status'] = false;
        targetTimers[stationNumber]['seconds'] = 0;
        
        var json = { stationNumber: stationNumber, heatNumber: targetTimers[stationNumber]['heat'] } ;
        showResults( json , target );
        
    }
    
}

// PiirtÃ¤Ã¤ timerin (v2) 
// Author:      Janne Mustonen
// Created:     3.7.2020
// Modified:    29.8.2021
function updateTargetTimer( stationNumber )
{
    //if ( targetTimers[stationNumber]['status'] === false ){ return; }
    
    // Laske montako prosenttia kierroksen ajasta on kulunut
    
    var elapsed = parseInt( targetTimers[stationNumber]['elapsed'] / competition.secondsPerHeat * 100 );
    var target = $( '[data-stationNumber="'+stationNumber+'"]' );
    var timer_length = 100-elapsed-2;
    
    if ( timer_length &gt; 0 )
    {
        target.find( 'div.bar' ).css( 'width' , timer_length+"%" );
    }
    else
    {
        target.find( 'div.bar' ).css( 'width' , "0px" );
        clearInterval(targetTimers[stationNumber]['timer']);
        targetTimers[stationNumber]['status'] = false;
        targetTimers[stationNumber]['seconds'] = 0;
        
        var json = { stationNumber: stationNumber, heatNumber: targetTimers[stationNumber]['heat'] } ;
        showResults( json , target );
        
        if ( targetStatus[stationNumber]['heat'] == competition.rounds )
        {
            finishShooting( stationNumber );
        }
        
    }
    targetTimers[stationNumber]['elapsed'] += 0.25;
}

// Hakee HCP-viestin
// Author:  Janne Mustonen
// Created: 1.7.2020
function getHCP( json )
{
    var url = "json/hcp/"+guid;
    if ( typeof( datamode != "undefined" ) ){ url+="?mode="+datamode; }
    
    var data = { targetNumber: json.stationNumber, heatNumber: json.heatNumber };
    var html = post( url , data , "get" );
    var hcp = html.responseText.data;
    return hcp;
}

function sleep(milliseconds)
{
  const date = Date.now();
  let currentDate = null;
  do {
    currentDate = Date.now();
  } while (currentDate - date &lt; milliseconds);
}

// PiirtÃ¤Ã¤ tulostauluun tuloksen
// Author:  Janne Mustonen
// Created: 1.7.2020
function showResults( json , target , hcp )
{    
    var timestamp;
    
    // Hae HCP-rivi, jos ei tule muuttujana
    if ( typeof( hcp ) === 'undefined' ){
        sleep( sleeptime );
        hcp = getHCP( json );
    }
    
    //targetPrev[hcp.targetNumber] = parseTimestamp( hcp.timestamp );
    
    // Hae target, jos ei tule muuttujana
    if ( target === 0 ){
        target = $( '[data-stationNumber="'+hcp.targetNumber+'"]' );
    }
    
    // LisÃ¤Ã¤ tulokset pallojen tilalle
    var text = "&lt;span class='hit'&gt;"+hcp.numberHits + "&lt;/span&gt; / &lt;span class='miss'&gt;" + hcp.numberMisses + "&lt;/span&gt; &lt;span class='time'&gt;" + hcp.heatDuration + "s&lt;/span&gt;";
    target.find( 'div.hitarea' ).hide();
    target.find( 'div.results' ).show().html( text );
    
    addResults( hcp );    
}

// LisÃ¤Ã¤ tuloksen tulostauluun (resultsboard)
function addResults( hcp )
{
    // Harjoitustilassa ei nÃ¤ytettÃ¤ tuloksia
    if ( parseInt( competition.mode ) === 0 ) {
      return;  
    }
    
    // Varmistetaan ettÃ¤ kierroksen tulosta ei ole vielÃ¤ piirretty.
    var heatNumber = parseInt( hcp.heatNumber );
    var heatresultswrapper = $( '[data-stationNumber="'+hcp.targetNumber+'"]' ).find( 'div.heatresults[data-heat="'+heatNumber+'"]' );
    if ( heatresultswrapper.length &gt; 0 ){ return; }
    
    // Laske kokonaisaikaa
    targetTimers[hcp.targetNumber]['total'] += parseFloat( hcp.heatDuration );
    
    // PÃ¤ivitÃ¤ edellinen timestamp
    targetTimers[hcp.targetNumber]['prev'] = parseTimestamp( hcp.timestamp );
    
    // LisÃ¤Ã¤ tulokset tulostauluun
    if ( $( '.results_wrapper' ).length &gt; 0 ){
        var totalTime = targetTimers[hcp.targetNumber]['total'];
        //console.log( totalTime );
        var totalTimeStr = totalTime.toFixed( 2 ).toString();
        
        //var totalTimeStr = "";
        text =  "&lt;div class='heatresults' data-heat='"+hcp.heatNumber+"'&gt;"
        text += "  &lt;div class='shootingresults'&gt;&lt;span class='hit'&gt;"+hcp.numberHits + "&lt;/span&gt; / &lt;span class='miss'&gt;" + hcp.numberMisses + "&lt;/span&gt;&lt;/div&gt;";
        text += "  &lt;div class='shootingtime'&gt;" + hcp.heatDuration + "&lt;/div&gt;";
        text += "  &lt;div class='totaltime'&gt;&lt;b&gt;" + formatStopwatch( totalTime ) + "&lt;/b&gt;&lt;/div&gt;";
        text += "&lt;/div&gt;";

        if ( targetTimers[hcp.targetNumber]['round'] == competition.rounds ){
            // Viimeinen juoksu
           text += "&lt;div class='runningtime finalrun' data-heat='"+hcp.heatNumber+"'&gt;&lt;/div&gt;"; 
        }else{
           text += "&lt;div class='runningtime' data-heat='"+hcp.heatNumber+"'&gt;&lt;/div&gt;";
        } 

        var results = $( 'table.resultBoard tr[data-stationnumber="'+hcp.targetNumber+'"]' ).find( 'div.results_wrapper' );
        results.append( text );
        
        //target.find( 'div.finalresults_wrapper' ).html( totalTime.toFixed( 2 ) +"s" );
    
        updateTotalTimeHtml( hcp.targetNumber );
        //sortResults();
    }  
}

function updateTotalTimeHtml( targetNumber )
{
    var target = $( '[data-stationNumber="'+targetNumber+'"]' );
    var totalTime = targetTimers[targetNumber]['total'];
    var totalTimeStr = totalTime.toFixed( 2 ).toString();
    totalTimeStr = totalTimeStr.replace( '.' , '' );
    target.find( 'div.finalresults_wrapper' ).html( totalTimeStr );
    
}

// Parsii 8-merkkisen timestampin millisekunneiksi
// Author:  Janne Mustonen
// Created: 1.7.2020
function parseTimestamp( timestamp )
{
     
    var hours = parseInt( timestamp.substring( 0 , 2 ) );
    var minutes = parseInt( timestamp.substring( 2 , 4 ) );
    var seconds = parseInt( timestamp.substring( 4 , 6 ) );
    var hundreds = parseInt( timestamp.substring( 6 , 8 ) );
    
    var milliseconds = hundreds * 10;
    milliseconds += seconds * 1000;
    milliseconds += minutes * 60 * 1000;
    milliseconds += hours * 3600 * 1000;
    
    return milliseconds;
}

// SiirtÃ¤Ã¤ maaliin tulleen ylempÃ¤Ã¤n resultsboardiin
function moveToResultsBoard( id )
{
    
    var table = $( '#results' );
    var tbody = table.find( 'tbody' );
    var tr = tbody.find( 'tr[data-stationNumber="'+id+'"' );
    
    var table2 = $( '#results_final' );
    table2.find( 'tbody' ).append( tr );
    table2.show();
    
    if ( $( '#results tbody tr' ).length == 0 ){ table.hide(); }
}

// KÃ¤y lÃ¤pi resultsboardin ja jÃ¤rjestÃ¤Ã¤ sijoitukset uudellee
function sortResults()
{
    // Harjoitustilassa ei jÃ¤rjestetÃ¤ jÃ¤rjestystÃ¤
    if ( parseInt( competition.mode ) === 0 ) return;
    
    //var table = $( 'table.resultBoard_final' );
    var table = $( '#results_final' );
    var tbody = table.find( 'tbody' );
    var asc = true;
    var num = 1;
    
    // Sort
    tbody.find( 'tr' ).sort( function(a, b) {
        var val1 = parseInt( $('td:last', a).text() );
        var val2 = parseInt( $('td:last', b).text() );
        if (asc) {
            if ( val1 &lt; val2 ){
                return -1;
            }else{
                return 1;
            }
            //return val1.localeCompare( val2 );
        } else {
            if ( val1 &lt; val2 ){
                return 1;
            }else{
                return -1;
            }
        }
    }).appendTo(tbody);
    
    // Rearrange numbers
    num = 1;
    tbody.find( 'tr' ).each( function( index, tr ){
        $( tr ).find( 'td:first' ).html( num );
        num++;
    });
    
}

function getData( guid , mode ){
    if ( autoget === true ){
        //ajax_getTarget( -1 );
        getTargetRows( mode );
    }
}

function getStationData( stationNumber , rownum )
{
    $.getJSON( "/view/getStationData/"+stationNumber+"/"+rownum, function( json ) {
        if ( json.status == false ){ alert( 'Ei dataa!' ); return; }
        drawStationData( json );
        //console.log( json[0] );
        //console.log( heatNumber );
        if ( heatNumber &lt; json[0].heatNumber ){
           getHeatData( stationNumber , heatNumber );
           heatNumber = json[0].heatNumber;
       }
        
    });
}

function drawStation( heatNumber )
{
    $.getJSON( "/view/getHeatData/"+stationNumber+"/"+heatNumber, function( json ) {
        if ( json.status == false ){ alert( 'Ei dataa!' ); return; }
        //drawHeatData( json );
        $( 'section.heat_wrapper').append( json.targetview );
    });
}

function drawStationData( jsondata )
{
    $.each( jsondata , function( index, data ) {
       var td;
       
       if ( data.shotHit == 1 ){
           td = "&lt;td&gt;"+data.secondsFromHeatStart+"&lt;/td&gt;"
           $('section.hitmiss_wrapper table tbody tr').append( td );
       }else{
           $('section.hitmiss_wrapper table tbody tr td:last').append( "." );
       }
       
    });
}

function getHeatData( stationNumber , heatNumber )
{
    $.getJSON( "/view/getHeatData/"+stationNumber+"/"+heatNumber, function( json ) {
        if ( json.status == false ){ alert( 'Ei dataa!' ); return; }
        //drawHeatData( json );
        $( 'section.heat_wrapper').append( json.targetview );
    });
}

function drawHeatData( jsondata )
{
    $.each( jsondata , function( index, data ) {
        
    });
};

// TODO: TÃ¤mÃ¤ on tÃ¤llÃ¤ hetkellÃ¤ tarpeeton
function getResults( guid , station ){
    
    $.getJSON( "/view/getResults/"+guid+"/"+station, function( json ) {
        if ( json.status == false ){ return; }
    });
}

/*
 * Singleview
 */

// Alustaa taulun
function initSingleView( stationNumber , milliseconds )
{
    // Alusta targetTimers-arrayhin radan tiedot
    if ( typeof( targetTimers[stationNumber] ) === "undefined" )
    {
        console.log ( "Init target: " + stationNumber );
        
        targetTimers[stationNumber] = [];        
        targetTimers[stationNumber]['seconds'] = competition.secondsPerHeat;
        targetTimers[stationNumber]['heat'] = 0;
        targetTimers[stationNumber]['round'] = 0;
        targetTimers[stationNumber]['lastHitNum'] = 0;
        targetTimers[stationNumber]['firstTimestamp'] = milliseconds;           // First competition TAP timestamp (milliseconds)
        targetTimers[stationNumber]['heatTimestamp'] = 0;                       // Current heat start timestamp (milliseconds)
        targetTimers[stationNumber]['heatElapsed'] = 0;                         // Seconds from start of current heat
        targetTimers[stationNumber]['prev'] = milliseconds;                     // Previous TAP or HCP timestamp (milliseconds)
        targetTimers[stationNumber]['total'] = 0;                               // Target total in milliseconds
        targetTimers[stationNumber]['runningtime'] = 0;                         // Target running time in milliseconds
        targetTimers[stationNumber]['shootingtime'] = 0;                         // Target shooting time in milliseconds
        targetTimers[stationNumber]['shots'] = 0;                               // Target shots
        targetTimers[stationNumber]['hits'] = 0;                                // Target hits
        targetTimers[stationNumber]['miss'] = 0;                                // Target misses
        
        // Init targetStatus array
        if ( typeof(targetStatus[stationNumber]) === 'undefined' ){
            //targetStatus[stationNumber] = { finished: false };
            targetStatus[stationNumber] = [];
        }
        
        // Hae viimeisen kierroksen HCP-viesti (jos on jo tullut)
        targetStatus[stationNumber]['hcp'] = [];                                // Target HCP's for rounds
        var hcp = getHCP( { stationNumber: stationNumber, heatNumber: competition.rounds } );
        targetStatus[stationNumber]['hcp'][competition.rounds] = hcp;
    }
}

// PiirtÃ¤Ã¤ singleview taulu-alueen
function drawTarget( data )
{
    var wrapper = $( 'section.wrapper' );
    var template = $( 'section.template' ).find( 'section.singletarget_wrapper' );
    var newTarget = template.clone();
    newTarget.attr( 'data-heat' , data.heatNumber );
    newTarget.find( 'table caption div.serie' ).html( 'Series ' + data.heatNumber );
    
    if ( competition.mode == 0 )
    {
        newTarget.prependTo( wrapper );
    }
    else
    {
        newTarget.appendTo( wrapper );
    }
    
    
    var stationNumber = data.stationNumber;
    var milliseconds = parseTimestamp( data.timestamp );
    
    initSingleView( stationNumber , milliseconds );
    
}

// PiirtÃ¤Ã¤ singleview osuman
function drawShot( data ){
    var delay, hitStatus, x, y;
    var milliseconds = parseTimestamp( data.timestamp );
    last_row = data.id;
    
    // Viimeisen kierroksen jÃ¤lkeinen laukaus, ei jatketa
    if ( data.heatNumber &gt; competition.rounds &amp;&amp; datamode !== 'training' )
    {
        console.log( "More rounds than heats!" );
        return;
    }
    
    var prevheat;
    
    if ( competition.shootingArea == 2 ){
        x = data.xC8;
        y = data.yC8;
    }else if ( competition.shootingArea == 1 ){
        x = data.xC1;
        y = data.yC1;
    }else if ( competition.shootingArea == 0 ){
        x = data.xC7;
        y = data.yC7;
    }
    
    // Init targetStatus
    initSingleView( data.stationNumber , milliseconds );
    
    // Kierroksen 1. laukaus
    if ( data.heatShotNumber == 1 ) {
        
        if ( data.heatNumber &gt; 1 &amp;&amp; parseInt( competition.mode ) &gt; 0) {
            // Kierros nro 2 tai suurempi, haetaan edellinen HCP
            prevheat = parseInt( data.heatNumber ) - 1;

            var hcp = { stationNumber: data.stationNumber, heatNumber: prevheat };
            hcp = getHCP( hcp );
            targetStatus[data.stationNumber]['hcp'][prevheat] = hcp;
            
            // Tarkasta, onko edellinen kieros pÃ¤Ã¤ttynyt timeoutiin
            if ( hcp.heatEndedType == "T" ){
                var prevtarget = $( 'section.singletarget_wrapper[data-heat="'+prevheat+'"] &gt; div.target_area' );
                drawTotalTime( prevtarget , hcp.heatDuration );
            }
            
            // Training modessa ei nÃ¤ytetÃ¤ juoksuaikoja
            
            //console.log( hcp );
            targetTimers[data.stationNumber]['total'] += parseFloat( hcp.heatDuration );
            targetTimers[data.stationNumber]['shootingtime'] += parseFloat( hcp.heatDuration );

            // LisÃ¤Ã¤ kokonaisaikaan juoksuaika laskemalla laukauksen ja edellisen HCP-viestin erotus
            var runningtime = ( milliseconds - parseTimestamp( hcp.timestamp ) ) / 1000;

            if ( runningtime &gt; 0 ) {
                targetTimers[data.stationNumber]['total'] += runningtime;
                targetTimers[data.stationNumber]['runningtime'] += runningtime;
            } else {
                runningtime = 0;
            }
            
            // LisÃ¤Ã¤ ruudulle edellisen kierroksen juoksuaika
            debuglog( 'Heat '+prevheat+' duration: ' + hcp.heatDuration );
            debuglog( 'Heat '+prevheat+' runningtime: ' + runningtime );
            $( '#singleTargetWrapper section.singletarget_wrapper:last' ).find( 'div.runningtime' ).html( formatStopwatch( runningtime ) ).show();
        }
        
        drawTarget( data );
        delay = "";
    }
    else
    {
        delay = parseFloat( data.secondsFromHeatStart - targetStatus[data.stationNumber].prev ).toFixed( 2 );
    }
    
    // PiirrÃ¤ osuma
    var coords = calcShotLocation( x , y );
    if ( data.shotHit == 1 ){
        // LisÃ¤Ã¤ osumaan satunnaisuutta arpomalla satunnaisluku vÃ¤liltÃ¤ -7 ... +7
        //var random_x = 7 - Math.floor(Math.random() * 14);
        //var random_y = 7 - Math.floor(Math.random() * 14);
        //coords['x'] += random_x;
        //coords['y'] += random_y;
        
        // Random tietokannasta
        coords['x'] += parseInt( data.xRandom );
        coords['y'] += parseInt( data.yRandom );
        //console.log( data.xRandom );
        
        hitStatus = "hit";
        targetTimers[data.stationNumber]['hits'] += 1;
        
    }else{
        hitStatus = "miss";
        targetTimers[data.stationNumber]['miss'] += 1;
    }
    
    // PiirrÃ¤ osuma tauluun
    var shot = "&lt;span class='"+hitStatus+"' style='left: "+coords['x']+"px; top: "+coords['y']+"px;' title='"+x+", "+y+"'&gt;X&lt;/span&gt;";
    var target = $( 'section.singletarget_wrapper[data-heat="'+data.heatNumber+'"] &gt; div.target_area' );
    target.find( 'div.hitarea' ).append( shot );
    
    
    // Jos osuma tulee reunamille, piirretÃ¤Ã¤n laaja-alainen alue
    if ( Math.abs( x ) &gt; 1.1 || Math.abs( y ) &gt; 1.1 ){
        target.find( 'div.hitarea span' ).last().addClass( 'wide' );
    }
    
    // LisÃ¤Ã¤ kierroksen tulostauluun osuman tiedot
    target.find( 'table.results tbody' ).append( '&lt;tr class="'+hitStatus+'"&gt;&lt;td&gt;'+data.heatShotNumber+'&lt;/td&gt;&lt;td&gt;'+ data.secondsFromHeatStart+'&lt;/td&gt;&lt;td&gt;'+delay+'&lt;/td&gt;&lt;td&gt;&lt;span class="ui-icon"&gt;&lt;/span&gt;&lt;/td&gt;' );
    target.find( 'table.results tbody tr' ).last().find( 'span.ui-icon' ).addClass( coords['dir'] ).css( 'cursor' , 'initial' );
    
    // Kierroksen viimeinen osuma
    if ( data.heatShotHit == competition.shotsPerHeat &amp;&amp; competition.mode &gt; 0 )
    {
        $( '#singleTargetWrapper section.singletarget_wrapper:last' ).find( 'div.runningtime' ).show();
        //targetTimers[data.stationNumber]['shootingtime'] += parseFloat( data.secondsFromHeatStart );
        //targetTimers[data.stationNumber]['total'] += parseFloat( data.secondsFromHeatStart );
        drawTotalTime( target , data.secondsFromHeatStart );
        //target.find( 'table.results tbody' ).append( '&lt;tr&gt;&lt;td colspan="4"&gt;'+data.secondsFromHeatStart+'s&lt;/td&gt;&lt;/tr&gt;' );
    }
    
    targetStatus[data.stationNumber].prev = data.secondsFromHeatStart;    
    targetTimers[data.stationNumber]['prev'] = milliseconds;
    targetTimers[data.stationNumber]['shots'] += 1;  

    // Kilpailun viimeinen kierros
    if ( datamode !== 'training' &amp;&amp; data.heatNumber == competition.rounds )
    {
        var lastHeatFinished = false;
        
        // TODO: Tunnista viimeisen kierroksen kesken loppunut aika.
        
        
        if ( typeof( targetStatus[data.stationNumber]['hcp'][competition.rounds] ) === 'object' &amp;&amp; data.heatShotNumber == targetStatus[data.stationNumber]['hcp'][competition.rounds].numberOfShots )
        {
            // HCP-tiedon perusteella viimeisen kierroksen viimeinen laukaus saatu
            lastHeatFinished = true;
            debuglog( 'Heat finished: Last shot found (HCP)!' );
        }
        else if ( data.heatShotHit == competition.shotsPerHeat )
        {
            // Viimeisen kierroksen viimeinen osuma saatu
            lastHeatFinished = true;
            debuglog( 'Heat finished: Last shot found (shots per heat)!' );
        }
        
        
        // Kilpailu on pÃ¤Ã¤ttynyt
        if ( lastHeatFinished === true )
        {
            var heat = parseInt( data.heatNumber ) - 1;
            //var hcp = { stationNumber: data.stationNumber, heatNumber: heat };
            //hcp = getHCP( hcp );
            hcp = targetStatus[data.stationNumber]['hcp'][competition.rounds];

            // PÃ¤ivitÃ¤ kokonaisaika, sekÃ¤ kokonais ammunta-aika
            targetTimers[hcp.targetNumber]['total'] += parseFloat( hcp.heatDuration );
            targetTimers[data.stationNumber]['shootingtime'] += parseFloat( hcp.heatDuration );
            debuglog( 'Heat duration: ' + hcp.heatDuration );

            
            // LisÃ¤Ã¤ kokonaisaikaan juoksuaika
            var runningtime;
            if ( confirmedTimers[data.stationNumber] != "" )
            {
                runningtime = confirmedTotalTimeDifference( data.stationNumber );
                //targetTimers[data.stationNumber]['runningtime'] += confirmedTotalTimeDifference( data.stationNumber );
            }
            else
            {
                runningtime = ( milliseconds - parseTimestamp( hcp.timestamp ) ) / 1000;
            }
            
            targetTimers[data.stationNumber]['total'] += runningtime;
            targetTimers[data.stationNumber]['runningtime'] += runningtime;
            
            // NÃ¤ytÃ¤ viimeinen juoksuaika
            //$( '#singleTargetWrapper' ).last( 'section.singletarget_wrapper' ).find( 'div.runningtime' ).html( formatStopwatch( runningtime ) ).show();
            

            // Onko kyseessÃ¤ viimeisellÃ¤ kierroksella kesken loppunut aika?
            if ( hcp.heatEndedType == "T" )
            {
                target = $( 'section.singletarget_wrapper[data-heat="'+competition.rounds+'"]');
                drawTotalTime( target , hcp.heatDuration );
            }

            // LisÃ¤tÃ¤Ã¤n nÃ¤kymÃ¤n perÃ¤Ã¤n kokonaissekunnit
            //confirmedTotalTimeDifference( data.stationNumber );

            var wrapper = $('#singleTargetWrapper');
            var resultswrapper = $( '#singleTargetResults' );
            
            //console.log( "Erotus: " + difference );
            
            resultswrapper.find('span[data-content="racetime"]').html( 'TBD' );
            resultswrapper.find('span[data-content="runningtime"]').html( 'TBD' );
            resultswrapper.find('span[data-content="shootingtime"]').html( formatStopwatch( targetTimers[data.stationNumber]['shootingtime'] ) );
            resultswrapper.find('span[data-content="shots"]').html( targetTimers[data.stationNumber]['shots'] );
            resultswrapper.find('span[data-content="hits"]').html( targetTimers[data.stationNumber]['hits'] );
            resultswrapper.find('span[data-content="miss"]').html( targetTimers[data.stationNumber]['miss'] );
            
            // Jos vahvistettu aika, nÃ¤ytÃ¤ vahvistettu aika, sekÃ¤ korjattu juoksuaika
            if ( confirmedTimers[data.stationNumber] != "" )
            {
                debuglog( "Vahvistettu aika: " + confirmedTimers[data.stationNumber] );
                //resultswrapper.find('span[data-content="racetime"]').html( formatStopwatch( targetTimers[data.stationNumber]['total'] ) );
                resultswrapper.find('span[data-content="racetime"]').html( confirmedTimers[data.stationNumber] );
                resultswrapper.find('span[data-content="runningtime"]').html( formatStopwatch(targetTimers[data.stationNumber]['runningtime'] ) );
                resultswrapper.find( 'span[data-content="racetime"]' ).prepend( '&lt;span class="ui-icon ui-icon-clock"&gt;&lt;/span&gt; ' );
            }
            else if ( typeof( targetStatus[data.stationNumber]['hcp'][competition.rounds] + 1 ) === 'object' )
            {
                // Ei vahvistettua aikaa, mutta kilpailun ulkopuolinen laukaus lÃ¶ytyy
                //resultswrapper.find('span[data-content="racetime"]').html( confirmedTimers[data.stationNumber] );
                //resultswrapper.find('span[data-content="runningtime"]').html( formatStopwatch(targetTimers[data.stationNumber]['runningtime'] ) );
            }
            
            $( '#singleTargetWrapper section.singletarget_wrapper:last' ).find( 'div.runningtime' ).show();
            resultswrapper.removeClass().appendTo( wrapper );
            //return;
            
        }

            
        
    }
    
}

// PiirtÃ¤Ã¤ tauluun kokonaisajan
function drawTotalTime( target , secondsFromHeatStart )
{
    target.find( 'table.results tbody' ).append( '&lt;tr&gt;&lt;td colspan="4"&gt;'+secondsFromHeatStart+'s&lt;/td&gt;&lt;/tr&gt;' );
}

// Laskee koordinaatin osumapisteelle. Origo keskellÃ¤.
function calcShotLocation( x , y )
{
    var max_pix = 230/2;
    var max_coord = 2.7;

    if ( x &gt; 1.4 ) x = 1.4;
    if ( x &lt; -1.4 ) x = -1.4;
    if ( y &gt; 1.4 ) y = 1.4;
    if ( y &lt; -1.4 ) y = -1.4;
    
    var xp = Math.abs(x) / max_coord;
    var yp = Math.abs(y) / max_coord;
    
    var coords = [];
    if ( x &lt; 0 ){
        coords['x'] = max_pix - ( max_pix * xp );
    }else{
        coords['x'] = max_pix + ( max_pix * xp );
    }
    
    if ( y &lt; 0 ){
        coords['y'] = max_pix + ( max_pix * yp );
    }else{
        coords['y'] = max_pix - ( max_pix * yp );
    }
    
    // Find dir
    if ( x == 0 &amp;&amp; y &gt; 0 ){ coords['dir'] = "ui-icon-arrowthick-1-n"; }
    if ( x &gt; 0 &amp;&amp; y &gt; 0 ){ coords['dir'] = "ui-icon-arrowthick-1-ne"; }
    if ( x &gt; 0 &amp;&amp; y == 0 ){ coords['dir'] = "ui-icon-arrowthick-1-e"; }
    if ( x &gt; 0 &amp;&amp; y &lt; 0 ){ coords['dir'] = "ui-icon-arrowthick-1-se"; }
    if ( x == 0 &amp;&amp; y &lt; 0 ){ coords['dir'] = "ui-icon-arrowthick-1-s"; }
    if ( x &lt; 0 &amp;&amp; y &lt; 0 ){ coords['dir'] = "ui-icon-arrowthick-1-sw"; }
    if ( x &lt; 0 &amp;&amp; y == 0 ){ coords['dir'] = "ui-icon-arrowthick-1-w"; }
    if ( x &lt; 0 &amp;&amp; y &gt; 0 ){ coords['dir'] = "ui-icon-arrowthick-1-nw"; }
    if ( x == 0 &amp;&amp; y == 0 ){ coords['dir'] = "ui-icon-check"; }
    
    // KeskitÃ¤
    coords['x'] -= 9;
    coords['y'] -= 9;
    
    // Jos osuma reuna-alueilla, siirretÃ¤Ã¤n, koska osuma-alue on suurempi
    if ( Math.abs(x) &gt; 1 || Math.abs(y) &gt; 1 ){
        coords['x'] -= 14;
        coords['y'] -= 10;
    }
    
    coords['x'] = parseInt( coords['x'] );
    coords['y'] = parseInt( coords['y'] );
    
    //console.log( coords );
    return coords;
}

</pre></body></html>