
// WebSDR JavaScript part
// copyright 2007-2010, Pieter-Tjerk de Boer, pa3fwm@amsat.org

// variables governing what the user listens to:
var lo=-2.7,hi=-0.3;   // edges of passband, in kHz w.r.t. the carrier
var mode=0;            // 1 if AM, 0 otherwise (SSB/CW)
var band=0;            // id of the band we're listening to
var freq=1300;         // frequency (of the carrier) in kHz


// variables governing what the user sees:
var Views={ allbands:0, othersslow:1, oneband:2, blind:3 };
var view=Views.blind;
var nwaterfalls=0;
var waterslowness=4;
var waterheight=100;
var watermode=1;


// information about the available bands:
var band_effcenterfreq = new Array();
var band_effsamplerate = new Array();
var zooms = new Array();
var starts = new Array();


// references to objects on the screen:
var scaleobj;
var scaleobjs = new Array();
var scaleimgs = new Array();
var passbandobj;
var edgelowerobj;
var edgeupperobj;
var carrierobj;
var smeterobj;
var numericalsmeterobj;
var smeterpeakobj;
var numericalsmeterpeakobj;


// timers:
var interval_updatesmeter;
var interval_ajax3;


// misc
var serveravailable=-1;  // -1 means yet to be tested, 0 and 1 mean false and true
var smeterpeaktimer=2;
var smeterpeak=0;    
var allloadeddone=false;
var waitingforwaterfalls=0;  // number of waterfallapplets that are still in the process of starting


// derived quantities:
var khzperpixel=band_samplerate[band]/1024;
var passbandobjstart=0;    // position (in pixels) of start of passband on frequency axis, w.r.t. location of carrier
var passbandobjwidth=0;    // width of passband in pixels
var centerfreq=band_centerfreq[band];






function send_soundsettings_to_server()
{
  document.soundapplet.setparam(
      "f="+freq
     +"&band="+band
     +"&lo="+lo
     +"&hi="+hi
     +"&mode="+mode
     +"&name="+encodeURIComponent(document.usernameform.username.value) 
     );
}


function draw_passband()
{
   passbandobjstart=Math.round((lo-0.045)/khzperpixel);
   passbandobjwidth=Math.round((hi+0.045)/khzperpixel)-passbandobjstart;
   passbandobj.style.width=passbandobjwidth+"px";
   if (!scaleobj) return;

   var x=(freq-centerfreq)/khzperpixel+512;
   var y=scaleobj.offsetTop+15;
   passbandobj.style.top=y+"px";
   edgelowerobj.style.top=y+"px";
   edgeupperobj.style.top=y+"px";
   carrierobj.style.top=y+"px";
   carrierobj.style.left=x+"px";
   x=x+passbandobjstart;
   passbandobj.style.left=x+"px";
   edgelowerobj.style.left=(x-11)+"px";
   edgeupperobj.style.left=(x+passbandobjwidth)+"px";
}


function iscw()
{
   return hi-lo < 1.0;
}

function nominalfreq()
{
   if (iscw()) return freq+(hi+lo)/2;
   return freq;
}





// this function is called from java when the scrollwheel is moved to change the zoom
function zoomchange(id,zoom,start)
{
   var b=id;
   if (view==Views.oneband) b=band;
   band_effsamplerate[b] = band_samplerate[b]/(1<<zoom);
   band_effcenterfreq[b] = band_centerfreq[b] - band_samplerate[b]/2 + (start*(band_samplerate[b]/(1<<band_maxzoom[b]))/1024) + band_effsamplerate[b]/2;
   zooms[b]=zoom;
   starts[b]=start;
   scaleimgs[id].src = band_scaleimgz[b][zooms[b]];
   scaleimgs[id].style.left = - starts[b]/(1<<(band_maxzoom[b]-zooms[b]));
   if (b==band) {
      khzperpixel = band_effsamplerate[band]/1024;
      centerfreq = band_effcenterfreq[band];
      updbw();
   }
}


var dont_update_textual_frequency=false;

function setfreq(f)
{
   freq=f;
   send_soundsettings_to_server();
   if (view!=Views.blind) draw_passband();
   if (dont_update_textual_frequency) return;
   var nomfreq=nominalfreq();
   if (freq.toFixed) document.freqform.frequency.value=nomfreq.toFixed(2);
   else document.freqform.frequency.value=nomfreq+" kHz";
}

function setfreqif(str)
// called when frequency is entered textually
{
   f=parseFloat(str);
   dont_update_textual_frequency=true;
   if (f<=0) return;
   if (iscw()) f-=(hi+lo)/2;
   if (f>centerfreq-band_samplerate[band]/2-4 && f<centerfreq+band_samplerate[band]/2+4) {
      // new frequency is in the current band
      setfreq(f);
      dont_update_textual_frequency=false;
      return;
   }
   // new frequency is not in the current band: then search through all bands until we find the right one (if any)
   for (i=0;i<nbands;i++) {
      c=band_centerfreq[i];
      w=band_samplerate[i]/2+4;
      if (f>c-w && f<c+w) {
         band_vfo[i]=f;
         setband(i);
         document.freqform.frequency.value=str;
         dont_update_textual_frequency=false;
         return;
      } 
   }
   dont_update_textual_frequency=false;
}


function set_am(l,h)
{
   mode=1;
   lo=l;
   hi=h;
   updbw();
}

function set_ssb(l,h)
{
   mode=0;
   lo=l;
   hi=h;
   updbw();
}

function setmode(m)
{
   if (m=="AM") mode=1;
   else mode=0;
}



function setview(v)
{
   if ((v==Views.allbands && view==Views.othersslow) || (view==Views.allbands && v==Views.othersslow)) {
      // no need to restart the applets in this case
      view=v;   
      createCookie("view",view,3652);
      waterfallspeed(waterslowness);
      return;
   }

   if (view==Views.blind) {
      var els = document.getElementsByTagName('*');
      for (i=0; i<els.length; i++) {
         if (els[i].className=="hideblind") els[i].style.display="inline";
         if (els[i].className=="showblind") els[i].style.display="none";
      }
   }
   for (i=0;i<nwaterfalls;i++) document["waterfallapplet"+i].destroy();

   view=v;   
   createCookie("view",view,3652);

   document_waterfalls();  // (re)start the waterfall applets

   if (view==Views.blind) {
      var els = document.getElementsByTagName('*');
      for (i=0; i<els.length; i++) {
         if (els[i].className=="showblind") els[i].style.display="inline";
         if (els[i].className=="hideblind") els[i].style.display="none";
      }
      return;
   }

}


function setband(b)
{
   if (b<0 || b>=nbands) return;
   band_vfo[band]=freq;

   if ( (centerfreq>9000 && band_centerfreq[b]<=9000) || (band_centerfreq[b]>9000 && centerfreq<=9000)) {
      // if needed, exchange LSB/USB 
      var tmp=hi;
      hi=-lo;
      lo=-tmp;
   }

   band=b;
   document.freqform.group0[band].checked=true;
   if (view==Views.allbands || view==Views.othersslow) {
      scaleobj = scaleobjs[b];
   } else if (view==Views.oneband) {
      scaleobj = scaleobjs[0];
      scaleimgs[0].src = band_scaleimgz[b][zooms[b]];
      scaleimgs[0].style.left = - starts[b]/(1<<(band_maxzoom[b]-zooms[b]));
      if (waitingforwaterfalls==0) document["waterfallapplet0"].setband(b, band_maxzoom[b], zooms[b], starts[b]);
   }
   centerfreq = band_effcenterfreq[band];
   khzperpixel = band_effsamplerate[band]/1024;
   setfreq(band_vfo[band]);
   waterfallspeed(waterslowness);
}


function test_serverbusy()
{
   serveravailable=document.soundapplet.getid();
   if (serveravailable==0) {
      document.body.innerHTML="Sorry, the WebSDR server is too busy right now; please try again later.\n";
      clearInterval(interval_updatesmeter);
      clearInterval(interval_ajax3);
   }
}



function updatesmeter()
{
   if (!allloadeddone) return;

   var s=document.soundapplet.smeter;
   var c=''+(s/100.0-127).toFixed(1);
   if (c.length<6) c='&nbsp;&nbsp;'+c;
   numericalsmeterobj.innerHTML = c;
   if (s>=0) smeterobj.style.width= s*0.0191667+"px";
   else smeterobj.style.width="0px";
   smeterpeaktimer--;
   if ((smeterpeak<s-0.1) || (smeterpeaktimer<=0)) {
      smeterpeak=s;
      smeterpeaktimer=10;
      if (smeterpeak>=0) smeterpeakobj.style.width= smeterpeak*0.0191667 +"px";
      else smeterpeakobj.style.width="0px";
      var c=''+(s/100.0-127).toFixed(1);
      if (c.length<6) c='&nbsp;&nbsp;'+c;
      numericalsmeterpeakobj.innerHTML = c;
   }

   if (serveravailable<0) test_serverbusy();
}



function ajaxFunction3()
{
  var xmlHttp;
  try { xmlHttp=new XMLHttpRequest(); }
    catch (e) { try { xmlHttp=new ActiveXObject("Msxml2.XMLHTTP"); }
      catch (e) { try { xmlHttp=new ActiveXObject("Microsoft.XMLHTTP"); }
        catch (e) { alert("Your browser does not support AJAX!"); return false; } } }
  xmlHttp.onreadystatechange=function()
    {
    if(xmlHttp.readyState==4)
      {
      if (xmlHttp.responseText!="") eval(xmlHttp.responseText);
      }
    }
  var url="/~~othersj?chseq="+chseq;
  xmlHttp.open("GET",url,true);
  xmlHttp.send(null);
}


function javatest()
{
   var javaversion;
   try {
   javaversion = document.versiontestapplet.javaversion();
   } catch(err) {
      document.getElementById("javawarning").innerHTML='<b><i><font color=red>It seems Java is not installed (or disabled) on your computer. You need to install and enable it for this website to work properly.</font></i></b>';
      javaversion="999";
   }
   if (javaversion<"1.4.2") document.getElementById("javawarning").innerHTML='<b><i><font color=red>Your Java version is '+javaversion+', which is too old for the WebSDR. Please install version 1.4.2 or newer, e.g. from <a href="http://www.java.com">http://www.java.com</a> if you hear no sound.</font></i></b>';
}



function updbw()
{
   if (lo>hi) {
      if (document.onmousemove == useMouseXYloweredge) lo=hi;
      else hi=lo;
   }
   if (lo<-3.9) lo=-3.9;
   if (hi>3.9) hi=3.9;
   var x6=document.getElementById('numericalbandwidth6');
   var x60=document.getElementById('numericalbandwidth60');
   x6.innerHTML=(hi-lo+0.091).toFixed(2);
   x60.innerHTML=(hi-lo+0.551).toFixed(2);
   setfreq(freq);
}


// from http://www.quirksmode.org/js/cookies.html
function createCookie(name,value,days) {
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
}

function readCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return null;
}



function waterfallspeed(sp)
{
   waterslowness=sp;
   if (waitingforwaterfalls>0) return;
   if (view==Views.othersslow) {
      for (i=0;i<nwaterfalls;i++)
         if (i==band) document["waterfallapplet"+i].setslow(sp);
         else document["waterfallapplet"+i].setslow(100);
   } else {
     for (i=0;i<nwaterfalls;i++)
        document["waterfallapplet"+i].setslow(sp);
   }
}

function waterfallheight(si)
{
   waterheight=si;
   if (waitingforwaterfalls>0) return;
   for (i=0;i<nwaterfalls;i++) {
      document["waterfallapplet"+i].height=si;
      document["waterfallapplet"+i].setSize(1024,si);
      document["waterfallapplet"+i].setheight(si);
   }

   var y=scaleobj.offsetTop+15;
   passbandobj.style.top=y+"px";
   edgelowerobj.style.top=y+"px";
   edgeupperobj.style.top=y+"px";
   carrierobj.style.top=y+"px";
}

function waterfallmode(m)
{
   watermode=m;
   if (waitingforwaterfalls>0) return;
   for (i=0;i<nwaterfalls;i++) {
      document["waterfallapplet"+i].setmode(m);
   }
}


function allloaded()
{
   if (centerfreq>9000 && hi<0) { tmp=hi; hi=-lo; lo=-tmp; }
   updbw();
   setfreq(centerfreq+10);

   test_serverbusy();
}

function soundappletstarted()
{
   allloadeddone=true;

   if (centerfreq>9000 && hi<0) { tmp=hi; hi=-lo; lo=-tmp; }
   updbw();
   setfreq(centerfreq+10);

   test_serverbusy();
}


function waterfallappletstarted(id)
{
   // this function is called when a waterfall applet becomes active
   waitingforwaterfalls--;
   if (waitingforwaterfalls<0) waitingforwaterfalls=0; // shouldn't happen...
   if (waitingforwaterfalls!=0) return;

   // now all waterfall applets have started so we can initialize their settings
   waterfallspeed(waterslowness);
   waterfallmode(watermode);
   var i;
   for (i=0;i<nwaterfalls;i++)
      document["waterfallapplet"+i].setband(i, band_maxzoom[i], zooms[i], starts[i]);
   if (view==Views.oneband) document["waterfallapplet0"].setband(band, band_maxzoom[band], zooms[band], starts[band]);

   // and when the applets run, we can also be sure that the HTML elements for the frequency scale have been rendered:
   for (i=0;i<nwaterfalls;i++) {
     scaleobjs[i] = document.getElementById('clipscale'+i);
     scaleimgs[i] = document.images["scale"+i];
   }
   if (view==Views.oneband) {
      scaleimgs[0].src = band_scaleimgz[band][zooms[band]];
      scaleimgs[0].style.left = - starts[band]/(1<<(band_maxzoom[band]-zooms[band]));
      scaleobj = scaleobjs[0];
   } else {
      for (i=0;i<nwaterfalls;i++) {
         scaleimgs[i].src = band_scaleimgz[i][zooms[i]];
         scaleimgs[i].style.left = - starts[i]/(1<<(band_maxzoom[i]-zooms[i]));
      }
      scaleobj=scaleobjs[band];
   }
   draw_passband();
}


function bodyonload()
{
   view= readCookie('view');
   if (view==null) view=Views.oneband;
   var s;
   if (nbands>=2) s='<input type="radio" name="group" value="all bands" onclick="setview(0);">all bands<input type="radio" name="group" value="others slow" onclick="setview(1);" >others slow<input type="radio" name="group" value="one band" onclick="setview(2);" >one band';
   else {
      s='<input type="radio" name="group" value="waterfall" onclick="setview(2);">waterfall';
      if (view==Views.othersslow || view==Views.allbands) view=Views.oneband;
   }
   s+='<input type="radio" name="group" value="blind" onclick="setview(3);" >blind';
   document.getElementById('viewformbuttons').innerHTML = s;
   if (nbands>=2) document.viewform.group[view].checked=true;
   else document.viewform.group[view-2].checked=true;

   passbandobj =document.getElementById('yellowbar');
   edgeupperobj = document.images.edgeupper;
   edgelowerobj = document.images.edgelower;
   carrierobj = document.images.carrier;
   smeterobj = document.getElementById('smeterbar');
   numericalsmeterobj=document.getElementById('numericalsmeter');
   smeterpeakobj = document.getElementById('smeterpeak');
   numericalsmeterpeakobj=document.getElementById('numericalsmeterpeak');
   smeterobj.style.top= smeterpeakobj.style.top;
   smeterobj.style.left= smeterpeakobj.style.left;


   for (i=0;i<nbands;i++) {
      band_effcenterfreq[i]=band_centerfreq[i];
      band_effsamplerate[i]=band_samplerate[i];
      zooms[i]=0;
      starts[i]=0;
   }

   document.freqform.frequency.value=freq;

   interval_ajax3 = setInterval('ajaxFunction3()',1000);

   setTimeout('javatest()',2000);

   interval_updatesmeter = setInterval('updatesmeter()',100);

   if (!document.versiontestapplet || !navigator.javaEnabled()) {
      document.getElementById("javawarning").innerHTML='<b><i><font color=red>It seems Java is not installed (or disabled) on your computer. You need to install and enable it for this website to work properly.</font></i></b>'
   }

   chatboxobj = document.getElementById('chatbox');
   statsobj = document.getElementById('stats');
   numusersobj = document.getElementById('numusers');
   usersobj = document.getElementById('users');

   setview(view);
}


function setusernamecookie() {
   createCookie('username',document.usernameform.username.value,365*5);
   var p=document.getElementById("please");
   if (p) p.innerHTML="Your name or callsign: ";
   send_soundsettings_to_server();
}

//----------------------------------------------------------------------------------------
// things related to interaction with the mouse (clicking & dragging on the frequency axes)

var dragging=false;
var dragorigX;
var dragorigval;

function getMouseXY(e)
{
   e = e || window.event;
   if (e.pageX || e.pageY) return {x:e.pageX, y:e.pageY};
   return {
     x:e.clientX + document.body.scrollLeft - document.body.clientLeft,
     y:e.clientY + document.body.scrollTop  - document.body.clientTop
   };
// from: http://www.webreference.com/programming/javascript/mk/column2/
}


function useMouseXY(e)
{
   var pos=getMouseXY(e);
   setfreq((pos.x-scaleobj.offsetParent.offsetLeft-512)*khzperpixel+centerfreq-(hi+lo)/2);
}

function useMouseXYloweredge(e)
{
   var pos=getMouseXY(e);
   lo=dragorigval+(pos.x-dragorigX)*khzperpixel;
   updbw();
}

function useMouseXYupperedge(e)
{
   var pos=getMouseXY(e);
   hi=dragorigval+(pos.x-dragorigX)*khzperpixel;
   updbw();
}

function useMouseXYpassband(e)
{
   var pos=getMouseXY(e);
   setfreq(dragorigval+(pos.x-dragorigX)*khzperpixel);
}


function mouseup(e)
{
   if (dragging) {
      dragging=false;
      document.onmousemove(e);
      document.onmousemove = null;
   }
}

function imgmousedown(ev,bb)
{
   dragging=true;
   document.onmousemove = useMouseXY;
   if (band!=bb) {
      if (view==Views.othersslow) waterfallspeed(waterslowness);
      if (view!=Views.oneband) setband(bb);
      useMouseXY(ev);
   }
}

function mousedownlower(ev)
{
   var pos=getMouseXY(ev);
   dragging=true;
   document.onmousemove = useMouseXYloweredge;
   dragorigX=pos.x;
   dragorigval=lo;
}

function mousedownupper(ev)
{
   var pos=getMouseXY(ev);
   dragging=true;
   document.onmousemove = useMouseXYupperedge;
   dragorigX=pos.x;
   dragorigval=hi;
}

function mousedownpassband(ev)
{
   var pos=getMouseXY(ev);
   dragging=true;
   document.onmousemove = useMouseXYpassband;
   dragorigX=pos.x;
   dragorigval=freq;
}

function docmousedown(ev)
{
   if (!ev && event.srcElement) {
      // for IE:
      var fobj = event.srcElement;
      if (fobj.className == "scale" || fobj.className=="scaleabs") {
         fobj = ev.target;
         // bovenstaande geeft onder IE een foutmelding; dat (b)lijkt de enige manier te zijn om de default-actie van de muisklik te onderdrukken :-(
         return false;
      }
      return true;
   }
   // for FF etc.:
   var fobj = ev.target;
   if (fobj.className == "scale" || fobj.className=="scaleabs") return false;
   return true;
}


document.onmouseup = mouseup;
document.onmousedown = docmousedown;


//----------------------------------------------------------------------------------------
// functions that create part of the HTML GUI

function document_username() 
{
  var x= readCookie('username');
  if (x) {
    document.write("Your name or callsign: ");
    document.write('<input type="text" value="" name="username" onchange="setusernamecookie()" onclick="">');
    document.usernameform.username.value=x;
  } else {
    document.write('<span id="please"><b><i>Please log in by typing your name or callsign here (it will be saved for later visits in a cookie):<\/i><\/b></span> ');
    document.write('<input type="text" value="" name="username" onchange="setusernamecookie()" onclick="">');
  }
}


function document_waterfalls() 
{
  if (view==Views.allbands || view==Views.othersslow) nwaterfalls=nbands;
  else if (view==Views.oneband) nwaterfalls=1;
  else { 
     nwaterfalls=0;
     document.getElementById('waterfalls').innerHTML="";
     return;
  }

  var i;
  var s="";
  for (i=0;i<nwaterfalls;i++) {
    s+=
      '<applet code="websdrwaterfall.class" archive="websdr-20101224.jar" width=1024 height='+waterheight+' name="waterfallapplet'+i+'" MAYSCRIPT>' +
      '<param name="rq" value="/~~waterstream'+i+'">' +
      '<param name="maxh" value="200">' +
      '<param name="maxzoom" value="' + band_maxzoom[i] + '">' +
      '<param name="id" value="'+i+'">' +
      '<\/applet>'+
      '<div style="overflow:hidden; width:1024px" title="click to tune" id="clipscale'+i+'">' +
        '<img src="'+band_scaleimgz[i][0]+'" onmousedown="imgmousedown(event,'+i+')" class="scale" name="scale'+i+'">' +
      '</div>' +
      '<div style="width:1024px;height:30px;background-color:black" id="blackbar'+i+'" title="click to tune" onmousedown="imgmousedown(event,'+i+')"><\/div>' +
      '\n';
  }
  waitingforwaterfalls=nwaterfalls;     // this must be before the next line, to prevent a race
  document.getElementById('waterfalls').innerHTML=s;


  for (i=0;i<nwaterfalls;i++) {
    scaleobjs[i] = document.getElementById('clipscale'+i);
    scaleimgs[i] = document.images["scale"+i];
  }

}


function document_bandbuttons() {
    if (nbands>1) {
       document.write("<br>Band: ")
       var i;
       for (i=0;i<nbands;i++) document.write ("<input type=\"radio\" name=\"group0\" value=\""+band_name[i]+"\" onclick=\"setband("+i+")\">"+band_name[i]+"\n");
    }
}


function document_soundapplet() {
  document.write(
     '<applet code="websdrsound.class" archive="websdr-20101224.jar" width=400 height=100 name="soundapplet" MAYSCRIPT></applet>' +
     '<form action="#"><input type="button" value="show/hide audio status" onclick="document.soundapplet.togglehidestatus()"></form>' +
     '\n' );
}





//----------------------------------------------------------------------------------------
// things not directly related to the SDR: chatbox, logbook

function sendchat()
{
  var xmlHttp;
  try { xmlHttp=new XMLHttpRequest(); }
    catch (e) { try { xmlHttp=new ActiveXObject("Msxml2.XMLHTTP"); }
      catch (e) { try { xmlHttp=new ActiveXObject("Microsoft.XMLHTTP"); }
        catch (e) { alert("Your browser does not support AJAX!"); return false; } } }
  var url="/~~chat";
  url=url+"?name="+encodeURIComponent(document.usernameform.username.value)+"&msg="+encodeURIComponent(document.chatform.chat.value);
  xmlHttp.open("GET",url,true);
  xmlHttp.send(null);
  document.chatform.chat.value="";
  return false;
}

function sendlogclear()
{
  document.logform.comment.value="";
}

function sendlog()
{
  var xmlHttp;
  try { xmlHttp=new XMLHttpRequest(); }
    catch (e) { try { xmlHttp=new ActiveXObject("Msxml2.XMLHTTP"); }
      catch (e) { try { xmlHttp=new ActiveXObject("Microsoft.XMLHTTP"); }
        catch (e) { alert("Your browser does not support AJAX!"); return false; } } }
  var url="/~~loginsert";
  url=url
     +"?name="+encodeURIComponent(document.usernameform.username.value)
     +"&freq="+nominalfreq()
     +"&call="+encodeURIComponent(document.logform.call.value)
     +"&comment="+encodeURIComponent(document.logform.comment.value)
     ;
  xmlHttp.open("GET",url,true);
  xmlHttp.send(null);
  document.logform.call.value="";
  document.logform.comment.value="";
  xmlHttp.onreadystatechange=function()
    {
    if(xmlHttp.readyState==4)
      {
      document.logform.comment.value=xmlHttp.responseText;
      }
    }
  setTimeout("document.logform.comment.value=''",1000);
  return false;
}



