function setText(id, text) {
  get(id).innerHTML = text;
}

function appendText(id, text) {
  get(id).innerHTML = get(id).innerHTML + text;
}

function show(id) {
  get(id).style.display = 'block';
}

function hide(id) {
  get(id).style.display = 'none';
}

function enable(id) {
  get(id).removeAttribute('disabled');
}

function disable(id) {
  get(id).setAttribute('disabled', 'true');
}

function red(id) {
  get(id).setAttribute('style', 'color: #D22');
  get(id).style.color = '#D22';
}

function green(id) {
  get(id).setAttribute('style', 'color: #3D2');
  get(id).style.color = '#3D2';
}

function black(id) {
  get(id).setAttribute('style', '');
  get(id).style.color = 'black';
}

var animationOn = 0;
var type; 
var speedGoal; 
var accuracyGoal; 
var rhyGoal; 
var exerciseStarted = 0;
var choosingTimeLimit = 0;
var timeLimit = 0;
var actionId = 0;

// mighty globals
var action;
var topic;

function setAction(topic, action) {
  initSessionCheck();
  
  type = actions[topic][action][1];
  var text = actions[topic][action][2];
  speedGoal = actions[topic][action][3];
  accuracyGoal = actions[topic][action][4];
  rhyGoal = actions[topic][action][5];
  
  actionId = actions[topic][action][0];
  setText('debug', 'action id = ' + actionId);
  
  setText('topic', topics[topic][2] + '. ' + topics[topic][1]);
  
  // hide everything
  hide('time_entry');
  hide('sample');
  hide('sample2');
  setSample('');
  setSample2('');
  hide('keyboard');
  hide('numeric_keyboard');
  hide('info');
  hide('results');
  hide('message');
  hide('game');
  hide('textbox');

  hide('contents_button');
  show('exit_button');

  exerciseStarted = 0;
  showingResults = 0;
  timeLimit = 0;

  // show html content
  if (type == 1) {
    black('info');
    setText('info', text);
    show('info');
    registerStart(1); 
    enable('next_button');
  }

  // animates main keyboard keys
  if (type == 2) {
    show('keyboard');
    var parts = text.split('\n');
    var lastIdx = parts.length - 1;
    
    animationOn = '' + action + '-' + topic;
    animateKeys(parts[lastIdx], 0);
    parts[lastIdx] = '';
    black('info');
    setText('info', parts.join('<br />'));
    show('info');
    registerStart(1);
    enable('next_button');
  }

  //  3 -text input with keyboard, one row, 5 - with time control
  if (type == 3) {
    show('info');
    
    var parts = text.split('\n');
    setText('info', parts[0]);
    black('info');

    // this flag signals that there were too many errors, try again
    if (thisIsRetry) {
      setText('info', 'Pārāk daudz kļūdu. Lūdzu, izpildiet vēlreiz!');
      red('info');    
    }

    setSample(parts[1]);
    
    indexInSample = 0;
    show('sample');
    show('keyboard');
    disable('next_button');
    get('next_button').blur();
    blinkCursor();
    
    startRhyMeasurements();
    startErrorCollection();
    registerStart();
    
    exerciseStarted = 1;  
  }

  // 4 - text input without keyboard, multiple rows, 6 - with time control
  if (type == 4 || type == 5) {
    setSample(text);
    indexInSample = 0;
    show('sample');
    disable('next_button');
    get('next_button').blur();
    blinkCursor();
    
    startRhyMeasurements();
    startErrorCollection();
    registerStart();
    
      
    exerciseStarted = 1;  
    if (type == 5)
      startClock();  
  }

  if (type == 6) {
    setSample2(text);
    indexInSample = 0;
    show('time_entry');
    
    hide('next_button');
    show('start_button');
    
    choosingTimeLimit = 1;
    startErrorCollection();
    registerStart();
    
    exerciseStarted = 0;  
    get('time_limit').focus();
  }


  // animate keys on numeric keyboard
  if (type == 7) {
    show('numeric_keyboard');
    var parts = text.split('\n');
    var lastIdx = parts.length - 1;
    
    animationOn = '' + action + '-' + topic;
    animateKeys(parts[lastIdx], 0);
    parts[lastIdx] = '';
    black('info');
    setText('info', parts.join('<br />'));
    show('info');
    registerStart(1);
    enable('next_button');
  }

  // text input on numeric keyboard
  if (type == 8) {
    show('info');
    
    var parts = text.split('\n');
    setText('info', parts[0]);
    setSample(parts[1]);
    
    indexInSample = 0;
    show('sample');
    show('numeric_keyboard');
    disable('next_button');
    get('next_button').blur();
    blinkCursor();
    
    startRhyMeasurements();
    startErrorCollection();
    registerStart();
    
    exerciseStarted = 1;  
  }
  
  // keybard with free text entry
  if (type == 9) {
    show('info');
    
    var parts = text.split('\n');
    setText('info', parts[0]);
    setSample(parts[1]);

    indexInSample = 0;
    show('sample');
    show('keyboard');
    get('next_button').blur();
    registerStart();
    blinkCursor();
    exerciseStarted = 1;  
  }

  if (type == 13) {
    show('game');
    
    startRhyMeasurements();
    startErrorCollection();
    registerStart();
    exerciseStarted = 1;  
    
    initGameScroll(text);
  }

  if (type == 14) {
    show('textbox');
    
    startRhyMeasurements();
    registerStart();
    exerciseStarted = 1;  
    
  }
  
  thisIsRetry = 0;
  scrollTop();
  loadPointers();
}

function startSpeedExercise() {
  timeLimit = get('time_limit').value;
  hide('time_entry');
  hide('start_button');
  show('next_button'); 
  disable('next_button');

  show('sample2');  
  blinkCursor();
  
  startMeasurements();
  startRhyMeasurements();
  
  disable('next_button');
  get('next_button').blur();
  scrollTop();
  exerciseStarted = 1;  
  startClock();
}

function setContents() { 
  action = -1; // so first next_button press works as expected
  topic = 0;

  exerciseStarted = 0;
  choosingTimeLimit = 0;

  show('info');
  var text = '<h1>Stundas plāns</h1>';
  var js;
  var mark;
  text += '<ol id="contents">';
  for (var i = 0, t; t = topics[i]; i++) {
    js = 'topic = ' + i + '; action = 0; setAction(topic, action);';
    mark = 'na';
    if (topicStats[topics[i][0]] == 1) mark = 'complete';
    if (topicStats[topics[i][0]] == -1) mark = 'incomplete';
    
    text += '<li class="' + mark + '"><a href="javascript:' + js + '">' + t[1] + '</a></li>';
  }
  text += '</ol>';
  
  black('info');
  setText('info', text);    
  hide('time_entry');
  hide('message');
  hide('sample');
  hide('sample2');
  hide('keyboard');
  hide('numeric_keyboard');
  hide('results');
  hide('start_button');
  hide('game');
  hide('textbox');
  
  setText('topic', '&nbsp;');
  
  enable('next_button');
  
  show('contents_button');
  hide('exit_button');
}

var summaryIsSet = 0;
function setSummary() {
  // user wants summary
  // but we should check, if all topics are complete, and if not,
  // jump back to contents!
  var ok = 1;
  var t;
  for (t in topicStats)
    if (topicStats[t] == -1) 
      ok = 0;
  
  if (!ok) {
    setContents();
    return true;
  }
  
  hide('info');
  var text = '<h1>Nodarbība pabeigta!</h1>';
  text += '<p>Apsveicam!</p>';
  
  setText('results', text);    
  show('results');
  hide('message');
  hide('game');
  hide('textbox');

  
  hide('sample');
  hide('sample2');
  hide('keyboard');
  hide('exit_button');

  enable('next_button');
  
  summaryIsSet = 1;
}

function setExpiredSession() {
  // session expired, inform user
  
  hide('info');
  var text = '<h1>Sesija pārtraukta</h1>';
  text += '<p>Lūdzu atgriezieties sākuma lapā un pieslēdzieties sistēmai vēlreiz!</p>';
  
  setText('results', text);    
  show('results');
  hide('message');
  hide('game');
  hide('textbox');

  
  hide('info');
  
  hide('sample');
  hide('sample2');
  hide('keyboard');
  hide('numeric_keyboard');
  hide('exit_button');

  enable('next_button');
  
  summaryIsSet = 1;
  // break action and topic!
  action = -2;
  topic = -2;
}



function setSample(text) {
  setText('sample', text);
}

function setSample2(text) {
  setText('sample2', text);
}



var keyCodeTargets = new Array;
var phases = new Array;

function init() {
  setContents();
  if (navigator.appName == 'Netscape') {
    window.onkeypress = trapKeys;
    window.onkeydown = modifierDown;
    window.onkeyup = modifierUp;
  } 

  var target;
  for (var i = 0; i <= 97; i++) {
    target = get('k' + i);
    if (target) keyCodeTargets[i] = target;
  }
    
}

// if true, nextAction will not advance, but will reset this flag to false
var retryThisAction = 0; 
var thisIsRetry = 0; 
function nextAction() {
  if (choosingTimeLimit) {
    choosingTimeLimit = 0;
    startSpeedExercise();
    return true;
  }

  if (retryThisAction) {
    retryThisAction = 0;
    thisIsRetry = 1;
  } else {
    action += 1;
    animationOn = 0;
  
    if (!actions[topic] || !actions[topic][action]) {
      action = 0;
      // we've just completed a topic?
      if (topics[topic] && topicStats[topics[topic][0]])
        topicStats[topics[topic][0]] = 1;
      topic += 1;
    }
  }

  if (actions[topic]) {
    setAction(topic, action);
  } else {
    // we're through!
    if (!summaryIsSet)
      setSummary();
    else 
      goToContents();
  }
  
  return true;
}

function fadeOut(c) {
  var color;
  var phase = phases[c];
  if (phase == 4) color = "#beff9b";
  if (phase == 3) color = "#d0ffb7";
  if (phase == 2) color = "#e7ffda";
  if (phase == 1) color = "";
  
  if (keyCodeTargets[c])
    keyCodeTargets[c].style.backgroundColor = color;
  if (keyCodeTargets[c + 'b'])
    keyCodeTargets[c + 'b'].style.backgroundColor = color;
    
  phase = phase - 1;
  phases[c] = phase;
  if (phase > 0)
    setTimeout('fadeOut(' + c + ')', fadeout_speed);
}

function lightOn(c) {
  // windows alt+ctrl weirdness workaround
  if ((c == 56 || c == 57) && lastKeyDownCode == 18)
    return true;
  
  phases[c] = 4;
  if (keyCodeTargets[c])
    keyCodeTargets[c].style.backgroundColor = "#beff9b";
  if (keyCodeTargets[c + 'b'])
    keyCodeTargets[c + 'b'].style.backgroundColor = "#beff9b";

  if (c != 16 && c != 49) // leave shifts and caps alone
    setTimeout('emergencyLightOff(' + c + ')', 4000);
  return true;
}

function emergencyLightOff(c) {
  if (phases[c]) fadeOut(c);
  
}

function lightOff(c) {
  phases[c] = 0;
  if (keyCodeTargets[c])
    keyCodeTargets[c].style.backgroundColor = "";
  if (keyCodeTargets[c + 'b'])
    keyCodeTargets[c + 'b'].style.backgroundColor = "";
}

function initFadeOut(c) { 
  var launchFader = !phases[c];
  phases[c] = 4;
  if (launchFader)
    setTimeout('fadeOut(' + c + ')', 1);
}

var indexInSample = 0;
var cnt = 0;

function forgeFirstTab(ee) {
    
  var e = new Object;
  e.keyCode = 9;
  e.shiftKey = 0;
  trapKeys(e);
}

function forgeTabPress(color) {
  var e = new Object;
  e.forged = 1;
  e.forgedColor = color;
  e.keyCode = 9;
  e.shiftKey = 0;
  trapKeys(e);
}

function trapKeys(e) {
  if (type == 14)
    return true;

  if (!e) e = window.event;
  var c = e.keyCode;
  if (!c) c = e.charCode; 

  // with no exercise running, enter and space
  // translates to "next slide please"
  // and others are ignored...
  if (!exerciseStarted) {
    if ((c == 13 || c == 32) && !showingResults)
      nextAction();
    
    return true;
  }

  // with action type = 9, enter translates to 
  // "next slide please"
  if (type == 9 && c == 13) {
    registerStop();
    nextAction();
    return true;
  }
  
  if (type == 13) {
    gameKeyPress(c);
    return true;
  }
  
  // no code, no keypress FIXME
  // 191 is upside down question mark
  //if (!c || c == 16 || c == 17 || c == 18 || c == 20 || c == 191 || c == 27) return false;
  if (!c || c == 16 || c == 17 || c == 18 || c == 20 || c == 191 || c == 27 || c == 8) return false;
  
  // backspace
  /* if (c == 8) {
    if (indexInSample == 0) return false;

    // delete caret
    var ch = ccget(indexInSample);
    if (!ch) return false;
    ch.style.background = '';

    indexInSample -= 1;    

    // delete the sample char
    var ch = ccget(indexInSample);
    if (!ch) return false;
    ch.style.background = 'url(img/dot.gif) bottom repeat-x';
    ch.innerHTML = '';

    var sch = cget(indexInSample);
    if (!sch) return false;
    sch.style.backgroundColor = "";
    

    
    return false;
  } */
  
  var mapping = String.fromCharCode(c);
  var mapping2 = "--- nothing ---";
  var mapping3 = "--- nothing ---";
  var mapping4 = "--- nothing ---";
  
  if (keyMapping[mapping]) 
    initFadeOut(keyMapping[mapping]);
    
  if (c == 13) {
    initFadeOut(50);
    initFadeOut(97);
  }
  
  
  if (mapping == ' ') {
    mapping = '&nbsp;';
    mapping2 = String.fromCharCode(160);
  }

  if (c == 13) {
    mapping =  '<span class="enter">&nbsp;</span>';
    mapping2 = '<SPAN class=enter>&nbsp;</SPAN>';
  }
  
  if (c == 9)  {
    mapping =  '<span class="tabkey">&nbsp;</span>'; 
    mapping2 = '<SPAN class=tabkey>&nbsp;</SPAN>'; 
    mapping3 = '<span class="tabkey-line">&nbsp;</span>'; 
    mapping4 = '<SPAN class=tabkey-line>&nbsp;</SPAN>'; 
  }

  if (mapping == '&') {
    mapping2 = '&amp;';
  }


  if (mapping == '<') {
    mapping2 = '&lt;';
  }

  if (mapping == '>') {
    mapping2 = '&gt;';
  }

  if (!mapping) return false;
  
  // type=9 - free input, no sample
  if (type != 9) {  
    var sch = cget(indexInSample);
    if (!sch) return false;
    sch.style.backgroundColor = "#EEE";
    
    
  }


  // remove caret
  var ch = ccget(indexInSample);
  if (!ch) return false;
  ch.style.background = '';
  
  var color;
  var correct;
  var ih = '';
  if (type == 9) 
    color = "#EEE";
  else {
    ih = sch.innerHTML;
    correct = ih == mapping || ih == mapping2 || ih == mapping3 || ih == mapping4;
    registerKeyPress(correct);
    registerRhyKeyPress();
    if (!correct && errorReportingOn) {
      var correctMapping = sch.innerHTML;
      
      if (mapping && correctMapping)
        registerError(correctMapping, mapping, getSurroundings(indexInSample));
      
    }
    color = correct ? "#beff9b" : "#FAA";
    if (c == 9) { // tab
      var pos = 100000 - sch.getAttribute('iir')
      if (e.forged) {
        color = e.forgedColor;
      } else {
        // forge another 4 keypresses
        var cnt = pos % 5;
        cnt = cnt ? cnt : 5;
        for (var i = 1; i < cnt; i++)
        setTimeout('forgeTabPress("' + color + '")', 5 + i);
      }
      mapping = (pos - 1) % 5 ? '<span class="tabkey-line">&nbsp;</span>' : '<span class="tabkey">&nbsp;</span>';
    }  
  }
  
  
  ch.style.backgroundColor = color;
  ch.innerHTML = mapping;

  var nch = ccget((indexInSample * 1 + 1) );
  if (!nch) {
    // here we realise this is the end of exercise
    // actions to be taken:
    exerciseStarted = 0;
    
      
    // register a finished action and in case of speed tests,
    // show summary page
    if (type == 3 || type == 4 || type == 5 || type == 6 || type == 8 || type == 9)
      registerStop( type == 6 || type == 5);
      
    if (type == 6) 
      updateClock(); // i.e. hide it
    
    if (errorReportingOn)
      stopErrorCollection();

    if (!exerciseStarted) // if registerStop->nextAction->setAction didnt come first..
    if ((type == 3 || type == 5 || type == 4 || type == 8) && c == 13) {
      // this is text exercise, we are at the end,
      // user pressed enter and expects to
      // jump to next screen
      // 
      // so, let's throw another enter event so that happens
      trapKeys(e);
      return true;
    }
      
    return true;
  }
  
  nch.style.background = 'url(img/dot.gif) bottom repeat-x';
  
  indexInSample += 1;

  // enter may mean we need to scroll the box
  if (indexInSample % 50 == 0 || c == 13 || sch.innerHTML.indexOf('enter') > 0)
    scroll();
    
  return false;
}  

function getSurroundings(index) {
  function normalChar(s) {
    if (s.substr(0, 5) == '<span' || s.substr(0, 5) == '<SPAN')
      return '&nbsp;';
    
    return s;
  }
  
  var ch1 = '_';
  var ch2 = '_';
  var ch3 = '_';
  
  var el = cget(index * 1 - 1);
  if (el) ch1 = normalChar(el.innerHTML);
  
  var el = cget(index * 1);
  if (el) ch2 = normalChar(el.innerHTML);
  
  var el = cget(index * 1 + 1);
  if (el) ch3 = normalChar(el.innerHTML);
  
  return ch1 + ch2 + ch3;
}

function scrollTop() {
  get('sample2').scrollTop = 0;
}

function scroll() {
  var el = get('cc' + indexInSample);
  if (!el) return false;
  var ofs = el.parentNode.getAttribute('offs');
  ofs = (ofs - 1) * 85;
  
  var max = get('sample2').scrollHeight;
  if (ofs > ofs) ofs = max;
  
  get('sample2').scrollTop = ofs;
  
  return true;  
}


function blinkCursor() {
  var ch = get('cc0');
  
  if (!ch) return false;
  
  ch.style.background = 'url(img/dot.gif) bottom repeat-x';
  return true;
}

var lastKeyDownCode;
function modifierDown(e) {
  // checkCapsState(e);
  if (type == 14)
    return true;

  if (!e) e = window.event;
  var c = e.keyCode;
  if (c == 8) {
    e.returnValue = false;
  }
  
  
  if (c == 16) {
    lightOn(51);
    lightOn(52);
  }
  
  if (c == 17) {
    setTimeout('lightOn(56)', 1);
    setTimeout('lightOn(57)', 1);
  }
  
  if (c == 18) {
    lightOn(53);
    lightOn(54);
  }
  
  lastKeyDownCode = c;
  return false;
}

function modifierUp(e) {
  if (type == 14)
    return true;

  if (!e) e = window.event;
  e.cancelBubble = true;

  var c = e.keyCode;
  
  if (c == 16) {
    lightOff(51);
    lightOff(52);
  }
  
  if (c == 17) {
    lightOff(56);
    lightOff(57);
  }
  
  if (c == 18) {
    lightOff(53);
    lightOff(54);
  }
  
  return false;
}

function animateKeys(a, idx, fingerprint) {
  if (!fingerprint) fingerprint = '' + action + '-' + topic;
  
  if (animationOn != fingerprint) return false;
  var codes = a.split(' ');
  var nextIdx = (idx + 1) % codes.length;
  initFadeOut(codes[idx]);
  setTimeout('animateKeys("' + a + '", ' + nextIdx + ',"' + fingerprint +'")', 1000);
  return true;
}

var capsState = 0;
var upperCase = 'AĀBCČDEĒFGĢHIĪJKĶLĻMNŅOPQRSŠTUŪVZŽXYZ';
var lowerCase = 'aābcčdeēfgģhiījkļlļmnņopqrsštuūvzšxyz';
function checkCapsState(e) {
  if (!e) e = window.event;
  var c = e.keyCode;
  if (!c) c = e.charCode; 

  if (c == 20) { 
    capsState = capsState ? 0 : 1;
    updateCapsState();
    return true;
  }
  
  var charIsLowerCase = lowerCase.indexOf(String.fromCharCode(c)) == -1 ? 0 : 1;
  var charIsUpperCase = upperCase.indexOf(String.fromCharCode(c)) == -1 ? 0 : 1;
  
  var newCapsState = -1;
  if ((e.shiftKey && charIsLowerCase) || (!e.shiftKey && charIsUpperCase))
    newCapsState = 1;
    
  if ((e.shiftKey && charIsUpperCase) || (!e.shiftKey && charIsLowerCase))
    newCapsState = 0;
  
  if ((newCapsState != -1) && (capsState != newCapsState)) {
    capsState = newCapsState;
    updateCapsState();
  }  
  
  return true;
}


function updateCapsState() {
  if (capsState) 
    lightOn(49)
  else 
    lightOff(49);
}


// performance measurements, logging

var startTime = 0;
var ignoreTime = 0;
var stopTime = 0;
var charsEntered = 0;
var errors = 0;

function startMeasurements() {
  startTime = (new Date()).getTime();
  ignoreTime = 0;
  charsEntered = 0;
  errors = 0;
  
}

var access_log_event_id = 0;
function registerStart(immediateFinish) {
  
  startMeasurements(); // resets time counters
  
  var query = 'action_id=' + actionId;
  if (immediateFinish)
    query += '&finish=' + startTime;
  
  var xmlhttp = getTransport();
  xmlhttp.open("GET", "register_start.php?" + query, true);
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4) {
      if (xmlhttp.responseText == 0) 
        alert('Neizdevās saglabāt informāciju par uzdevuma izpildes uzsākšanu!');
      else 
        access_log_event_id = xmlhttp.responseText;    
    } 
  }
  xmlhttp.send(null)
}

function registerKeyPress(isCorrect) {
  charsEntered += 1;
  if (!isCorrect) 
    errors += 1;
}

var showingResults = 0;

function registerStop(showSummary) {
  
  if (charsEntered > 0 && errors > 0 && ((type == 3 && errors > short_exercise_max_errors) || errors / charsEntered > long_exercise_max_errors)) {
    var text = 'Pārāk daudz kļūdu. Lūdzu, izpildiet vēlreiz!';
    
    setText('message', text);
    hide('sample');
    hide('sample2');
    hide('info');
    hide('game');
    hide('textbox');

    show('message');
    
    // start over!
    retryThisAction = 1;
    
    if (type == 3)
      nextAction();
    else
      enable('next_button');

    return false;
  }
  
  // it was not awfully bad, allow next button
  enable('next_button');
  
  function formatFloat(f) {
    // we need this because parseInt(x * 100) / 100
    // sometimes gives results like 2.0100000000002
    
    var intPart = parseInt(f);
    var floatPart = parseInt(f * 10).toString();
    floatPart = floatPart.substr(floatPart.length - 1,1)
    return intPart + '.' + floatPart;
  }
  
  function speed(charCount, errorCount, time) {
    return (charCount / (time / 1000)) * 60  
  }

  function adjSpeed(charCount, errorCount, time) {
    
  }

  
  stopTime = (new Date()).getTime();
  var time = (stopTime - startTime - ignoreTime) / 1000; // now we got seconds
  if (!time) throw 'time = 0, this is too fast!';
  var cps = charsEntered / time;
  var cpm = formatFloat(cps * 60);
  var wpm = formatFloat(60 * cps / 5); // on average, 5 chars per word
  var adjSpeed = wpm - errors / 3;
  if (adjSpeed < 0) adjSpeed = 0;
  adjSpeed = formatFloat(adjSpeed);
  
  if (charsEntered > 0)
    var accuracy = formatFloat(100 * (charsEntered - errors) / charsEntered);
  else
    var accuracy = 0;
    
  var rhy = formatFloat(getRhyScore());
  
  if (showSummary) {
    wpmString = wpm + ' ' + ((parseInt(wpm) == 1) ? 'vārds' : 'vārdi') + ' minūtē';
    cpmString = cpm + ' ' + ((parseInt(cpm) == 1) ? 'rakstīme' : 'rakstzīmes') + ' minūtē';
    errorsString = errors + ' ' + ((errors == 1) ? 'kļūda' : 'kļūdas');
    adjSpeedString = adjSpeed + ' ' + ((parseInt(adjSpeed) % 10 == 1) ? 'vārdi' : 'vārdi') + ' minūtē';
    
    setText('res_time', formatTime(stopTime - startTime - ignoreTime));
    setText('res_accuracy', accuracy + '%');
    setText('res_speed', wpmString + '<br />' + cpmString);
    setText('res_errors', errorsString);
    setText('res_adj_speed', adjSpeedString);
    setText('res_rhy', rhy + '%');
    
    setText('res_speed_goal', speedGoal ? '(Mērķis: ' + speedGoal + ' vārdi minūtē)' : '');
    setText('res_accuracy_goal', accuracyGoal ? '(Mērķis: ' + accuracyGoal + '%)' : '');
    setText('res_rhy_goal', rhyGoal && speedGoal ? '(Mērķis: ' + rhyGoal + '%)' : '');
    
    black('res_speed_row');
    black('res_time_row');
    black('res_rhy_row');
    errors ? black('res_accuracy_row') : green('res_accuracy_row');
    errors ? black('res_errors_row') : green('res_errors_row');
    
    if (accuracy < accuracyGoal) {
      red('res_accuracy_row');
      retryThisAction = true;
    }

    if (speedGoal && rhyGoal && rhy > rhyGoal) {
      red('res_rhy_row');
      retryThisAction = true;
    }
 
    if (wpm < speedGoal) {
      red('res_speed_row');
      red('res_time_row');
      retryThisAction = true;
    }
    
    hide('sample');
    hide('sample2');
    hide('keyboard');
    hide('info');
    hide('game');
    hide('textbox');

    show('results');


    showingResults = 1;
    setTimeout('showingResults = 0;', pause_after_summary * 1000);
    
    setText('res_title', 'Uzdevums izpildīts!');
    if (retryThisAction) {
      setText('res_title', 'Uzdevums <b>nav</b> izpildīts!');
      return true;
    }
    
  }
  
  // register the result
  if (charsEntered > 0)
    var errPercentage = parseInt((errors / charsEntered) * 100);
  else
    var errPercentage = 0;
    
  var query = 'speed=' + wpm + '&errors=' + errPercentage 
    + '&event_id=' + access_log_event_id + '&rhy=' + rhy 
    + '&char_count=' + charsEntered + '&error_count=' + errors
    + '&ignore_time=' + parseInt(ignoreTime / 1000);
    
  var xmlhttp = getTransport();
  xmlhttp.open("GET", "register_stop.php?" + query, true);
  xmlhttp.onreadystatechange=function() {
    if (xmlhttp.readyState==4) {
     if (xmlhttp.responseText != 1) 
      appendText('results', '<br />Rezultātu neizdevās saglabāt.');
    }
  }
  xmlhttp.send(null)

  return true;
}

// rhythm stuff

var lastKeyTime;
var rhy1, rhy2, rhy3, rhy4;
var scores, scoreCount;

function startRhyMeasurements() {
  lastKeyTime = (new Date()).getTime();
  
  scores = 0;
  scoreCount = 0;
  
  rhy1 = -1;
  rhy2 = -1;
  rhy3 = -1;
  rhy4 = -1;
}

function registerRhyKeyPress() {
  var time = (new Date()).getTime();
  var interval = time - lastKeyTime;
  if (!interval) return false;
  if (interval > 5000)
    ignoreTime += interval - 5000; // you still get 5 sec penalty for pausing!

  lastKeyTime = time;
  
  
  if (rhy4 != -1) {
    avgInterval = (rhy1 + rhy2 + rhy3 +rhy4) / 4;
    
    var delta = avgInterval - interval;
    if (delta < 0) delta *= -1;
    
    scores += delta / interval;
    scoreCount += 1;    
  }

  rhy4 = rhy3;
  rhy3 = rhy2;
  rhy2 = rhy1;
  rhy1 = interval;
  
  return true;
}

function getRhyScore() {
  if (!scoreCount) return 0;
  
  var percentage = (scores / scoreCount) * 100;
  return percentage;
}

// error stuff

var errorLog;
var errorReportingOn = 0;
function startErrorCollection() {
  errorReportingOn = 1;
  errorLog = '';
}

function registerError(correct, pressed, surroundings) {
  if (!pressed) pressed = 0;
  if (errorLog) errorLog += ',';
  errorLog += correct + '-' + pressed + '-' + surroundings;
}

function stopErrorCollection() {
  errorReportingOn = 0;
  if (errorLog)
    registerErrors(actionId, errorLog);
}

function registerErrors(actionId, errors) {
  var query = 'action_id=' + actionId + '&errors=' + encodeURIComponent(errors);
  
  var xmlhttp = getTransport();
  xmlhttp.open("GET", "register_errors.php?" + query, true);
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState==4) {
     if (xmlhttp.responseText != 1) 
      alert('Neizdevās saglabāt informāciju par kļūdām!');
    }
  }
  xmlhttp.send(null)
}


function goToContents() {
  window.location = 'contents.php';
}


function startClock() {
  updateClock();
  show('clock');
  return true;
}

function formatTime(millis) {
  var time = parseInt(millis) / 1000; 
  var minutes = '' + parseInt(time / 60);
  var seconds = '' + parseInt(time % 60);
  if (minutes.length < 2) 
    minutes = '0' + minutes;
  if (seconds.length < 2)
    seconds = '0' + seconds;

  return minutes + ':' + seconds;
}

function updateClock() {
  if (exerciseStarted == 0) {
    hide('clock');
    return true;
  }

  var time = (new Date()).getTime() - startTime - ignoreTime;  
  var now = (new Date()).getTime();
  var interval = now - lastKeyTime;
  
  // we reached time limit! excellent!
  if (timeLimit && (time > timeLimit * 1000 * 60)) {
    registerStop(true);
    enable('next_button');
    exerciseStarted = 0;
  }  
  
  // update clock only if user is currently typing  
  if (interval <= 5000)  
    setText('clock_value', formatTime(time));
  
  setTimeout('updateClock()', 1000);
}

function initSessionCheck() {
  var xmlhttp = getTransport();
  xmlhttp.open("GET", "check_session.php?id=" + lesson_id, true);
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState==4) {
     if (xmlhttp.responseText != 1) 
      setExpiredSession();
    }
  }
  xmlhttp.send(null)
}

var cPointers;
function loadPointers() {
  cPointers = new Array();
  var tdArr = get('sample2').getElementsByTagName('td');
  for (var i = 0, td; td = tdArr[i]; i++)
    cPointers[td.getAttribute('id')] = td;

  var tdArr = get('sample').getElementsByTagName('td');
  for (var i = 0, td; td = tdArr[i]; i++)
    cPointers[td.getAttribute('id')] = td;

}

function cget(idx) {
  return cPointers['c' + idx];
}

function ccget(idx) {
  return cPointers['cc' + idx];
}


var gameScrollText = '';
var gameScrollIdx = 0;
var gameScrollMaxIdx = 0;
var gameCaretIdx = 0;
var gameTiles = new Array();
var gameTileCells = new Array();
var scrollDelay = 1000;

function initGameScroll(text) {
  gameScrollText = text;
  gameScrollIdx = 0;
  gameCaretIdx = 0;
  gameScrollMaxIdx = text.length;
  gameScrollText += '                                                  ';
  scrollDelay = 1000 / (speedGoal / 12);

  gameTiles = new Array();
  for (var i = 0; i < 50; i++) {
    var el = get('g' + i);
    el.innerHTML = '';
    var tn = document.createTextNode("");
    el.appendChild(tn);
    gameTiles[i] = tn;
    gameTileCells[i] = el;
    gameTileCells[i].style.backgroundColor = '';
  }
    
  gameScroll();
}


function gameScroll() {
  if (!exerciseStarted) return false;

  for (var i = 0; i < 49; i++) {
    gameTiles[i].nodeValue = gameTiles[i + 1].nodeValue;

    gameTileCells[i].style.backgroundColor = gameTileCells[i + 1].style.backgroundColor;
  }
  
  var ch = gameScrollText.substr(gameScrollIdx, 1);
  gameTiles[49].nodeValue = ch;
  gameTileCells[49].style.backgroundColor = '';
  
  
  
  if (gameScrollIdx + 1 < gameScrollMaxIdx + 50 && gameCaretIdx < gameScrollMaxIdx  && gameScrollIdx - gameCaretIdx < 48) {
    gameScrollIdx += 1;
    
    // adjust speed... 
    var adjDelay = scrollDelay;
    var pos = 25 - (gameScrollIdx - gameCaretIdx);
    
    if (pos < 0)
      adjDelay = adjDelay + ((pos / -25) * adjDelay);
    
    if (pos > 0)
      adjDelay = adjDelay - ((pos / 25) * adjDelay * 1.1)
      if (adjDelay < 1) adjDelay = 1;
    
    setTimeout('gameScroll()', adjDelay);
  } else {
    // finnish !
    
    exerciseStarted = 0;
    registerStop( 1 );
       
    if (errorReportingOn)
      stopErrorCollection();    
    
  }
}

function gameKeyPress(c) {
  var screenCaretIdx = 50 - (gameScrollIdx - gameCaretIdx);
  if (screenCaretIdx > 49) return true; // too fast! 

  var mapping = String.fromCharCode(c);
  var mapping2 = "--- nothing ---";
  var mapping3 = "--- nothing ---";
  var mapping4 = "--- nothing ---";
  
  if (mapping == ' ') {
    mapping2 = '&nbsp;';
    mapping3 = String.fromCharCode(160);
    mapping4 = '';
  }

  if (c == 13) {
    mapping =  '<span class="enter">&nbsp;</span>';
    mapping2 = '<SPAN class=enter>&nbsp;</SPAN>';
  }
  
  if (c == 9)  {
    mapping =  '<span class="tabkey">&nbsp;</span>'; 
    mapping2 = '<SPAN class=tabkey>&nbsp;</SPAN>'; 
    mapping3 = '<span class="tabkey-line">&nbsp;</span>'; 
    mapping4 = '<SPAN class=tabkey-line>&nbsp;</SPAN>'; 
  }

  if (mapping == '&') {
    mapping2 = '&amp;';
  }


  if (mapping == '<') {
    mapping2 = '&lt;';
  }

  if (mapping == '>') {
    mapping2 = '&gt;';
  }

  
  var realCh = gameTiles[screenCaretIdx].nodeValue;
  
  var correct = mapping == realCh || mapping2 == realCh || mapping3 == realCh || mapping4 == realCh;
  
  registerKeyPress(correct);
  registerRhyKeyPress();

  color = correct ? "#beff9b" : "#FAA";
  
  gameTileCells[screenCaretIdx].style.backgroundColor = color;

  gameCaretIdx += 1;  
}


