
// SPDX-License-Identifier: CC-BY-NC-SA-4.0
//
// Copyright (C) 2026 Bit by Bit Signal Processing LLC (https://bxbsp.com)
//
// This work is placed under the "Creative Commons Attribution
// NonCommercial ShareAlike 4.0 International" license, known
// by the shortened acronym "CC-BY-NC-SA-4.0".
//
// This work is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// A CC-BY-NC-SA-4.0 license allows you to use this work for
// noncommercial purposes so long as attribution is made to the
// original author.  Modified versions of this work may be distributed,
// but only under the same license.  For further details, see the
// Creative Commons License "CC-BY-NC-SA-4.0".
//
// You should have received a copy of the CC-BY-NC-SA-4.0 license
// along with this work. If not, see
// <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
//

//
// Part of the mechanism to make sure that too many graphs aren't sent, so they don't become
// queued up.
//
var last_graph_count_received = 0;


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Constants
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Text drawing flags
const DRAW_TEXT_X_LEFT     = 0x00;
const DRAW_TEXT_X_CENTER   = 0x01;
const DRAW_TEXT_X_RIGHT    = 0x02;
const DRAW_TEXT_X_MASK     = 0x03;

const DRAW_TEXT_Y_BOTTOM   = 0x00;
const DRAW_TEXT_Y_CENTER   = 0x10;
const DRAW_TEXT_Y_TOP      = 0x20;
const DRAW_TEXT_Y_MASK     = 0x30;

const DRAW_TEXT_ROTATE_0        = 0x000;
const DRAW_TEXT_ROTATE_90_LEFT  = 0x100;
const DRAW_TEXT_ROTATE_90_RIGHT = 0x200;
const DRAW_TEXT_ROTATE_MASK     = 0x300;

const MAX_COLORS = 22;

// Audio Beeps
const BEEP_ID_TOUCH_RECOGNIZED = 1;
const BEEP_ID_ENTRY_ACCEPTED   = 2;
const BEEP_ID_ENTRY_REJECTED   = 3;


//
// Drawing commands
//
const WS_COMMAND_RECTANGLE                   = 101;
const WS_COMMAND_TRIANGLE                    = 102;
const WS_COMMAND_CIRCLE                      = 103;
const WS_COMMAND_LINE                        = 104;
const WS_COMMAND_GRAPH_DATA                  = 105;
const WS_COMMAND_TEXT                        = 107;
const WS_COMMAND_FONT                        = 108;
const WS_COMMAND_MULTICOLORED_TEXT           = 109;
const WS_COMMAND_SVG                         = 110;

const WS_COMMAND_SET_CURRENT_LAYER           = 201;
const WS_COMMAND_CLEAR_LAYER                 = 202;
const WS_COMMAND_MAKE_LAYER_VISIBLE          = 203;
const WS_COMMAND_MAKE_LAYER_HIDDEN           = 204;
const WS_COMMAND_SET_LAYER_VISIBILITY        = 205;
const WS_COMMAND_COPY_LAYER_TO_LAYER         = 206;
const WS_COMMAND_MOVE_LAYER_TO_LAYER         = 207;

const WS_COMMAND_BEEP                        = 301;

const WS_COMMAND_GRAPH_DATA_OLD              = 199;

//
// Commands server to GUI client with response
//
const WS_COMMAND_TEXT_SIZE                   = 401;

//
// Responses GUI client to server
//
const WS_EVENT_BUTTON_DOWN                  = 1101;
const WS_EVENT_BUTTON_UP                    = 1102;
const WS_EVENT_MOUSE_MOVE                   = 1103;
const WS_EVENT_WHEEL                        = 1104;
const WS_EVENT_TOUCHSTART                   = 1105;
const WS_EVENT_TOUCHEND                     = 1106;
const WS_EVENT_TOUCHMOVE                    = 1107;
const WS_EVENT_TOUCHCANCEL                  = 1108;

const WS_EVENT_RESIZE                       = 1201;

const WS_EVENT_DEBUG_TEXT                   = 1301;

const WS_EVENT_TEXT_SIZE                    = 1401;

const WS_EVENT_GRAPH_COUNT_UPDATE           = 1501;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Utility functions
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////


function toColor(num)
{
    var a = (num & 0xFF) / 255;
    var b = (num & 0xFF00) >>> 8;
    var g = (num & 0xFF0000) >>> 16;
    var r = (num & 0xFF000000) >>> 24;
    return "rgba(" + [r, g, b, a].join(",") + ")";
}

var clipCount = 1;

function getUniqueClipPathID()
{
    if(clipCount++>100000)
	clipCount = 1;
    return 'ClipPathID_' + clipCount;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Audio Functions
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////





var audioContext = new (window.AudioContext || window.webkitAudioContext)()


function loadSound(filename)
{
    var sound = {volume: 1, audioBuffer: null}
 
    var ajax = new XMLHttpRequest()
    ajax.open("GET", filename, true)
    ajax.responseType = "arraybuffer"
    ajax.onload = function()
    {
	audioContext.decodeAudioData
	(
	    ajax.response,
	    function(buffer)
	    {
		sound.audioBuffer = buffer
	    },
	    function(error)
	    {
	    //  debugger
	    }
	)
    }
 
    ajax.onerror = function()
    {
    //	debugger
    }
 
    ajax.send();
    
    return sound;
}


function playSound(sound)
{    
    if(!sound.audioBuffer)
	return false;
 
    var source = audioContext.createBufferSource()
    if(!source)
	return false;
 
    source.buffer = sound.audioBuffer;
    if(!source.start)
	source.start = source.noteOn;
 
    if(!source.start)
	return false;
 
    var gainNode = audioContext.createGain();
    gainNode.gain.value = sound.volume;
    source.connect(gainNode);
    gainNode.connect(audioContext.destination);
    
    source.start(0);

    sound.gainNode = gainNode;

    return true;
}


function setSoundVolume(sound, volume)
{
    sound.volume = volume;
    
    if(sound.gainNode)
	sound.gainNode.gain.value = volume;
}


function stopSound(sound)
{
    setSoundVolume(sound, 0);
}


function mySound(amp, freq, ms)
{
    var sound = {volume: 1, audioBuffer: null}

    var num_samples_zero = 2048;
    var num_samples      = ms * audioContext.sampleRate / 1000;

    var myArrayBuffer = audioContext.createBuffer(1,                                  // Number of channels
					      num_samples + num_samples_zero,     // Number of samples
					      audioContext.sampleRate);               // samples per second
  
    for(var channel = 0; channel < myArrayBuffer.numberOfChannels; channel++)
    {
	var nowBuffering = myArrayBuffer.getChannelData(channel);
	for (var i = 0; i<myArrayBuffer.length; i++)
	{
	    var M_PI           =  3.141592768;
	    var waveform       =  Math.sin(i * 2.0 * M_PI * freq / audioContext.sampleRate);
	    var envelope       =  (i<num_samples/4)?1.0:0.0; ///*(i<num_samples_waveform) ? 1.0 : 0.0; */ 0.5*(1.0+cos(2.0*M_PI*(i-num_samples/2)/num_samples));
	    var amplitude_max  =  30000;
	    var value          =  amp * waveform * envelope * amplitude_max;
	    while(value<-32767)
		value += 32767;
	    while(value>32767)
		value -= 32767;
	    
	    nowBuffering[i] = value / 32767.0;
	}
    }

    sound.audioBuffer = myArrayBuffer

    
 return sound
}

//var yaySound              = loadSound("yay.wav")
var touchRecognizedSound  = mySound(0.20 /* amplitude */,  400.0 /*Hz*/, 300 /*milliseconds*/);
var entryAcceptedSound    = mySound(0.40 /* amplitude */,  800.0 /*Hz*/, 300 /*milliseconds*/);
var entryRejectedSound    = mySound(2.50 /* amplitude */,   50.0 /*Hz*/, 300 /*milliseconds*/);
var emptySound            = mySound(0.00 /* amplitude */,  400.0 /*Hz*/, 2 /*milliseconds*/);

audio_initialized = false;

function init_audio()
{
    if(!audio_initialized)
    {
	//playSound(emptySound);
	audioContext.resume();

	if(audioContext.state !== 'suspended')
	    audio_initialized = true;
    }
}


//function unlockAudioContext(audioCtx)
//{
//    if(audioCtx.state !== 'suspended')
//	return;
//
//    const b = document.body;
//    const events = ['touchstart','touchend', 'mousedown','keydown'];
//
//    events.forEach(e => b.addEventListener(e, unlock, false));
//    
//    function unlock() { audioCtx.resume().then(clean); 	playSound(yaySound);}
//    function clean() { events.forEach(e => b.removeEventListener(e, unlock)); 	playSound(yaySound);}
//}
//
//unlockAudioContext(audioContext);


var flashing_screen = 0;

function screenFlash()
{
    if(flashing_screen)
	return;

    flashing_screen=1;

    var w = window.innerWidth;
    var h = window.innerHeight;

    var rct = createNode('rect', {});
    rct.setAttribute('fill',   'rgba(255,255,255,1.0)');
    rct.setAttribute('x',      0);
    rct.setAttribute('y',      0);
    rct.setAttribute('width',  w);
    rct.setAttribute('height', h);
    
    svg.appendChild(rct);

    setTimeout(() => { svg.removeChild(rct); flashing_screen=0; }, 20);
    setTimeout(() => { flashing_screen=0; }, 2000);
}


function do_beep(beep_id)
{
    if(beep_id == BEEP_ID_TOUCH_RECOGNIZED)
    {
	playSound(touchRecognizedSound);
    }
    else if(beep_id == BEEP_ID_ENTRY_ACCEPTED)
    {
	playSound(entryAcceptedSound);
    }
    else if(beep_id == BEEP_ID_ENTRY_REJECTED)
    {
	screenFlash();
	playSound(entryRejectedSound);
    }
    else
    {
	console.log("Beeper ID " + beep_id + " isn't defined.\n");
    }
}




///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Layers API
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var font_size = 20;
var font_name = "Helvetica";
const text_sizing = document.getElementById("text_sizing");

text_sizing.setAttribute("font-size", font_size);

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// SVG and SVG Layers API
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
//var svgNS = svg.namespaceURI;
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");

const num_layers = 32;
var current_layer = 0;

var svg_layer_visible = new Array(num_layers);
var svg_layer         = new Array(num_layers);


function setCurrentLayer(layer)
{
    current_layer = layer;

    //console.log("setCurrentLayer " + layer + "\n");
}

//
// Clears out an existing layer.  "layer" is the number of the layer.
//
function clearLayer(layer)
{
    if(layer<0 || layer>31)
    {
	console.log("Layer " + layer + " is out of range in clearLayer().\n");
	return;
    }

    var layer_visible = svg_layer_visible[layer];

    if(layer_visible)
	makeLayerHidden(layer);
    var empty_layer = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svg_layer[layer] = empty_layer;

    if(layer_visible)
	makeLayerVisible(layer);

    //console.log("clearLayer on layer " + layer + "\n");
}

function makeLayerVisible(layer)
{
    if(layer<0 || layer>31)
    {
	console.log("Layer " + layer + " is out of range in makeLayerVisible().\n");
	return;
    }

    //console.log("makeLayerVisible(" + layer + ");\n");
    
    // If layer is not visible, add it to the top-level SVG.
    if(!svg_layer_visible[layer])
    {
	svg_layer_visible[layer] = true;
	svg.appendChild(svg_layer[layer]);
	//console.log("makeLayerVisible " + layer + "\n");
    }
    else
    {
	//console.log("makeLayerVisible " + layer + " on already visible layer\n");
	svg.removeChild(svg_layer[layer]);
	svg.appendChild(svg_layer[layer]);
    }
}

function makeLayerHidden(layer)
{
    if(layer<0 || layer>31)
    {
	console.log("Layer " + layer + " is out of range in makeLayerHidden().\n");
	return;
    }

    //console.log("makeLayerHidden(" + layer + ");\n");

    // If layer is visible, remove it from the top-level SVG.
    if(svg_layer_visible[layer])
    {
	svg_layer_visible[layer] = false;
	svg.removeChild(svg_layer[layer]);
	//console.log("makeLayerHidden " + layer + "\n");
    }
    else
    {
	//console.log("makeLayerHidden " + layer + " on already hidden layer\n");
    }
}


//
// Sets visibility of all layers.  Normally, only the first few layers
// are visible, and the last layers are hidden.  Hidden layers are for
// saving drawings and for drawing new things that can be swapped out
// for the visible layers without flicker.
//
function setLayerVisibility(layermask)
{
    for(var layer=0; layer<num_layers; layer++)
    {
	if(layermask & (1<<layer))
	    makeLayerVisible(layer);
	else
	    makeLayerHidden(layer);
    }
}



//
// Move contents of Layer "layer_from" to the Layer "layer_to", with the new "layer_to"
// having the same visibility as "layer_to" had before.  So if "layer_to" was previosly
// visible, it's atomically replaced with "layer_from".  At the end, "layer_from" is
// cleared, and is hidden.
//
function moveLayerToLayer(layer_from, layer_to)
{
    if(layer_from<0 || layer_to<0 || layer_from>31 || layer_to>31)
    {
	console.log("One of layers layer_from=" + layer_from + " or layer_to=" + layer_to + " is out of range in swapLayerNumbers().\n");
	return;
    }

    //console.log("move contents of layer " + layer_from + " to layer " + layer_to + "\n"); 
    
    var to_visible   = svg_layer_visible[layer_to];
    var from_visible = svg_layer_visible[layer_from];
    if(to_visible && from_visible)
    {
	svg.removeChild(svg_layer[layer_to]);
    }
    else if(to_visible  && !from_visible)
    {
	svg_layer[layer_to].replaceWith(svg_layer[layer_from]);
    }
    else if(from_visible && !to_visible)
    {
	svg.removeChild(svg_layer[layer_from]);
    }
	
    svg_layer[layer_to]   = svg_layer[layer_from];
    svg_layer[layer_from] = document.createElementNS("http://www.w3.org/2000/svg", "svg");

    svg_layer_visible[layer_from] = false;
}


//
// Make a second copy of the contents of Layer "layer_from" and move it to the Layer
// "layer_to", with "layer_to" keeping its visibility.  So if "layer_to" was previosly
// visible, it's atomically replaced with "layer_from".  "layer_from" retains its
// value and its visibility.
//
function copyLayerToLayer(layer_from, layer_to)
{
    if(layer_from<0 || layer_to<0 || layer_from>31 || layer_to>31)
    {
	console.log("One of layers layer_from=" + layer_from + " or layer_to=" + layer_to + " is out of range in swapLayerNumbers().\n");
	return;
    }

    //console.log("copy contents of layer " + layer_from + " to layer " + layer_to + "\n"); 

    var deep = true;
    var copy = svg_layer[layer_from].cloneNode(deep);

    var to_visible   = svg_layer_visible[layer_to];
    if(to_visible)
    {
	svg_layer[layer_to].replaceWith(copy);
    }

    svg_layer[layer_to]   = copy;
}




//
// Initialize SVG layers
//
for(var layer=0; layer<num_layers; layer++)
{
    svg_layer[layer]         = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svg_layer_visible[layer] = false;
}

//makeLayerVisible(current_layer);



//function mouseMoveHandler()
//{
//    
//}
//
//svg.onmousemove = mouseMoveHandler;
//



function createNode(n, v)
{
    n = document.createElementNS("http://www.w3.org/2000/svg", n);
    for (var p in v)
        n.setAttributeNS(null, p, v[p]);
    return n
}


function createTextNode(t, v)
{
    var n = document.createElementNS("http://www.w3.org/2000/svg", 'text');
    for (var p in v)
        n.setAttribute(p, v[p]);
    n.textContent = t;
    return n
}


function get_appropriate_ws_url(extra_url)
{
    var pcol;
    var u = document.URL;
    
    /*
     * We open the websocket encrypted if this page came on an
     * https:// url itself, otherwise unencrypted
     */
    
    if (u.substring(0, 5) === "https")
    {
	pcol = "wss://";
	u = u.substr(8);
    }
    else
    {
	pcol = "ws://";

	if (u.substring(0, 4) === "http")
	    u = u.substr(7);
    }

    u = u.split("/");

    /* + "/xxx" bit is for IE10 workaround */

    return pcol + u[0] + "/" + extra_url;
}


//function new_ws(urlpath, protocol)
//{
//    return new WebSocket(urlpath, protocol);
//}

var ws;



function debugText(text)
{
    var utf8 = unescape(encodeURIComponent(text));

    const text_length = utf8.length;

    var size = Math.ceil((2 * 4 + text_length + 1) / 4) * 4;
    const buffer = new ArrayBuffer(size);
    const array  = new Int32Array(buffer);

    array[0] = 1301; // WS_EVENT_DEBUG_TEXT
    array[1] = size;

    const u8array  = new Uint8Array(buffer);

    for(var i=0; i<utf8.length; i++)
	u8array[8 + i] = utf8.charCodeAt(i);

    u8array[8 + utf8.length] = 0;
    
    ws.send(buffer);
}


function start()
{
    //ws = new WebSocket("ws://rf", "lws-bxb");
    //ws = new WebSocket(location.origin.replace(/^http/, 'ws'), "lws-bxb");
    ws = new WebSocket(get_appropriate_ws_url(""), "lws-bxb");
    //ws = new WebSocket(get_appropriate_ws_url(""));

    ws.binaryType = 'arraybuffer';

    try
    {
	ws.onopen = (e) =>
	{
	    if(e.target.readyState !== WebSocket.OPEN)
		return;
	    
	    //console.log("In onopen function.\n");
	    //document.getElementById("m").disabled = 0;
	    //document.getElementById("b").disabled = 0;
	    displayWindowSize();
	    //debugText("Javascript Drawing Client, Copyright (C) 2026 Bit by Bit Signal Processing"); 

	    console.log("Javascript Drawing Client, Copyright (C) 2026 Bit by Bit Signal Processing");
	    //var bb = text_sizing.getBBox();

	    //debugText("Bounding box is w=" + bb.width + " h=" + bb.height);

	    //text_sizing.textContent = "text";

	    //bb = text_sizing.getBBox();
	    
	    //debugText("Bounding box is w=" + bb.width + " h=" + bb.height);
	};


	ws.onclose = (e) =>
	{
	    //console.log("In onopen function.\n");
	    //document.getElementById("m").disabled = 0;
	    //document.getElementById("b").disabled = 0;
	    //displayWindowSize();
	    //debugText("Javascript Drawing Client, Copyright (C) 2026 Bit by Bit Signal Processing"); 

	    console.log("Thank you for using Javascript Drawing Client, Copyright (C) 2026 Bit by Bit Signal Processing");
	    //var bb = text_sizing.getBBox();

	    //debugText("Bounding box is w=" + bb.width + " h=" + bb.height);

	    //text_sizing.textContent = "text";

	    //bb = text_sizing.getBBox();
	    
	    //debugText("Bounding box is w=" + bb.width + " h=" + bb.height);
	};


	
	ws.onmessage = function got_packet(msg)
	{
	    //console.log("got packet\n");
	    var buffer32 = new Int32Array(msg.data);
	    var bufferF  = new Float32Array(msg.data);
	    //var s = String.fromCharCode.apply(null, buffer8);
	    //for (var i=0; i < 12; i++) {
	    //	console.log("byte[" + i + "] = " + s[i] + " (" + buffer[i] + ")\n");
	    //}
	    //console.log("Got command packet type=" + buffer32[0] + " length=" + buffer32[1] + " length_rounded_up=" + msg.data.byteLength + "\n");

	    switch(buffer32[0])
	    {
		case WS_COMMAND_BEEP:
		{
		    var beep_id = buffer32[2];
		    do_beep(beep_id);

		    //console.log("got beep, id=" + beep_id + "\n");

		    break;
		}
		
		case WS_COMMAND_RECTANGLE:
		{
		    var rct = createNode('rect', {});

		    //console.log("got rectangle, color=" + toColor(buffer32[2]) + " x=" + bufferF[3] + " y=" + bufferF[4] + "\n");

		    rct.setAttribute('fill',   toColor(buffer32[2]));
		    rct.setAttribute('x',      bufferF[3]);
		    rct.setAttribute('y',      bufferF[4]);
		    rct.setAttribute('width',  bufferF[5]-bufferF[3]);
		    rct.setAttribute('height', bufferF[6]-bufferF[4]);

		    svg_layer[current_layer].appendChild(rct);
		    break;
		}
		
		case WS_COMMAND_TRIANGLE:
		{
		    var tri = createNode('polygon', {});

		    //console.log("got triangle, color=" + toColor(buffer32[2]) + " x=" + bufferF[3] + " y=" + bufferF[4] + "\n");

		    tri.setAttribute('fill',   toColor(buffer32[2]));
		    tri.setAttribute('points', [bufferF[3], bufferF[4], bufferF[5], bufferF[6], bufferF[7], bufferF[8]].join(" "));

		    svg_layer[current_layer].appendChild(tri);
		    break;
		}
		
		case WS_COMMAND_CIRCLE:
		{
		    var cir = createNode('circle', {});

		    console.log("got circle, color=" + toColor(buffer32[2]) + " x=" + bufferF[3] + " y=" + bufferF[4] + "current_layer=" + current_layer + "\n");

		    cir.setAttribute('fill',   toColor(buffer32[2]));
		    cir.setAttribute('cx',     bufferF[3]);
		    cir.setAttribute('cy',     bufferF[4]);
		    cir.setAttribute('r',      bufferF[5]);

		    svg_layer[current_layer].appendChild(cir);
		    break;
		}

		case WS_COMMAND_LINE:
		{
		    var lin = createNode('line', {});

		    //console.log("got line, color=" + toColor(buffer32[2]) + " width=" + bufferF[3] + " x1=" + bufferF[4] + " y1=" + bufferF[5] + " x2=" + bufferF[6] + " y2=" + bufferF[7] + "\n");

		    lin.setAttribute('stroke',       toColor(buffer32[2]));
		    lin.setAttribute('stroke-width', bufferF[3]);
		    lin.setAttribute('x1',           bufferF[4]);
		    lin.setAttribute('y1',           bufferF[5]);
		    lin.setAttribute('x2',           bufferF[6]);
		    lin.setAttribute('y2',           bufferF[7]);

		    svg_layer[current_layer].appendChild(lin);
		    break;
		}

		case WS_COMMAND_TEXT:
		{
		    var txt = createNode('text', {'cursor':'default'});  // cursor default makes text unselectable
 
		    var flags = buffer32[3];

		    //console.log("got text, color=" + toColor(buffer32[2]) + "flags=0x" + flags.toString(16) + " x=" + bufferF[4] + " y=" + bufferF[5] + "\n");

		    txt.setAttribute("font-family", font_name);
		    txt.setAttribute("font-size",   font_size);
		    txt.setAttribute('fill',        toColor(buffer32[2]));

		    var text = String.fromCodePoint.apply(null, buffer32.subarray(7, 7+buffer32[6]));
		    txt.textContent = text;

		    text_sizing.textContent = text;
		    var bb = text_sizing.getBBox();

		    var xoffset = 0;
		    var yoffset = 0;
		    
		    if(flags & DRAW_TEXT_X_RIGHT)
		    {
			xoffset = -bb.width;
		    }
		    else if(flags & DRAW_TEXT_X_CENTER)
		    {
			xoffset = -bb.width/2;
		    }
		    //else // DRAW_TEXT_X_LEFT
		    //{
		    //	xoffset = 0;
		    //}

		    if(flags & DRAW_TEXT_Y_TOP)
		    {
			yoffset = font_size; //-bb.y * 3/4;
		    }
		    else if(flags & DRAW_TEXT_Y_CENTER)
		    {
			yoffset = font_size/2; //-bb.y/4;
		    }
		    else // DRAW_TEXT_Y_BOTTOM
		    {
		    	yoffset = 0;//bb.y/4;
		    }

		    var centering_adjustment = 4 * font_size / 16;
		    yoffset -= centering_adjustment;
		    
		    if(flags & DRAW_TEXT_ROTATE_90_LEFT)
		    {
			txt.setAttribute('transform', 'rotate(-90 ' + bufferF[4] + ' ' + bufferF[5] + ')');
		    }
		    else if(flags & DRAW_TEXT_ROTATE_90_RIGHT)
		    {
			txt.setAttribute('transform', 'rotate(90 ' + bufferF[4] + ' ' + bufferF[5] + ')');
		    }
		    //else // DRAW_TEXT_ROTATE_0
		    //{
		    //}

		    //console.log("bb.width=" + bb.width + " bb.height=" + bb.height + " bb.x=" + bb.x + " bb.y=" + bb.y + " xoffset=" + xoffset + " yoffset=" + yoffset + "\n");

		    //console.log("Printing text to current_layer=" + current_layer + ":  \"" + text + "\".\n");

				
		    txt.setAttribute('x', bufferF[4] + xoffset);
		    txt.setAttribute('y', bufferF[5] + yoffset);

		    svg_layer[current_layer].appendChild(txt);
		    break;
		}

		case WS_COMMAND_MULTICOLORED_TEXT:
		{
		    var txt = createNode('text', {cursor:'default', visibility:'hidden'});  // cursor default makes text unselectable

		    var flags       = buffer32[MAX_COLORS + 2];
		    var x           = bufferF[MAX_COLORS + 3];
		    var y           = bufferF[MAX_COLORS + 4];
		    var text_length = buffer32[MAX_COLORS + 5];
		    
		    //console.log("got multicolored text, color0=" + toColor(buffer32[2]) + "color1=" + toColor(buffer32[3]) + "flags=0x" + flags.toString(16) + " x=" + x + " y=" + y + "\n");

		    txt.setAttribute("font-family", font_name);
		    txt.setAttribute("font-size",   font_size);

		    var fulltxt = buffer32.subarray(6+MAX_COLORS, 6+MAX_COLORS + text_length);

		    //console.log("buffer8=" + buffer8);
		    
		    var current_color = toColor(buffer32[2]);

		    for(;;)
		    {
			var pos = 0;

			// Find length before escape character, or end
			for(pos=0; pos<fulltxt.length; pos++)
			{
			    if(fulltxt[pos]>=10 && fulltxt[pos]<10 + MAX_COLORS)
				break;
			}

			var txtb = fulltxt.subarray(0, pos);

			//console.log("txtb=" + txtb + " (" + String.fromCharCode.apply(null, txtb) + ") color_index=" + color_index + " current_color=" + current_color + "\n");

			var tsp = createNode('tspan', {cursor:'default'});
			tsp.textContent = String.fromCodePoint.apply(null, txtb);;
			tsp.setAttribute('fill', current_color);
			txt.appendChild(tsp);

			if(pos>=fulltxt.length-1)
			    break;
			
			var color_index = fulltxt[pos] - 10;
			fulltxt = fulltxt.subarray(pos+1);

			if(color_index>=0 && color_index<MAX_COLORS)
			    current_color = toColor(buffer32[2+color_index]);
		    }

		    // Temporarily add the text to the top-level svg (hidden) so a bounding box can be calculated
		    svg.appendChild(txt);
		    var bb = txt.getBBox();
		    svg.removeChild(txt);

		    txt.setAttribute('visibility', 'visible');
		    
		    var xoffset = 0;
		    var yoffset = 0;
		    
		    if(flags & DRAW_TEXT_X_RIGHT)
		    {
			xoffset = -bb.width;
		    }
		    else if(flags & DRAW_TEXT_X_CENTER)
		    {
			xoffset = -bb.width/2;
		    }
		    //else // DRAW_TEXT_X_LEFT
		    //{
		    //	xoffset = 0;
		    //}

		    if(flags & DRAW_TEXT_Y_TOP)
		    {
			yoffset = font_size; //-bb.y * 3/4;
		    }
		    else if(flags & DRAW_TEXT_Y_CENTER)
		    {
			yoffset = font_size/2; //-bb.y/4;
		    }
		    else // DRAW_TEXT_Y_BOTTOM
		    {
		    	yoffset = 0; //bb.y/4;
		    }

		    var centering_adjustment = 4 * font_size / 16;
		    yoffset -= centering_adjustment;

		    if(flags & DRAW_TEXT_ROTATE_90_LEFT)
		    {
			txt.setAttribute('transform', 'rotate(-90 ' + x + ' ' + y + ')');
		    }
		    else if(flags & DRAW_TEXT_ROTATE_90_RIGHT)
		    {
			txt.setAttribute('transform', 'rotate(90 ' + x + ' ' + y + ')');
		    }
		    //else // DRAW_TEXT_ROTATE_0
		    //{
		    //}

		    //console.log("bb.width=" + bb.width + " bb.height=" + bb.height + " bb.x=" + bb.x + " bb.y=" + bb.y + " xoffset=" + xoffset + " yoffset=" + yoffset + "\n");
		    
		    txt.setAttribute('x', x + xoffset);
		    txt.setAttribute('y', y + yoffset);

		    svg_layer[current_layer].appendChild(txt);
		    break;
		}

		case WS_COMMAND_SVG:
		{
		    var buffer8 = new Uint8Array(msg.data);
		    
		    //var text = String.fromCharCode.apply(null, buffer8.subarray(28, 28 + buffer32[6]));

		    var text = new TextDecoder().decode(buffer8.subarray(28, 28 + buffer32[6]));
		    
		    var offset_x    = bufferF[2];
		    var offset_y    = bufferF[3];
		    var draw_width  = bufferF[4];
		    var draw_height = bufferF[5];

		    var web_svg = createNode('svg', {});
		    
		    web_svg.setAttribute('x', offset_x);
		    web_svg.setAttribute('y', offset_y);
		    
		    if(draw_width!=0)
			web_svg.setAttribute('width',  draw_width);
		    
		    if(draw_height!=0)
			web_svg.setAttribute('height', draw_height);

		    //web_svg.textContent = text;
		    web_svg.innerHTML = text;

		    //console.log("text content was \"" + web_svg.textContent + "\"\n");
		    //console.log("text content was \"" + web_svg.innerHTML + "\"\n");

                    var svg_inner = web_svg.querySelector('svg');

                    svg_inner.setAttribute('width', "100%");
                    svg_inner.setAttribute('height', "100%");
		    
		    svg_layer[current_layer].appendChild(web_svg);
			    
		    //var cir = createNode('circle', {});
		    //cir.setAttribute('fill',   'rgba(255,255,255,1.0)');
		    //cir.setAttribute('cx',     400);
		    //cir.setAttribute('cy',     400);
		    //cir.setAttribute('r',      400);
		    //svg_layer[current_layer].appendChild(cir);
		    
		    //console.log("SVG file from text \"" + text + "\" added with width=" + draw_width + " and height=" + draw_height + " to layer " + current_layer +".\n");		    
		    break;
		}


		case WS_COMMAND_GRAPH_DATA_OLD:
		{
		    var color        = toColor(buffer32[2]);
		    var num_points   = buffer32[3];
		    var x_start      = bufferF[4];
		    var x_step       = bufferF[5];
		    var x_at_left    = bufferF[6];
		    var x_at_right   = bufferF[7];
		    var y_at_bottom  = bufferF[8];
		    var y_at_top     = bufferF[9];
		    var x_offset     = bufferF[10];
		    var y_offset     = bufferF[11];
		    var width        = bufferF[12];
		    var height       = bufferF[13];
		    var graph_count  = buffer32[14];

		    last_graph_count_received = graph_count;
		    send_graph_count_update_event();
		    
		    var x_ratio = width  / (x_at_right - x_at_left);
		    var y_ratio = height / (y_at_top   - y_at_bottom);
		    		    
		    var topgrp = createNode('g', {});
		    var grp = createNode('g', {});
		    var clip = createNode('clipPath', {});
		    var rct = createNode('rect', {});

		    var clipID = getUniqueClipPathID();

		    clip.setAttribute('id',    clipID);
		    rct.setAttribute('x',      x_offset);
		    rct.setAttribute('y',      y_offset);
		    rct.setAttribute('width',  width);
		    rct.setAttribute('height', height);
		    clip.appendChild(rct);
		    topgrp.appendChild(clip);
		    topgrp.setAttribute('clip-path', 'url(#' + clipID + ')');

		    topgrp.appendChild(grp);

		    grp.setAttribute('stroke',       color);
		    grp.setAttribute('stroke-width', 1);
		    grp.setAttribute('fill',         color);
		    grp.setAttribute('transform',
				     'translate(' + (x_offset + (x_start - x_at_left) * x_ratio) + ',' +
				     (y_offset + y_at_top * y_ratio) +
				     ') scale(' + (x_ratio*x_step) + ', ' +
				     (-y_ratio) + ')');
		    
		    var x0          = 0;
		    var ymin0       = bufferF[15+2*x0];
		    var ymax0       = bufferF[16+2*x0];		    
		    
		    for(var x1=1; x1<num_points; x1++)
		    {
			var ymin1     = bufferF[15+2*x1];
			var ymax1     = bufferF[16+2*x1];

			if(ymin0==ymax0)
			{
			    if(ymin1==ymax1)
			    {
				// Draw line, since min and max are the same on both sides
				var lin = document.createElementNS("http://www.w3.org/2000/svg", 'line');
				lin.setAttribute('x1',           x0);
				lin.setAttribute('y1',           ymin0);
				lin.setAttribute('x2',           x1);
				lin.setAttribute('y2',           ymin1);
				lin.setAttribute('vector-effect', 'non-scaling-stroke');
				grp.appendChild(lin);
			    }
			    else
			    {
				// Draw triangle
				var poly = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
				var poly_points = [x0, ymin0, x1, ymin1, x1, ymax1].join(" ");
				poly.setAttribute('points', poly_points);
				poly.setAttribute('vector-effect', 'non-scaling-stroke');
				grp.appendChild(poly);
			    }
			}
			else
			{
			    if(ymin1==ymax1)
			    {
				// Draw triangle
				var poly = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
				var poly_points = [x0, ymin0, x1, ymin1, x0, ymax0].join(" ");
				poly.setAttribute('points', poly_points);
				poly.setAttribute('vector-effect', 'non-scaling-stroke');
				grp.appendChild(poly);
			    }
			    else
			    {
				// Draw quadrilateral
				var poly = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
				var poly_points = [x0, ymin0, x1, ymin1, x1, ymax1, x0, ymax0].join(" ");
				poly.setAttribute('points', poly_points);
				poly.setAttribute('vector-effect', 'non-scaling-stroke');
				grp.appendChild(poly);
			    }
			}

			x0    = x1;
			ymin0 = ymin1;
			ymax0 = ymax1;
		    }

		    svg_layer[current_layer].appendChild(topgrp);

		    //console.log("WS_COMMAND_GRAPH_DATA with " + num_points + " points and width=" + width + " and height=" + height + ".\n");
		    //console.log("poly_points= " + poly_points + "\n");
		    //console.log("x_start=" + x_start + " x_ratio=" + x_ratio + " x_at_left=" + x_at_left + " x_offset=" + x_offset + "\n");
		    //console.log("x_at_right=" + x_at_right + " x_at_left=" + x_at_left + " width=" + width + "\n");

		    break;
		}
		
		case WS_COMMAND_GRAPH_DATA:
		{
		    var color        = toColor(buffer32[2]);
		    var num_points   = buffer32[3];
		    var x_start      = bufferF[4];
		    var x_step       = bufferF[5];
		    var x_at_left    = bufferF[6];
		    var x_at_right   = bufferF[7];
		    var y_at_bottom  = bufferF[8];
		    var y_at_top     = bufferF[9];
		    var x_offset     = bufferF[10];
		    var y_offset     = bufferF[11];
		    var width        = bufferF[12];
		    var height       = bufferF[13];
		    var graph_count  = buffer32[14];

		    last_graph_count_received = graph_count;
		    send_graph_count_update_event();

		    var x_ratio = width  / (x_at_right - x_at_left);
		    var y_ratio = height / (y_at_top   - y_at_bottom);
		    		    
		    var grp = createNode('g', {});
		    var clip = createNode('clipPath', {});
		    var rct = createNode('rect', {});

		    var clipID = getUniqueClipPathID();

		    
		    clip.setAttribute('id',    clipID);
		    rct.setAttribute('x',      x_offset);
		    rct.setAttribute('y',      y_offset);
		    rct.setAttribute('width',  width);
		    rct.setAttribute('height', height);

		    clip.appendChild(rct);
		    grp.appendChild(clip);

		    // NAN sent in bufferF tells us not to draw a point.  So divide the points
		    // into sections with separate polygons for each.
		    var start = 0;
		    var newstart = 0;
		    for(;;)
		    {
			point_list = [];
			start = newstart;
			newstart = num_points;
			while(start<num_points-1 && isNaN(bufferF[15+2*start]) && isNaN(bufferF[16+2*start]))
			    start++;
			if(start>=num_points-1)
			    break;
			
			var poly = createNode('polygon', {});
			//var poly_points = "";

			for(var pt=start; pt<num_points; pt++)
			{
			    var x_d        = pt * x_step + x_start;
			    var ymin_d     = bufferF[15+2*pt];

			    if(isNaN(ymin_d))
			    {
				ymin_d = bufferF[16+2*pt];
				
				if(isNaN(ymin_d))
				{
				    newstart = pt;
				    break;
				}
			    }
			    
			    var x          = (x_d - x_at_left) * x_ratio + x_offset
			    var ymin       = (y_at_top - ymin_d)  * y_ratio + y_offset;
			    
			    //var poly_points = [poly_points, x, ymin].join(" ");
			    point_list.push(x, ymin);
			}

			for(var pt=newstart-1; pt>=start; pt--)
			{
			    var x_d        = pt * x_step + x_start;
			    var ymax_d     = bufferF[16+2*pt];
			    
			    if(isNaN(ymax_d))
			    {
				ymax_d = bufferF[15+2*pt];  // Code above assures this isn't NaN.
			    }			    
			    
			    var x          = (x_d - x_at_left) * x_ratio + x_offset
			    var ymax       = (y_at_top - ymax_d)  * y_ratio + y_offset;
			    
			    //var poly_points = [poly_points, x, ymax].join(" ");
			    point_list.push(x, ymax);
			}

			var poly_points = point_list.join(" ");
			
			poly.setAttribute('fill', color);
			poly.setAttribute('stroke', color);
			poly.setAttribute('points', poly_points);
			poly.setAttribute('clip-path', 'url(#' + clipID + ')');

			grp.appendChild(poly);
		    }
		    
		    svg_layer[current_layer].appendChild(grp);

		    //console.log("WS_COMMAND_GRAPH_DATA with " + num_points + " points and width=" + width + " and height=" + height + ".\n");
		    //console.log("poly_points= " + poly_points + "\n");
		    //console.log("x_start=" + x_start + " x_ratio=" + x_ratio + " x_at_left=" + x_at_left + " x_offset=" + x_offset + "\n");
		    //console.log("x_at_right=" + x_at_right + " x_at_left=" + x_at_left + " width=" + width + "\n");

		    break;
		}
		
		case WS_COMMAND_FONT:
		{
		    var buffer8 = new Uint8Array(msg.data);

		    font_name = String.fromCharCode.apply(null, buffer8.subarray(16, 16 + buffer32[3]));
		    font_size = buffer32[2];

		    text_sizing.setAttribute("font-family", font_name);
		    text_sizing.setAttribute("font-size",   font_size);
		    
		    //console.log("Command set font to \"" + font_name + "\" size " + font_size + ".\n");
		    break;
		}

		case WS_COMMAND_TEXT_SIZE:
		{
		    var buffer8 = new Uint8Array(msg.data);
		    var text = String.fromCharCode.apply(null, buffer8.subarray(12, 12 + buffer32[2]));
		    
		    text_sizing.textContent = text;

		    var bb = text_sizing.getBBox();

		    //console.log("Returning text size for font \"" + font_name + "\" size " + font_size + " text \"" + text + "\" to be width=" + bb.width + " height=" + bb.height + " x=" + bb.x + " y=" + bb.y + ".\n");
		    
		    //
		    // Send an WS_EVENT_TEXT_SIZE as a response
		    //
		    var size = 4 * 4;
		    const buffer   = new ArrayBuffer(size);
		    const array32  = new Int32Array(buffer);
		    const arrayF   = new Float32Array(buffer);

		    array32[0] = 1401; // WS_EVENT_TEXT_SIZE
		    array32[1] = size;
		    arrayF[2] = bb.width;
		    arrayF[3] = font_size; //bb.height; // (bb.height - bb.y)/2;

		    ws.send(buffer);
		    break;
		}

		case WS_COMMAND_SET_CURRENT_LAYER:
		{
		    var layer = buffer32[2];
		    setCurrentLayer(layer);
		    break;
		}

		case WS_COMMAND_CLEAR_LAYER:
		{
		    var layer = buffer32[2];
		    clearLayer(layer);
		    break;
		}

		case WS_COMMAND_MAKE_LAYER_VISIBLE:
		{
		    var layer = buffer32[2];
		    makeLayerVisible(layer);
		    break;
		}

		case WS_COMMAND_MAKE_LAYER_HIDDEN:
		{
		    var layer = buffer32[2];
		    makeLayerHidden(layer);
		    break;
		}

		case WS_COMMAND_SET_LAYER_VISIBILITY:
		{
		    var layer_mask = buffer32[2];
		    setLayerVisibility(layer_mask);
		    break;
		}

		case WS_COMMAND_COPY_LAYER_TO_LAYER:
		{
		    var layer_from = buffer32[2];
		    var layer_to   = buffer32[3];
		    copyLayerToLayer(layer_from, layer_to);
		    break;
		}

		case WS_COMMAND_MOVE_LAYER_TO_LAYER:
		{
		    var layer_from = buffer32[2];
		    var layer_to   = buffer32[3];
		    moveLayerToLayer(layer_from, layer_to);
		    break;
		}

		
		default:
		console.log("Got an unhandled command packet of type " + buffer32[0] + "\n");
	    }
	    //console.log("end got packet\n");
	};
	
	ws.onclose = function()
	{
	    //document.getElementById("m").disabled = 1;
	    //document.getElementById("b").disabled = 1;
	};
    }
    catch(exception)
    {
	alert("<p>Error " + exception);  
    }
    
    function sendmsg()
    {
	//ws.send(document.getElementById("m").value);
	//document.getElementById("m").value = "";
    }
    
    //document.getElementById("b").addEventListener("click", sendmsg);
    
    function displayWindowSize(){
	// Get width and height of the window excluding scrollbars
	//var w = document.documentElement.clientWidth;
	//var h = document.documentElement.clientHeight;

	//
	// This is done after a 200ms timeout, since it is called from
	// a resize event and the resize event on iOS is sometimes
	// called before values for innerWidth and innerHeight have
	// been (asynchronously) calculated.
	//

	setTimeout(() =>
		   {
	
		       svg.setAttribute('width', "100%");
		       svg.setAttribute('height', "100%");

		       //var bBox = svg.getBBox();
		       
		       //var w = bBox.width;
		       //var h = bBox.height;
		       
		       var w = window.innerWidth;
		       var h = window.innerHeight;
		       
		       svg.setAttribute('viewBox', "0 0 " + w + " " + h);
		       
		       //ws.send("Width: " + w + ", " + "Height: " + h);
		       
		       //if(rct)
		       //svg.removeChild(rct);
		       
		       //rct = createNode('rect', { id:'rct', width:20, height:20, fill:'#ff0000' });
		       //rct.setAttribute('x', w/2-10);
		       //rct.setAttribute('y', h/2-10);
		       //svg.appendChild(rct); 
		       
		       var size = 4 * 4;
		       const buffer = new ArrayBuffer(size);
		       const array  = new Int32Array(buffer);
		       
		       array[0] = 1201; // WS_EVENT_RESIZE
		       array[1] = size;
		       array[2] = w;
		       array[3] = h;
		       
		       ws.send(buffer);
		   }, 200);

    }
    
    // Attaching the event listener function to window's resize event
    window.addEventListener("resize", displayWindowSize);    
}


//var theta = 0;
//
//function animation_function()
//{
//    theta += .04;
//    if(theta>2*3.1415927)
//	theta -= 2*3.1415927;
//
//    var w = window.innerWidth;
//    var h = window.innerHeight;
//    
//    if(rct)
//    {
//	rct.setAttribute('x', w/2-10 + 80 * Math.cos(theta));
//	rct.setAttribute('y', h/2-10 + 80 * Math.sin(theta));
//    }
//}
//
//
//setInterval(animation_function, 30); // every N milliseconds
//





document.body.appendChild(svg);


document.addEventListener("DOMContentLoaded",
			  start,
			  false);


//
// Prevent right-mouse menus and text selections.  Might also want to add event.stopPropagation() ?
//
document.addEventListener('contextmenu', event => event.preventDefault());
document.addEventListener('selectstart', event => event.preventDefault());


button_down = false;
//mousex = -1;
//mousey = -1;


function send_graph_count_update_event()
{
    var size = 3 * 4;
    const buffer = new ArrayBuffer(size);
    const array  = new Int32Array(buffer);
    array[0] = WS_EVENT_GRAPH_COUNT_UPDATE;
    array[1] = size;
    array[2] = last_graph_count_received;
    ws.send(buffer);
}

pt = svg.createSVGPoint();

function SVG_coords(e)
{
    pt.x = e.clientX;
    pt.y = e.clientY;

    var cursorpt = pt.matrixTransform(svg.getScreenCTM().inverse());

    return cursorpt;
}


svg.addEventListener('wheel', (e) =>
		     {
			 if(e.deltaY==0)
			     return;
			 
			 //ws.send("Wheel motion by " + e.deltaY + ".");
			 var size = 5 * 4;
			 const buffer = new ArrayBuffer(size);
			 const array  = new Int32Array(buffer);

			 //var svgrect = svg.getBoundingClientRect();
			 //mousex = e.x - svgrect.left;
			 //mousey = e.y - svgrect.top;

			 var coords = SVG_coords(e);

			 array[0] = WS_EVENT_WHEEL;
			 array[1] = size;
			 array[2] = coords.x;
			 array[3] = coords.y;
			 array[4] = (e.deltaY>0) ? 1 : -1;

			 //console.log("wheel event step=" + array[4] + " x=" + array[2] + " y=" + array[3] + "\n");				    
			 
			 ws.send(buffer);

		     	 e.preventDefault();
			 e.stopPropagation();
		     });


svg.addEventListener('mouseup', (e) =>
		     {
			 button_down &= ~(1<<e.button);  // e.button is 0, 1, 2

			 //var svgrect = svg.getBoundingClientRect();
			 //mousex = e.x - svgrect.left;
			 //mousey = e.y - svgrect.top;

			 var coords = SVG_coords(e);
			 
			 var size = 5 * 4;
			 const buffer = new ArrayBuffer(size);
			 const array  = new Int32Array(buffer);

			 array[0] = WS_EVENT_BUTTON_UP;
			 array[1] = size;
			 array[2] = coords.x; //mousex;
			 array[3] = coords.y; //mousey;
			 array[4] = e.button;

			 //console.log("Sending mouseup event, button=" + e.button + "\n");
			 
			 ws.send(buffer);

		     	 e.preventDefault();
			 e.stopPropagation();
		     });


svg.addEventListener('mousedown', (e) =>
		     {
			 init_audio();
			 
			 button_down |= (1<<e.button);  // e.button is 0, 1, 2

			 //var svgrect = svg.getBoundingClientRect();
			 //mousex = e.x - svgrect.left;
			 //mousey = e.y - svgrect.top;

			 var coords = SVG_coords(e);

			 var size = 5 * 4;
			 const buffer = new ArrayBuffer(size);
			 const array  = new Int32Array(buffer);

			 array[0] = WS_EVENT_BUTTON_DOWN;
			 array[1] = size;
			 array[2] = coords.x; // mousex;
			 array[3] = coords.y; // mousey;
			 array[4] = e.button;

			 //console.log("Sending mousedown event, button=" + e.button + "\n");

			 ws.send(buffer);

		     	 e.preventDefault();
			 e.stopPropagation();
		     });



svg.addEventListener('mousemove', (e) =>
		     {
			 if(button_down)
			 {
			     //console.log("sending mousemove event with button_down=" + button_down + "\n");
			     //var svgrect = svg.getBoundingClientRect();
			     //mousex = e.x - svgrect.left;
			     //mousey = e.y - svgrect.top;

			     var coords = SVG_coords(e);

			     var size = 4 * 4;
			     const buffer = new ArrayBuffer(size);
			     const array  = new Int32Array(buffer);
			     
			     array[0] = WS_EVENT_MOUSE_MOVE;
			     array[1] = size;
			     array[2] = coords.x;
			     array[3] = coords.y;

			     //console.log("mousemove x=" + array[2] + " y=" + array[3] + "\n");
			     
			     ws.send(buffer);
			     
		     	     e.preventDefault();
			     e.stopPropagation();
			 }

		     });


//document.body.addEventListener('touchstart', function(){playSound(emptySound)}, false)
//document.body.addEventListener('touchstart', init_audio, false)


svg.addEventListener('touchstart', (e) =>
		     {
			 init_audio();

			 //var svgrect = svg.getBoundingClientRect();
			 //console.log("touchstart with " + e.touches.length + " touches.");
			 //for(var i=0; i<e.touches.length; i++)
			 //{
			 //    var touch = e.touches.item(i);
			 //    console.log("  touch " + i + " is at (" + (touch.clientX-svgrect.left) + ", " + (touch.clientY-svgrect.top) + ")");
			 //}
			 
			 var size = (3 + 2*e.touches.length) * 4;
			 const buffer = new ArrayBuffer(size);
			 const array  = new Int32Array(buffer);

			 array[0] = WS_EVENT_TOUCHSTART;
			 array[1] = size;
			 array[2] = e.touches.length;

			 for(var i=0; i<e.touches.length; i++)
			 {
			     var touch = e.touches.item(i);
			     var coords = SVG_coords(touch);

			     array[3+2*i] = coords.x; //touch.clientX - svgrect.left;
			     array[4+2*i] = coords.y; //touch.clientY - svgrect.top;
			 }

			 ws.send(buffer);
			 
		     	 e.preventDefault();
			 e.stopPropagation();
		     });



svg.addEventListener('touchmove', (e) =>
		     {
			 //var svgrect = svg.getBoundingClientRect();
			 //console.log("touchmove.");
			 //for(var i=0; i<e.touches.length; i++)
			 //{
			 //    var touch = e.touches.item(i);
			 //    console.log("  touch " + i + " is at (" + (touch.clientX-svgrect.left) + ", " + (touch.clientY-svgrect.top) + ")");
			 //}
			 
			 var size = (3 + 2*e.touches.length) * 4;
			 const buffer = new ArrayBuffer(size);
			 const array  = new Int32Array(buffer);

			 array[0] = WS_EVENT_TOUCHMOVE;
			 array[1] = size;
			 array[2] = e.touches.length;

			 for(var i=0; i<e.touches.length; i++)
			 {
			     var touch = e.touches.item(i);
			     var coords = SVG_coords(touch);
			     array[3+2*i] = coords.x; //touch.clientX - svgrect.left;
			     array[4+2*i] = coords.y; //touch.clientY - svgrect.top;
			 }

			 ws.send(buffer);

		     	 e.preventDefault();
			 e.stopPropagation();
		     });

svg.addEventListener('touchend', (e) =>
		     {
			 init_audio();

			 //var svgrect = svg.getBoundingClientRect();
			 //console.log("touchend with " + e.touches.length + " touches.");
			 //for(var i=0; i<e.touches.length; i++)
			 //{
			 //    var touch = e.touches.item(i);
			 //    console.log("  touch " + i + " is at (" + (touch.clientX-svgrect.left) + ", " + (touch.clientY-svgrect.top) + ")");
			 //}
			 
			 var size = (3 + 2*e.touches.length) * 4;
			 const buffer = new ArrayBuffer(size);
			 const array  = new Int32Array(buffer);

			 array[0] = WS_EVENT_TOUCHEND;
			 array[1] = size;
			 array[2] = e.touches.length;			 

			 for(var i=0; i<e.touches.length; i++)
			 {
			     var touch = e.touches.item(i);
			     var coords = SVG_coords(touch);
			     array[3+2*i] = coords.x; //touch.clientX - svgrect.left;
			     array[4+2*i] = coords.y; //touch.clientY - svgrect.top;
			 }

			 ws.send(buffer);
			 
		     	 e.preventDefault();
			 e.stopPropagation();
		     });




svg.addEventListener('touchcancel', (e) =>
		     {
			 //var svgrect = svg.getBoundingClientRect();
			 //console.log("touchcancel.");
			 //for(var i=0; i<e.touches.length; i++)
			 //{
			 //    var touch = e.touches.item(i);
			 //    console.log("  touch " + i + " is at (" + (touch.clientX-svgrect.left) + ", " + (touch.clientY-svgrect.top) + ")");
			 //}
			 
			 var size = (3 + 2*e.touches.length) * 4;
			 const buffer = new ArrayBuffer(size);
			 const array  = new Int32Array(buffer);

			 array[0] = WS_EVENT_TOUCHCANCEL;
			 array[1] = size;
			 array[2] = e.touches.length;

			 for(var i=0; i<e.touches.length; i++)
			 {
			     var touch = e.touches.item(i);
			     var coords = SVG_coords(touch);
			     array[3+2*i] = coords.x; //touch.clientX - svgrect.left;
			     array[4+2*i] = coords.y; //touch.clientY - svgrect.top;
			 }

			 ws.send(buffer);

			 e.preventDefault();
			 e.stopPropagation();
		     });



document.addEventListener('keydown', (e) =>
		     {
			 //console.log('Got keydown with key "' + e.key + '" and code "' + e.code + '"');
			 //if((e.key=='D' || e.key=='d') && e.ctrlKey )
			 //    console.log("Key was a ctrl-D");
			 //else if(e.key=='d')
			 //    console.log("Key was a d");
			 //else if(e.key=='D')
			 //    console.log("Key was a D");


			 if((e.key=='D' || e.key=='d' || e.key=='S' || e.key=='s') && e.ctrlKey )
			 {
		     	     e.preventDefault();
			     e.stopPropagation();

			     var w = window.innerWidth;
			     var h = window.innerHeight;

			     var rct = createNode('rect', {});
			     rct.setAttribute('fill',   'rgba(0,0,0,1.0)');
			     rct.setAttribute('x',      0);
			     rct.setAttribute('y',      0);
			     rct.setAttribute('width',  w);
			     rct.setAttribute('height', h);
			     svg.insertBefore(rct, svg.firstChild);
			     
			     var d = new Date();
			     var year      = ("0000" + d.getFullYear()).slice(-4);
			     var month     = ("0000" + d.getMonth()).slice(-2);
			     var day       = ("0000" + d.getDay()).slice(-2);
			     var hours     = ("0000" + d.getHours()).slice(-2);
			     var minutes   = ("0000" + d.getMinutes()).slice(-2);
			     var seconds   = ("0000" + d.getSeconds()).slice(-2);
			     var hiddenElement = document.createElement('a');
			     //var utf8      = new TextEncoder().encode(svg.outerHTML);
			     //const decoder = new TextDecoder('utf-8');
			     //var binary    = String.fromCharCode(...utf8);
			     //var binary = decoder.decode(utf8);
			     const binary = encodeURIComponent(svg.outerHTML).replace(/%([0-9A-F]{2})/g, function(match, p1) {
				 return String.fromCharCode(parseInt(p1, 16));
			     });

			     hiddenElement.href = 'data:image/svg+xml;base64,' + btoa(binary);
			     hiddenElement.target = '_blank';
			     hiddenElement.download = 'graph_' + year + '_' + month + '_' + day + '_at_' + hours + '.' + minutes + '.' + seconds + '.svg'; 
			     hiddenElement.click();
			     svg.removeChild(rct);
			 }
		     });

//console.log('Added keydown listener.');


 
//var audioCtx = new (window.AudioContext || window.webkitAudioContext);
//var sine = audioCtx.createOscillator();
//sine.connect(audioCtx.destination);
//sine.start();










//source.start();




/*
var resumeAudioContext = function() {
	// handler for fixing suspended audio context in Chrome	
	try {
		if (audioCtx && audioCtx.state === "suspended") {
			audioCtx.resume();
		}
	} catch (e) {
		// SoundJS context or web audio plugin may not exist
		console.error("There was an error while trying to resume the Web Audio context...");
		console.error(e);
	}
	// Should only need to fire once
	window.removeEventListener("click", resumeAudioContext);
};

window.addEventListener("click", resumeAudioContext);
*/



var close_timer;

function check_if_open()
{
    if(ws.readyState !== WebSocket.OPEN)
    {
	console.log("Graph Connection has closed.\n");
	
	clearInterval(close_timer);
	setLayerVisibility(1);  // Only layer 0 is visible
	clearLayer(0);


	var txt = createNode('text', {'cursor':'default'});  // cursor default makes text unselectable
 	txt.setAttribute("font-family", "Helvetica");
	txt.setAttribute("font-size",   40);
	txt.setAttribute('fill',        'red');
	text = "Graph Connection has Closed.";
	txt.textContent = text;
	text_sizing.textContent = text;
 	text_sizing.setAttribute("font-family", "Helvetica");
	text_sizing.setAttribute("font-size",   40);
	var bb = text_sizing.getBBox();

	var w = window.innerWidth;
	var h = window.innerHeight;
	var xoffset = -bb.width/2;
	var yoffset = -bb.y/4;
	txt.setAttribute('x', w/2 + xoffset);
	txt.setAttribute('y', h/2 + yoffset);

	svg_layer[0].appendChild(txt);
    }
    else
    {	
	send_graph_count_update_event();
    }

}

close_timer = setInterval(check_if_open, 1000);  // time is in ms.

/*
var id = 1;

function play_sound()
{
    //do_beep(BEEP_ID_TOUCH_RECOGNIZED);
    //do_beep(BEEP_ID_ENTRY_ACCEPTED);
    //do_beep(BEEP_ID_ENTRY_REJECTED);

    do_beep(id);

    id = (id % 3) + 1;
}

svg.addEventListener("mousedown", play_sound);
*/
