// Data
var segmentMap = SegmentMap;
var bv = bandersnatch.videos['80988062'].interactiveVideoMoments.value;
var choicePoints = bv.choicePointNavigatorMetadata.choicePointsMetadata.choicePoints;
var momentsBySegment = bv.momentsBySegment;
var segmentGroups = bv.segmentGroups;

// Persistent state
var ls = window.localStorage || {};

function msToString(ms) {
	return new Date(ms).toUTCString().split(' ')[4];
}

function getCurrentMs() {
	return Math.round(document.getElementById("video").currentTime * 1000.0);
}

function preconditionToJS(cond) {
	if (cond[0] == 'persistentState') {
		return 'JSON.parse(ls["persistentState_' + cond[1] + '"])';
	} else if (cond[0] == 'not') {
		return '!(' + preconditionToJS(cond[1]) + ')';
	} else if (cond[0] == 'and') {
		let conds = [];
		for (let i = 1; i < cond.length; i++) {
			conds.push('(' + preconditionToJS(cond[i]) + ')');
		}
		return '(' + conds.join(' && ') + ')';
	} else if (cond[0] == 'or') {
		let conds = [];
		for (let i = 1; i < cond.length; i++) {
			conds.push('(' + preconditionToJS(cond[i]) + ')');
		}
		return '(' + conds.join(' || ') + ')';
	} else {
		console.log('unsupported condition!', cond);
		return 'true';
	}
}

function checkPrecondition(segmentId) {
	let precondition = bv.preconditions[segmentId];

	if (precondition) {
		let cond = preconditionToJS(precondition);
		let match = eval(cond);

		console.log(cond, '==', match);

		return match;
	}

	return true;
}

function findSegment(id) {
	if (id.startsWith('nsg-')) {
		id = id.substr(4);
	}
	if (segmentMap.segments[id]) {
		// check precondition
		return id;
	}

	if (segmentGroups[id]) {
		for (let v of segmentGroups[id]) {
			if (v.segmentGroup) {
				return findSegment(v.segmentGroup);
			} else if (v.segment) {
				// check precondition
				return v.segment;
			} else {
				if (checkPrecondition(v))
					return v;
			}
		}
	}
	return id;
}

/// Returns the segment ID at the given timestamp.
/// There will be exactly one segment for any timestamp within the video file.
function getSegmentId(ms) {
	for (const [k, v] of Object.entries(segmentMap.segments)) {
		if (ms >= v.startTimeMs && ms < v.endTimeMs) {
			return k;
		}
	}
	return null;
}

function getSegmentMs(segmentId) {
	return segmentMap.segments[segmentId].startTimeMs;
}

function getMoments(segmentId, ms) {
	let result = {};
	let moments = momentsBySegment[segmentId] || [];
	for (let i = 0; i < moments.length; i++) {
		let m = moments[i];
		if (ms >= m.startMs && ms < m.endMs) {
			result[segmentId + '/' + i] = m;
		}
	}
	return result;
}

function newList(id) {
	var ul = document.getElementById(id);
	while (ul.firstChild) {
		ul.removeChild(ul.firstChild);
	}
	return ul;
}

function addItem(ul, text, url) {
	var li = document.createElement("li");
	var a = document.createElement("a");
	a.textContent = text;
	a.setAttribute('href', url);
	li.appendChild(a);
	ul.appendChild(li);
}

var nextChoice = -1;
var nextSegment = null;

function setNextSegment(segmentId, comment) {
	console.log('setNextSegment', segmentId, comment);
	nextSegment = segmentId;
	nextChoice = -1;
	var ul = newList("nextSegment");
	var caption = 'nextSegment: ' + segmentId;
	addItem(ul, comment ? caption + ' (' + comment + ')' : caption,
		'javascript:playSegment("' + segmentId + '")');
}

function addZones(segmentId) {
	var ul = newList("interactionZones");
	let caption = 'currentSegment(' + segmentId + ')';
	addItem(ul, caption, 'javascript:playSegment("' + segmentId + '")');

	var v = segmentMap.segments[segmentId];
	if (v && v.ui && v.ui.interactionZones) {
		var index = 0;
		for (var z of v.ui.interactionZones) {
			var startMs = z[0];
			var stopMs = z[1];
			let caption = segmentId + ' interactionZone ' + index;
			addItem(ul, caption, 'javascript:seek(' + startMs + ')');
			index++;
		}
	}

	ul = newList("nextSegments");
	let defaultSegmentId = null;
	for (const [k, v] of Object.entries(segmentMap.segments[segmentId].next)) {
		let caption = k;
		if (segmentMap.segments[segmentId].defaultNext == k) {
			caption = '[' + caption + ']';
			defaultSegmentId = k;
		}
		addItem(ul, caption, 'javascript:playSegment("' + k + '")');
	}
	setNextSegment(defaultSegmentId);
}

var currentChoiceMoment = null;

function addChoices(r) {
	currentChoiceMoment = r;
	nextChoice = -1;
	var ul = newList("choices");
	document.getElementById("choiceCaption").innerHTML = '';
	if (!r) return;

	nextChoice = r.defaultChoiceIndex;

	let index = 0;
	for (let x of r.choices) {
		var caption = r.defaultChoiceIndex == index ? '[' + x.text + ']' : x.text;
		addItem(ul, caption, 'javascript:choice(' + index + ')');
		index++;
	}

	if (r.id in choicePoints)
		document.getElementById("choiceCaption").innerHTML = choicePoints[r.id].description;
}

function momentStart(m, seeked) {
	console.log('momentStart', m, seeked);
	if (m.choices) {
		addChoices(m);
	}
	if (!seeked)
		applyImpression(m.impressionData);
}

function momentUpdate(m, ms) {
	//console.log('momentUpdate', m);
	if (m.choices) {
		var p = 100 - ((ms - m.startMs) * 100.0 / (m.endMs - m.startMs));
		document.getElementById("progress").style.width = p + '%';
	}
}

function momentEnd(m, seeked) {
	console.log('momentEnd', m, seeked);
	if (m.choices) {
		addChoices(null);
		document.getElementById("progress").style.width = 0;
	}
}

var timerId = 0;
var lastMs = 0;
var currentSegment;
var lastSegment = null;
var segmentTransition = false;
var lastMoments = [];

function ontimeupdate(evt) {
	var ms = getCurrentMs();
	currentSegment = getSegmentId(ms);

	if (timerId) {
		clearTimeout(timerId);
		timerId = 0;
	}

	// Distinguish between the user seeking manually with <video> controls,
	// and the video playing normally (past some timestamp / boundary).
	let timeElapsed = ms - lastMs;
	let seeked = timeElapsed < 0 || timeElapsed >= 2000;
	lastMs = ms;

	// Recalculate title and hash only when we pass some meaningful timestamp.
	let placeChanged = false;

	// Handle segment change
	if (lastSegment != currentSegment) {
		console.log('ontimeupdate', lastSegment, '->', currentSegment, ms, msToString(ms), seeked);
		lastSegment = currentSegment;
		if (!seeked) {
			if (playNextSegment()) {
				// playSegment decided to seek, which means that this
				// currentSegment is invalid, and a recursive
				// ontimeupdate invocation should have taken care of
				// things already. Return.
				return;
			}
		}
		addZones(currentSegment);
		placeChanged = true;
	}

	var naturalTransition = !seeked || segmentTransition;
	segmentTransition = false;

	var currentMoments = getMoments(currentSegment, ms);
	for (let k in lastMoments)
		if (!(k in currentMoments)) {
			momentEnd(lastMoments[k], !naturalTransition);
			placeChanged = true;
		}
	for (let k in lastMoments)
		if (k in currentMoments)
			momentUpdate(lastMoments[k], ms);
	for (let k in currentMoments)
		if (!(k in lastMoments)) {
			momentStart(currentMoments[k], !naturalTransition);
			placeChanged = true;
		}
	lastMoments = currentMoments;

	if (placeChanged) {
		let title = 'Bandersnatch';
		title += ' - Chapter ' + currentSegment;
		for (let k in currentMoments) {
			let m = currentMoments[k];
			if (m.type.substr(0, 6) == 'scene:') {
				if (m.id && m.id in choicePoints && choicePoints[m.id].description)
					title += ' - Choice "' + choicePoints[m.id].description + '"';
				else
					title += ' - Choice ' + (m.id || k);
			}
		}
		document.title = title;

		let hash = currentSegment;
		// Pick the moment which starts closer to the current timestamp.
		let bestMomentStart = segmentMap.segments[currentSegment].startTimeMs;
		for (let k in currentMoments) {
			let m = currentMoments[k];
			if (m.startMs > bestMomentStart) {
				hash = k;
				bestMomentStart = m.startMs;
			}
		}
		hash = '#' + hash;
		lastHash = hash; // suppress onhashchange event
		location.hash = hash;
		ls.place = hash;
	}

	// ontimeupdate resolution is about a second. Augment it using timer.
	let nextEvent = segmentMap.segments[currentSegment].endTimeMs;
	for (let k in currentMoments) {
		let m = currentMoments[k];
		if (m.endMs < nextEvent)
			nextEvent = m.endMs;
	}
	for (let m of momentsBySegment[currentSegment] || [])
		if (ms < m.startMs && m.startMs < nextEvent)
			nextEvent = m.startMs;
	var timeLeft = nextEvent - ms;
	if (timeLeft > 0)
		timerId = setTimeout(ontimeupdate, timeLeft);
}

function playNextSegment() {
	if (nextChoice >= 0) {
		let x = currentChoiceMoment.choices[nextChoice];
		let choiceId = x.segmentId ? x.segmentId : (x.sg ? x.sg : x.id);
		var segmentId = findSegment(choiceId);
		console.log('choice', choiceId, 'nextSegment', segmentId);
		applyImpression(x.impressionData);
		nextSegment = segmentId;
	}

	segmentTransition = true;
	let segment = nextSegment;
	nextSegment = null;
	return playSegment(segment, true);
}

function jumpForward() {
	var ms = getCurrentMs();
	var segmentId = getSegmentId(ms);

	var interactionMs = 0;
	let moments = momentsBySegment[segmentId] || [];
	// Find the earliest moment within this segment
	for (let m of moments)
		if (m.startMs >= ms && (interactionMs == 0 || m.startMs < interactionMs))
			interactionMs = m.startMs;

	if (interactionMs) {
		seek(interactionMs);
	} else {
		playNextSegment();
	}
}

function jumpBack() {
	var ms = getCurrentMs();
	var segmentId = getSegmentId(ms);
	var startMs = getSegmentMs(segmentId);
	var previousSegment = getSegmentId(startMs - 1000);
	console.log('jumpBack from-to', segmentId, previousSegment);
	playSegment(previousSegment);
}

function toggleFullScreen() {
	console.log('toggleFullScreen');
	var c = document.getElementById("c");
	if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) {
		if (c.requestFullscreen) {
			c.requestFullscreen();
		} else if (c.msRequestFullscreen) {
			c.msRequestFullscreen();
		} else if (c.mozRequestFullScreen) {
			c.mozRequestFullScreen();
		} else if (c.webkitRequestFullscreen) {
			c.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
		}
	} else {
		if (document.exitFullscreen) {
			document.exitFullscreen();
		} else if (document.msExitFullscreen) {
			document.msExitFullscreen();
		} else if (document.mozCancelFullScreen) {
			document.mozCancelFullScreen();
		} else if (document.webkitExitFullscreen) {
			document.webkitExitFullscreen();
		}
	}
}

function togglePlayPause() {
	var v = document.getElementById("video");
	if (v.paused) v.play();
	else v.pause();
}

window.onload = function() {
	var video_selector = document.getElementById("video");
	var video_source_selector = document.getElementById("video-source");
	var file_selector = document.getElementById("file-selector");
	function startPlayback() {
		file_selector.style.display = 'none';
		if (window.location.hash)
			playHash(window.location.hash);
		else if (ls.place)
			playHash(ls.place);
		else
			playSegment(null);
		video_selector.play();
	}
	if (video_source_selector.getAttribute("src") == '') {
		console.log('no video');
		file_selector.style.display = 'table';
		document.getElementById("wrapper-video").style.display = 'none';
	} else {
		startPlayback();
	}
	document.getElementById('fileinput').addEventListener('change', function () {
		var file = this.files[0];
		var fileUrl = URL.createObjectURL(file);
		video_selector.src = fileUrl;
		document.getElementById("wrapper-video").style.display = 'block';
		startPlayback();
	}, false);

	video_selector.ontimeupdate = ontimeupdate;

	var c = document.getElementById("c");
	c.ondblclick = toggleFullScreen;
	video_selector.onclick = function (e) {
		togglePlayPause();
		e.preventDefault();
	};

	document.onkeypress = function (e) {
		if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
			return;
		if (e.code == 'KeyF')
			toggleFullScreen();
		if (e.code == 'KeyR')
			playSegment(0);
		if (e.code == 'Space')
			togglePlayPause();
	};
	video_selector.onkeydown = function(e) {
		if (e.code == 'Space')
			e.preventDefault();
	};

	document.onkeydown = function (e) {
		if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
			return;
		if (e.key == 'ArrowLeft')
			jumpBack();
		if (e.key == 'ArrowRight')
			jumpForward();
		if (e.key == 'ArrowUp')
			video_selector.playbackRate = video_selector.playbackRate * 2.0;
		if (e.key == 'ArrowDown')
			video_selector.playbackRate = video_selector.playbackRate / 2.0;
	};

	window.onhashchange = function() {
		playHash(window.location.hash);
	};
};

function seek(ms) {
	console.log('seek', ms);
	document.getElementById("video").currentTime = ms / 1000.0;
	ontimeupdate(null);
}

function choice(choiceIndex) {
	nextChoice = choiceIndex;
	newList("choices");
	if (!currentChoiceMoment.config.disableImmediateSceneTransition)
		playNextSegment();
}

function applyImpression(impressionData) {
	if (impressionData && impressionData.type == 'userState') {
		for (const [variable, value] of Object.entries(impressionData.data.persistent)) {
			console.log('persistentState set', variable, '=', value);
			ls["persistentState_" + variable] = JSON.stringify(value);
		}
	}
}

function playSegment(segmentId, noSeek) {
	if (!segmentId || typeof segmentId === "undefined")
		segmentId = segmentMap.initialSegment;
	var oldSegment = getSegmentId(getCurrentMs());
	console.log('playSegment', oldSegment, '->', segmentId);
	if (!noSeek || oldSegment != segmentId) {
		var ms = getSegmentMs(segmentId);
		seek(ms);
		return true;
	}
	return false;
}

var lastHash = '';
function playHash(hash) {
	// console.log('playHash', lastHash, '->', hash);
	if (hash == lastHash)
		return;
	lastHash = hash;
	if (hash) {
		hash = hash.slice(1);
		if (hash[0] == 't')
			seek(Number(Math.round(hash.slice(1) * 1000.0)));
		else {
			let loc = hash.split('/');
			let segmentId = loc[0];
			if (loc.length > 1)
				seek(momentsBySegment[segmentId][loc[1]].startMs);
			else
				seek(getSegmentMs(segmentId));
		}
	}
}
