// Wall of DINGBATS(R)
// by Tom Gidden <gid@dingbats.net>
// Copyright (C) 2009, Starberry Ltd.
//
// All content, including puzzle designs are Copyright (C) 1980, 2009, Vista
// Publishing, and Starberry Ltd. DINGBATS(R), KINGBATS(TM), THINGBATS(TM), and
// all associated logos and logotypes are trade marks and/or registered
// trade marks of Paul Sellers, used with permission.

// Note, this Javascript is a hack job.  Sorry.


// Visual spacing of cards
var card_spacing_x = 51;
var card_spacing_y = 35;

// Visual size of card
var card_size_x = 48, card_size_x2 = card_size_x/2;
var card_size_y = 32, card_size_y2 = card_size_y/2;

// Spacing of cards in the image atlas
var atlas_spacing_x = 52;
var atlas_spacing_y = 36;

// Size of entire image atlas
var atlas_size_x = 776;
var atlas_size_y = 428;

// Note, the data is limited on the server to intervals of this length, so
// there's no point in trying to speed it up!  Sorry.
var ajax_cycle_time = 600;

// This is just to store the coordinates and image object of each
// cardCode.  This could probably be done better, but...
var card_hash = {};

// There are N sound effect objects loaded, so we can have flurries of
// tings.  This is done by shifting a ting off the front of the ting
// queue, pushing it to the back (rotate the queue, effectively), and
// playing it.  This should allow up to N simultaneous tings.
var simultaneous_tings = 32;
var tings = [];
var ting_delay = 50; // msec

// Fix to prevent multiple events at once.
var event_stagger = 120; // msec

// Internal flag for whether sound is on or not.  Set on page load.
var soundOn;
var soundLoaded = 0;


function pulse_card(cardCode, st, color)
// Trigger a card pulse.  To sync the sound, we start the sound playing
// first, and then trigger a call to a subsequent function for the actual
// work.
{
  if(soundOn) sound_playting();

  setTimeout("pulse_card2('"+cardCode+"','"+st+"','"+color+"')", ting_delay);
}

function closure_pulse_card(cardCode, st, color)
// Construct a closure for pulse_card... this is for setTimeout.  I can't
// remember all the stuff about closures in JS at the moment.
{
  return (function () {
	  pulse_card(cardCode, st, color);
	});
}


// (R) symbol
var regd = String.fromCharCode(174);

function pulse_card2(cardCode, st, color)
// Perform a card pulse.
{
  // Set the window status and the page title to the status text previously set.
  if(st) {
	window.status = st;
	document.title = 'Wall of DINGBATS'+regd+': '+st;
  }

  // Expansion factor: this means the card will grow by 60%
  var t = 0.6;

  // Get the card's metrics and objects
  var card_data = card_hash[cardCode];
  var card = card_data.card;
  var img = card_data.img;
  var opac = card_data.opac;
  var cwrap = card_data.cwrap;

  // Enlarge the card.  Since overflow:visible is set, this is okay.
  cwrap.css('left', -Math.round(card_size_x2*t));
  cwrap.css('top', -Math.round(card_size_y2*t));
  cwrap.css('width', Math.round(card_size_x*(1+t)));
  cwrap.css('height', Math.round(card_size_y*(1+t)));

  // When the page is freshly loaded, all the cards are transparent.  This
  // opacifies this one card.
  img.css('opacity', 1);

  // The 'opac' object is the flash of colour that appears over the card,
  // and then fades out.
  opac.css('opacity', 1);
  opac.css('background', color);

  // Calculate the CSS offsets for the image
  var iw = atlas_size_x;
  var ih = atlas_size_y;
  var ix = card_data.x * -atlas_spacing_x;
  var iy = card_data.y * -atlas_spacing_y;

  // Resize the image atlas within the div.
  img.css('left', Math.round(ix*(1+t)));
  img.css('top', Math.round(iy*(1+t)));
  img.css('width', Math.round(iw*(1+t)));
  img.css('height', Math.round(ih*(1+t)));

  // Make this card overlap others
  var oldZ = cwrap.css('zIndex');
  cwrap.css('zIndex', 10);

  // Start an animation to restore the size of the card wrapper to normal.
  cwrap.stop(true, true);
  cwrap.animate({width:card_size_x, height:card_size_y, top:0, left:0}, 2000, 'easeOutElastic', function(){cwrap.css('zIndex', oldZ);});

  // Start an animation to restore the size of the image atlas to normal.
  img.stop(true, true);
  img.animate({width:iw, height:ih, top:iy, left:ix}, 2000, 'easeOutElastic');

  // Start an animation to fade out the flash.
  opac.stop(true, true);
  opac.animate({opacity:0}, 2000, 'easeOutCubic');
}

// 'slew' is the discovered discrepancy (if any) between client time and server time.
var ajax_slew = 0;


function ajax_init()
// Initialise the AJAX timeout loop by starting with a calibration step.
// This just sends our idea of time to the server, which responds with the
// difference between its idea of time and ours.  Not particularly
// precise, but good enough.
{
  $.getJSON('/iPhoneWall/calibrate.php', {now:Math.round((new Date()).getTime()/1000)}, ajax_initback);
}

function ajax_initback(data, textStatus)
// The server-side time calibration step returns, so we can calibrate our
// clock.
{
  if(textStatus != 'success') {
	alert('A horrible error occurred!');
	return;
  }

  if(data.error) {
	alert(data.error);
	return;
  }

  // Store the discovered discrepancy
  if(data.slew)
	ajax_slew = data.slew;

  // Okay, now we can get going with the wall... get the data epoch
  // (ie. the start of the timeslot we're in) and ask for the data.
  //
  // Note, in subsequent calls using ajax_trigger(), we ask for the _next_
  // timeslot, not the current one.  That's intentional.

  // Oh heck, let's allow a cheat.
  if(window.location.href.match(/fakeTime=(\d+)/)) {
	ajax_trigger(RegExp.$1);
	return;
  }

  var nowc = (new Date()).getTime()/1000; // Client time
  var now = (nowc + ajax_slew); 		  // Server/"Actual" time, roughly.

  // The start of this timeslot
  var dataEpoch = now - now%ajax_cycle_time;

  $.getJSON('/walldata/'+dataEpoch+'.txt', null, ajax_callback);
}

function ajax_trigger(fakeTime)
// Start a new download of wall data.  This function is called by
// setTimeout from the previous data reception (ajax_callback())
{
  var nextEpoch;

  if(fakeTime) {
	// Fake time: adjust server slew.  Note, the server data must exist
	// for this to work.
	ajax_slew = fakeTime - ((new Date()).getTime()/1000);
	nextEpoch = fakeTime - fakeTime%ajax_cycle_time;
  }
  else {
	var nowc = (new Date()).getTime()/1000; // Client time
	var now = (nowc + ajax_slew);			  // Server / "Actual" time

	// The start of the next timeslot
	nextEpoch = (now+ajax_cycle_time) - now%ajax_cycle_time;
  }

  $.getJSON('/walldata/'+nextEpoch+'.txt', null, ajax_callback);
}

var ajax_last_since = 0;		// The most recently _received_ timeslot
								// start, so we don't reprocess the same
								// data.

var ajax_last_event = 0;		// The latest event to be queued, so we
								// don't repeat ourselves.

function ajax_callback(data, textStatus)
// Data is received, following an ajax_trigger, so process it!
{
  if(textStatus != 'success') {
	alert('A horrible error occurred!');
	return;
  }

  if(data.error) {
	alert(data.error);
	return;
  }

  if(!(data.results)) {
	alert('No data was received');
	return;
  }

  // Get current client time in seconds
  var now = ((new Date()).getTime()/1000) + ajax_slew;

  // 'since' is the timeslot the server actually sent (since there's
  // sometimes redirects), and the base for which events are offset.
  var since = data.since;

  // If the data's the same as last time, schedule another AJAX call, but
  // a bit sooner (90%) as it's possible we're a bit desynced.
  if(since==ajax_last_since) {
	setTimeout(ajax_trigger, ajax_cycle_time*900);
	return;
  }

  // Record the since time for this data block.
  ajax_last_since = since;

  // 'off': we stagger events by 100 msec to get the flurries working
  // nicely.
  var off = 0;

  // For each incoming event...
  for (var i = 0; i < data.results.length; i++) {

	// The result:
	var datum = data.results[i];

	// If the datum time is after the last one already scheduled (ie. not
	// a duplicate) then we can schedule it.  Otherwise, we ignore.
	if(datum[0]+since >= ajax_last_event) {

	  // Record this event as the most recent
	  ajax_last_event = datum[0]+since;

	  // Find the offset (in seconds) from now
	  var t = ajax_last_event-now;

	  // If the event is in the future then process.  Otherwise, forget it
	  // and move on.
	  if(t>0) {

		// Calculate the setTimeout delay
		var pingt = 1000*t + off;

		// Grab the data
		var cardCode = datum[1];
		var score = datum[2];
		var playerName = datum[3];
		var countryCode = datum[4];

		// Form the status update line and pick a flash colour.
		if(score==0) { score = 'with a pass'; color = '#eef'; }
		else if(score==10) { score = 'for 10 points'; color = '#fdd'; }
		else if(score==25) { score = 'for 25 points'; color = '#fed'; }
		else if(score==50) { score = 'for 50 points'; color = '#def'; }
		else if(score==100) { score = 'for 100 points'; color = '#ffe'; }
		else { color = '#fff'; }

		var st = 'Card #'+cardCode+' played by '+playerName+' ['+countryCode+'] '+score;

		// Schedule the event
		var closure = closure_pulse_card(cardCode, st, color);
		setTimeout(closure, pingt);
		//		st = st.replace(/\'/, '\\\'');
		//		setTimeout("pulse_card('"+cardCode+"','"+st+"','"+color+"')", pingt);

		if(window.console && console.log)
		  console.log('In '+Math.round(pingt/1000)+'s: '+st);

		// Stagger event by N msec
		off += event_stagger;
	  }
	}
  }

  // If this is the initial hit (ie. result of ajax_initback()), then we
  // need to schedule another one to happen immediately for future data.
  if(!ajax_last_since) {
	setTimeout(ajax_trigger, 100);
  }

  // Otherwise, schedule another one in one cycle's time.
  else {
	setTimeout(ajax_trigger, ajax_cycle_time*1000);
  }
}


// Sound handling.  Not sure about this, really.

function sound_load()
// Load sound effects.  There are N identical sound effect objects in
// a queue, so we can have flurries of tings playing at once.
{
  //  if(tings) return;

  tings = new Array(simultaneous_tings);

  for(var i=0; i<simultaneous_tings; i++) {
	var div = document.createElement('div');
	div.id = 'ting'+i;
	document.body.appendChild(div);

	$(div).jPlayer({
	  ready: function () {
		  $(this).setFile("/iPhoneWall/ting.mp3", "/iPhoneWall/ting.ogg");
		  tings.push($(this));
		},
		  oggSupport: true,
		  swfPath: "/iPhoneWall/js"
		  });

	//	tings[i] = new Audio('/iPhoneWall/ting.wav');
	//	tings[i].load();
  }
}

function sound_playting()
// Play a 'ting' sound.  This is done by popping a ting off the end of
// the ting queue, unshifting it to the front (rotate the queue, effectively),
// and playing it.
{
  if(!tings) return;

  var a = tings.pop();
  if(a) {
	tings.unshift(a);
	a.stop(); // Perhaps optional
	a.currentTime = 0;
	a.play();
  }
}

function sound_toggle(e)
// Event handler for the sound on/off tickbox.
{
  if($('#soundOnCheck').is(':checked')) {
	// The box is ticked, so turn the sound on.

	soundOn = 1;

	// If the audio isn't loaded yet, we'll need to prepare the queue
	if(tings.length<1)
	  sound_load();

	// Play a ting, just for feedback.
	sound_playting();
  }
  else
	// Not ticked.  Screw it.
	soundOn = 0;

  // And save the setting in a long-lived cookie
  var date = new Date();
  date.setTime(date.getTime()+1000*86400*365);
  document.cookie = 'soundOn='+soundOn+'; expires='+date.toGMTString();
}

function sound_setup()
// Initialise sound support
{
  // The sound on/off tickbox
  var soc = $('#soundOnCheck');

  /*
	if(!(document.createElement('audio').canPlayType)) {
	// Sod this.
	soundOn = 0;
	soc.css('display', 'none');
	return;
	}
  */

  // Check for a sound on/off preference
  if(document.cookie.match(/soundOn=(\d+)/))
	// There's a cookie, so respect it.
	soundOn = RegExp.$1=='1' ? 1 : 0;
  else
	// No cookie, so assume they want sound.  Everyone likes noisy webpages!
	soundOn = 1;

  // Change the tickbox appropriately.  This would be done in PHP usually,
  // but I can't be bothered to figure out how to embed PHP in a Joomla
  // mod_custom thing.  Probably can't.  Oh well.
  soc.attr('checked', soundOn ? true : false);

  // If sound is enabled, load the sounds
  if(soundOn) sound_load();

  // And activate the tickbox action.
  soc.bind('change', sound_toggle);
}

function add_card(wrapper, cardCode, i)
// Create the DOM elements for a card, and insert it into the wall
// wrapper.  'i' is the index of the card, which determines coordinates.
{
  // Card location in the grid
  var x = i%15;
  var y = (i-x)/15;

  // Main card wrapper.  This doesn't really move or change much.
  var card = document.createElement('div');
  card.className = 'card';
  card.id = 'card'+cardCode;
  card.style.left = (x*card_spacing_x)+'px';
  card.style.top = (y*card_spacing_y)+'px';
  $(card).bind('click', function() {pulse_card(cardCode,'','#fff');});

  // The internal card wrapper.  This resizes inside the wrapper, which
  // has overflow:visible. It, however, has overflow:hidden, to
  // effectively crop the image atlas below.
  var cwrap = document.createElement('div');
  cwrap.className = 'cwrap';

  // The image atlas itself.  This gets resized and offsetted
  var img = document.createElement('img');
  img.src = '/iPhoneWall/allcards2.png';
  img.style.left = (x*-atlas_spacing_x)+'px';
  img.style.top = (y*-atlas_spacing_y)+'px';

  // 'opac' is a flat colour layer used to 'flash' the card.
  var opac = document.createElement('div');
  opac.className = 'opac';

  // This probably doesn't need to be so convoluted.  The main reason is
  // to support the flash effect, and to allow two different easing
  // equations: one for scaling, one for flash.  I can't see how to
  // animate the same element simultaneously with the same easing eqn.
  // Probably not possible with this version of JQuery.

  // Store the card grid location in a hash... I can't remember if there's
  // an easy way to add a value to a JS object.  We also store the
  // objects, just to make lookup quicker.
  card_hash[cardCode] = {x:x, y:y, opac:$(opac), img:$(img), cwrap:$(cwrap), card:$(card)};

  // Construct the DOM
  cwrap.appendChild(img);
  cwrap.appendChild(opac);
  card.appendChild(cwrap);

  // And add the kaboodle to the wall wrapper.
  wrapper.appendChild(card);
}

function add_cards()
// Add all the cards div, in the same arrangement as allcards2.png
{
  var wall=document.getElementById('wall');

  add_card(wall,"J565",0);
  add_card(wall,"T076",1);
  add_card(wall,"T129",2);
  add_card(wall,"D123",3);
  add_card(wall,"Y137",4);
  add_card(wall,"J003",5);
  add_card(wall,"J575",6);
  add_card(wall,"P337",7);
  add_card(wall,"J136",8);
  add_card(wall,"P370",9);
  add_card(wall,"D099",10);
  add_card(wall,"P380",11);
  add_card(wall,"P246",12);
  add_card(wall,"K076",13);
  add_card(wall,"K007",14);
  add_card(wall,"T012",15);
  add_card(wall,"D074",16);
  add_card(wall,"P417",17);
  add_card(wall,"J502",18);
  add_card(wall,"Y140",19);
  add_card(wall,"J020",20);
  add_card(wall,"P006",21);
  add_card(wall,"P378",22);
  add_card(wall,"Y070",23);
  add_card(wall,"Y102",24);
  add_card(wall,"J079",25);
  add_card(wall,"Y198",26);
  add_card(wall,"Y138",27);
  add_card(wall,"K094",28);
  add_card(wall,"K081",29);
  add_card(wall,"D121",30);
  add_card(wall,"T027",31);
  add_card(wall,"Y078",32);
  add_card(wall,"J550",33);
  add_card(wall,"Y145",34);
  add_card(wall,"J073",35);
  add_card(wall,"P042",36);
  add_card(wall,"Y025",37);
  add_card(wall,"P011",38);
  add_card(wall,"Y112",39);
  add_card(wall,"J083",40);
  add_card(wall,"D060",41);
  add_card(wall,"K127",42);
  add_card(wall,"K122",43);
  add_card(wall,"P004",44);
  add_card(wall,"T043",45);
  add_card(wall,"J106",46);
  add_card(wall,"P235",47);
  add_card(wall,"P037",48);
  add_card(wall,"D005",49);
  add_card(wall,"D030",50);
  add_card(wall,"P101",51);
  add_card(wall,"J125",52);
  add_card(wall,"P428",53);
  add_card(wall,"Y170",54);
  add_card(wall,"J237",55);
  add_card(wall,"K005",56);
  add_card(wall,"Q121",57);
  add_card(wall,"K128",58);
  add_card(wall,"D088",59);
  add_card(wall,"T010",60);
  add_card(wall,"P258",61);
  add_card(wall,"J276",62);
  add_card(wall,"T009",63);
  add_card(wall,"T018",64);
  add_card(wall,"D071",65);
  add_card(wall,"J368",66);
  add_card(wall,"P259",67);
  add_card(wall,"J002",68);
  add_card(wall,"P261",69);
  add_card(wall,"D047",70);
  add_card(wall,"P019",71);
  add_card(wall,"P059",72);
  add_card(wall,"K057",73);
  add_card(wall,"Y514",74);
  add_card(wall,"D043",75);
  add_card(wall,"T033",76);
  add_card(wall,"T002",77);
  add_card(wall,"D098",78);
  add_card(wall,"P148",79);
  add_card(wall,"D073",80);
  add_card(wall,"J391",81);
  add_card(wall,"P279",82);
  add_card(wall,"Y182",83);
  add_card(wall,"P317",84);
  add_card(wall,"D057",85);
  add_card(wall,"P373",86);
  add_card(wall,"K074",87);
  add_card(wall,"Q090",88);
  add_card(wall,"K070",89);
  add_card(wall,"T014",90);
  add_card(wall,"D133",91);
  add_card(wall,"P409",92);
  add_card(wall,"D006",93);
  add_card(wall,"P165",94);
  add_card(wall,"D084",95);
  add_card(wall,"J452",96);
  add_card(wall,"P294",97);
  add_card(wall,"J070",98);
  add_card(wall,"P321",99);
  add_card(wall,"D062",100);
  add_card(wall,"K047",101);
  add_card(wall,"K040",102);
  add_card(wall,"K027",103);
  add_card(wall,"Q111",104);
  add_card(wall,"D034",105);
  add_card(wall,"T006",106);
  add_card(wall,"J291",107);
  add_card(wall,"D093",108);
  add_card(wall,"P210",109);
  add_card(wall,"D101",110);
  add_card(wall,"J556",111);
  add_card(wall,"P333",112);
  add_card(wall,"P256",113);
  add_card(wall,"P325",114);
  add_card(wall,"D122",115);
  add_card(wall,"Q144",116);
  add_card(wall,"K117",117);
  add_card(wall,"K058",118);
  add_card(wall,"Q012",119);
  add_card(wall,"D009",120);
  add_card(wall,"J017",121);
  add_card(wall,"T008",122);
  add_card(wall,"T007",123);
  add_card(wall,"T068",124);
  add_card(wall,"D013",125);
  add_card(wall,"J117",126);
  add_card(wall,"P156",127);
  add_card(wall,"Y072",128);
  add_card(wall,"P082",129);
  add_card(wall,"P018",130);
  add_card(wall,"K133",131);
  add_card(wall,"K009",132);
  add_card(wall,"K002",133);
  add_card(wall,"N004",134);
  add_card(wall,"T101",135);
  add_card(wall,"J261",136);
  add_card(wall,"T022",137);
  add_card(wall,"T020",138);
  add_card(wall,"T115",139);
  add_card(wall,"D046",140);
  add_card(wall,"J118",141);
  add_card(wall,"P170",142);
  add_card(wall,"D017",143);
  add_card(wall,"P091",144);
  add_card(wall,"D004",145);
  add_card(wall,"K006",146);
  add_card(wall,"K012",147);
  add_card(wall,"K071",148);
  add_card(wall,"K059",149);
  add_card(wall,"D102",150);
  add_card(wall,"T090",151);
  add_card(wall,"T046",152);
  add_card(wall,"D012",153);
  add_card(wall,"P105",154);
  add_card(wall,"D053",155);
  add_card(wall,"J131",156);
  add_card(wall,"P187",157);
  add_card(wall,"D077",158);
  add_card(wall,"P232",159);
  add_card(wall,"K121",160);
  add_card(wall,"K013",161);
  add_card(wall,"K017",162);
  add_card(wall,"Q045",163);
  add_card(wall,"A882",164);
  add_card(wall,"J263",165);
  add_card(wall,"D087",166);
  add_card(wall,"P339",167);
  add_card(wall,"D063",168);
  add_card(wall,"P114",169);
  add_card(wall,"D068",170);
  add_card(wall,"J143",171);
  add_card(wall,"P207",172);
  add_card(wall,"D117",173);
  add_card(wall,"J123",174);
  add_card(wall,"K021",175);
  add_card(wall,"Y524",176);
  add_card(wall,"K018",177);
  add_card(wall,"K046",178);
  add_card(wall,"Q005",179);
}

$(document).ready(add_cards);
$(document).ready(sound_setup);
$(document).ready(ajax_init);
