<script>
	export let isAnimated = false;

	import FilePicker from './FilePicker.svelte';

	import { onMount, onDestroy, tick as nextTick } from 'svelte';
	import { config } from './stores.js';
	import { aggregateWordFixations } from './helpers';

	let observer, canvas, progress, ctx, show_sequential, fixation_data, sequential_fixation_data, fixation_backdrop, data, container;

	$: {
		data = (show_sequential) ? sequential_fixation_data : fixation_data;
		reset();
	}

	let i = 0, t = null, isInitialized = false;

	onMount(() => {
		ctx = canvas.getContext('2d');
		observer = new ResizeObserver( entries => {
			canvas.height = entries[0].contentRect.height;
			canvas.width = entries[0].contentRect.width;
		});
		observer.observe(container);
	});

	onDestroy(() => {
		observer.disconnect();
	});

	async function onLoad(event) {
		fixation_data = event.detail.fixation_data;
		sequential_fixation_data = aggregateWordFixations(event.detail.fixation_data);
		fixation_backdrop = event.detail.image;
		isInitialized = true;
		await nextTick();
		//config.update(c => {c.$config.zoom = canvas.width/fixation_backdrop.width; return c;});
		requestAnimationFrame(tick);
	}

	function tick(time) {
		if (!data || !fixation_backdrop) {
			requestAnimationFrame(tick);
			return;
		}

		let dt;

		if (isAnimated){
			if (t == null) t = time;
			dt = (time - t)*$config.speed/100
			if (dt > data[i].dur) {
				i = (i<data.length-1)?i+1:0;
				t = time;
				progress.style.width = (100/data.length*i)+'%';
			}
		} else {
			dt = data[i].dur;
			progress.style.width = (100/data.length*i)+'%';
		}

		ctx.clearRect(0, 0, 5000, 5000);
		ctx.save();
		
		drawBackdrop();
		drawFixation(data[i], dt);
		drawTrace(data.slice((i-$config.trace_length>0)?i-$config.trace_length:0,i+1))
		ctx.restore();

		requestAnimationFrame(tick);
	}

	export function reset() {
		i = 0;
		t = null;
	}

	function transform({ x, y }) {
		// transforms coordinates from image space into drawing space
		return [$config.zoom/100 * x + $config.translatex, $config.zoom/100 * y + $config.translatey]
	}

	function drawBackdrop() {
		ctx.drawImage(fixation_backdrop, $config.translatex, $config.translatey, $config.zoom/100*fixation_backdrop.width, $config.zoom/100*fixation_backdrop.height);
	}

	function drawFixation({ x, y, dur }, dt) {
		ctx.save();
		ctx.beginPath();
		let radius = Math.pow(dt/dur, $config.duration_exponent)*dur*$config.duration_multiplier;
		let [xt, yt] = transform({x, y});
		let gradient;
		try {
			gradient = ctx.createRadialGradient(xt,yt,0, xt,yt,radius);
		} catch(e) {
			console.error(e);
			return;
		}
		gradient.addColorStop(0, 'rgb(200, 0, 0, 0.5)');
		gradient.addColorStop(1, 'rgb(200, 0, 0, 0)');
		ctx.fillStyle = gradient;
		ctx.arc(xt, yt, radius, 0, Math.PI*2, true);
		ctx.fill();
		ctx.restore();
	}

	function drawTrace(trace) {
		ctx.save()
		ctx.lineWidth = $config.trace_width;
		let alpha = 0.2 + 0.8/$config.trace_length;
		let i=0, xt, yt, xtn, ytn;
		for (i; i<trace.length-1; i++) {
			let { x, y } = trace[i];
			let { x:xn, y:yn } = trace[i+1];
			[xt, yt] = transform({x, y});
			[xtn, ytn] = transform({x:xn, y:yn});
			ctx.strokeStyle = `rgba(0,0,0,${alpha}`;
			ctx.beginPath();
			ctx.moveTo(xt, yt);
			ctx.arc(xt, yt, $config.trace_radius, 0, Math.PI*2, true);
			ctx.lineTo(xtn, ytn);
			ctx.stroke();
			alpha += 0.8/$config.trace_length;
		}
		ctx.beginPath();
		ctx.arc(xt, yt, $config.trace_radius, 0, Math.PI*2, true);
		ctx.stroke();
		ctx.restore();
	}

	function translate(e) {
		if (e.buttons == 1) {
			$config.translatex += e.movementX;
			$config.translatey += e.movementY;
		}
	}

	function setZoom(e) {
		$config.zoom += (e.deltaY > 0) ? -1 : 1;
	}
</script>

<div bind:this={container} id="container">
{#if !isInitialized}
	<FilePicker on:load={onLoad} />
{/if}
<span id="loadFile" class:hide={!isInitialized} title="Close and load other files" on:click={() => isInitialized = false}>🞩</span>
<div id="controls" class:hide={!isInitialized}>
	<label title="Aggregates multiple (re-)visits into one fixation per word. Averages the fixation position and plays back in word order. Use to compare to fixation prediction models."><input type="checkbox" bind:checked={show_sequential}> As sequential word fixations</label>
</div>
<canvas class:hide={!isInitialized} bind:this={canvas} on:mousemove={translate} on:wheel|preventDefault={setZoom} height="1080" width="1920">This browser does not support canvas animations, which are required to render the visualization.</canvas>
<div id="progress" bind:this={progress}></div>
</div>

<style>
	canvas {
		margin: auto;
	}

	label {
		font-weight: normal;
	}

	#loadFile {
		position: absolute;
		top: 0;
		right: 0;
		cursor: pointer;
		margin: 10px;
	}

	#container {
		position: relative;
		flex: 1;
		display: flex;
		align-items: center;
		justify-content: center;
		overflow: hidden;
	}

	#progress {
		position: absolute;
		bottom: 0;
		left: 0;
		width: 0;
		height: 20px;
		background: #9b4dca;
	}

	#controls {
		position: absolute;
		right: 0;
		bottom: 0;
		text-align: right;
		background: rgba(255,255,255,0.5);
		padding: 10px;
	}

	.hide {
		display: none;
	}
</style>