<script>
	// get the data from the JSON Webservices feed in +page.js
	export let data;
	const { webservices } = data;

	import { references } from '$lib/references.js';
	import { colours } from '$lib/colours.js';

	// the filters
	import { setFilter, getFilter } from '$lib/components/filter.js';
	import Filterable from '$lib/components/Filterable.svelte';
	// collapsible sections
	import CollapsibleSection from '$lib/components/CollapsibleSection.svelte'

	//console.log('colours: ', colours);

    //
    // digest and return useful info from the Webservices JSON feed
    function processData(webservices) {
        let instances = 0;
        let current = [];
        let future = [];
        // properties of technologies
        let category_list = [];
        let analogue_list = [];
        let license_list = [];
        // properties of instances
        let status_list = [];
        let affiliate_list = [];
        let host_list = [];

        // actual objects
        let hosts = [];
        let affiliates = [];
        //
        // just a trivial reassignment
        //let hosts = webservices.hosts;
        //
        // pull out relevant info in useful chunks.
        for (let key in webservices.technologies) {
            let tech = webservices.technologies[key];
            if (tech.hasOwnProperty('categories') && tech.categories.constructor === Array) {
                tech.categories.forEach(function (category, index) {
                    if (category_list.hasOwnProperty(category)) category_list[category]++;
                    else category_list[category] = 1;
                });
            }
            if (tech.hasOwnProperty('analogues') && tech.analogues.constructor === Array) {
                tech.analogues.forEach(function (analogue, index) {
                    if (analogue_list.hasOwnProperty(analogue)) analogue_list[analogue]++;
                    else analogue_list[analogue] = 1;
                });
            }
            if (tech.hasOwnProperty('license')) {
                let license = tech.license;
                if (license_list.hasOwnProperty(license)) license_list[license]++;
                else license_list[license] = 1;
            }

            if (hasInstances(tech)) {
                tech['name'] = key;
                current.push(tech);
                //console.log(tech.name + ': ' + tech.instances.length + ' instances...');
                tech.instances.forEach(function (instance, i) {
                    instances++;
                    if (instance.hasOwnProperty('status')) {
                        let tag = instance.status;
                        if (status_list.hasOwnProperty(tag)) status_list[tag]++;
                        else status_list[tag] = 1;
                    }
                    if (instance.hasOwnProperty('affiliation')) {
                        let tag = instance.affiliation;

                        if (affiliate_list.hasOwnProperty(tag)) affiliate_list[tag]++;
                        else affiliate_list[tag] = 1;
                    }
                    if (instance.hasOwnProperty('host')) {
                        let tag = instance.host;
                        if (host_list.hasOwnProperty(tag)) host_list[tag]++;
                        else host_list[tag] = 1;
                    }
                });
            } else {
                future[key] = tech;
            }
        }
        for (let key in webservices.hosts) {
            //console.log('key: ', key);
            let host = webservices.hosts[key];
            host['name'] = key;
            //console.log('host: ', host);
            if (
                host.hasOwnProperty('domain') &&
                !(host.hasOwnProperty('status') && host.status == 'retired')
            ) {
                hosts[key] = host;
            }
        }
        for (let key in webservices.affiliates) {
            let affiliate = webservices.affiliates[key];
            affiliates[key] = affiliate;
        }

        return {
            total_instances: instances,
            active_services: current,
            candidate_services: future,
            tech_lists: {
                category_list: category_list,
                analogue_list: analogue_list,
                license_list: license_list
            },
            instance_lists: {
                status_list: status_list,
                affiliate_list: affiliate_list,
                host_list: host_list
            },
            hosts: hosts,
            affiliates: affiliates
        };
    }

	//    console.log(technologies);
	// return true if a tech object includes valid instances
	function hasInstances(tech) {
		if (
			tech.hasOwnProperty('instances') &&
			tech.instances.constructor === Array &&
			tech.instances.length
		) return true;
		return false;
	}

	// return an object with ob's keys ordered alphabetically, with an idfrom an object
	function getSortedFilter(ob) {
		let keys = [];
		let i = 0;
		let key_array = [];
		// first create a flat array.
		for (let key in ob) {
			keys.push(key);
		}
		// sort the array alphabetically
		keys.sort();
		// then create a dictionary of them
		keys.forEach(function(name) {
			key_array.push({ "id": i++, "name": name, "active": true });
		});
		return key_array;
	}

	// get intersection: ref https://bobbyhadz.com/blog/javascript-get-intersection-of-two-arrays
	function getIntersection(a, b) {
		const set1 = new Set(a);
		const set2 = new Set(b);

		const intersection = [...set1].filter((element) => set2.has(element));

		return intersection;
	}

	// combine an array of terms into a sentence with proper Oxford commas, dealing with the special cases
	// of one or two elements.
	function toOxfordCommaString(arr) {
		if (arr.length == 1) return arr;
		if (arr.length == 2) return arr.join(' and ');
		else {
			var last = arr.pop();
			return arr.join(', ') + ', and ' + last;
		}
	}

	// return a singular or plural term depending on the value of 'count'
	function pluraliser(singular, plural, count) {
		if (count > 1) return plural;
		return singular;
	}

	// assign colours from a set of differentiated colours to a list of tags...
	// this one is for 'hosts'
	//
	// host_list is in the form of
	function hostColours(host_list, colours, hosts) {
		let host_array = {};
		let i = 0;

		console.log('hosts:', hosts);
		console.log('host_list:', host_list);

		for (let index in host_list) {
			console.log(host_list[index]);
			let hostname = host_list[index].name;
			if (hosts[hostname].hasOwnProperty('domain') && hosts[hostname].hasOwnProperty('affiliation')) {
				host_array[hostname] = {
					colour: colours[i++],
					domain: hosts[hostname].domain,
					affiliation: hosts[hostname].affiliation
				};
			}
		}
		return host_array;
	}

	// assign colours from a set of differentiated colours to a list of tags...
	// this one is for 'affiliates'
	function affiliateColours(affiliate_list, colours, affiliates) {
		let affiliate_result = {};
		let i = 0;

		//console.log('affiliates:', affiliates);
		console.log('affiliate_list:', affiliate_list);

		for (let index in affiliate_list) {
			//console.log('affiliate:', affiliate);
			let affiliate = affiliate_list[index].name;
			if (
				affiliates[affiliate].hasOwnProperty('name') &&
				affiliates[affiliate].hasOwnProperty('website')
			) {
				affiliate_result[affiliate] = {
					colour: colours[i++],
					name: affiliates[affiliate].name,
					website: affiliates[affiliate].website
				};
			}
		}
		return affiliate_result;
	}

	// sort technologies alphabetically by name
	function sortTechnologies(technologies) {
		return technologies.sort((a, b) => a.name.localeCompare(b.name));
	}

	// generic function to find the intersection of two arrays
	// approach ref: https://bobbyhadz.com/blog/javascript-get-intersection-of-two-arrays
	function inCommon(a, b) {
		const set1 = new Set(a);
		const set2 = new Set(b);

		const intersection = [...set1].filter((element) => set2.has(element));
		console.log('intersection = ', intersection);
		return intersection;
	}

	// converts a filter with id, name, and active fields into an array of names
	function flattenFilter(filter) {
		let flat = [];
	    if (filter.constructor === Array) {
			filter.forEach(function(e) { if (e.active) flat.push(e.name) });
	    }
		return flat;
	}

	// filter technologies based on a list of Categories
	function filterTechnologiesByCategoryList(technologies, list) {
		console.log('looking for tech in categories: ', list);
		const included = [];
		technologies.forEach(function (tech) {
			if (hasInstances(tech)) {
				const intersection = inCommon(tech.categories, list);
				//console.log('categories: ', tech.categories);
				//console.log('list: ', list);
				console.log('intersection: ', intersection);
				if (intersection && intersection.constructor === Array) {
					//console.log('found intersection!', intersection.constructor);
					console.log('intersection length: ', intersection.length);
					if (intersection.length > 0) {
						//console.log('including tech: ', tech);
						included.push(tech);
					}
				} else {
					console.log('intersection not array');
				}
			}
		});
		console.log('found ' + included.length + ' technologies.');
		return included;
	}

	const results = processData(webservices);

	//const technologies = webservices.technologies;
	//console.log(technologies);

	// set up filters for each type
	setFilter('categories');
	setFilter('analogues');
	setFilter('licenses');
	setFilter('statuses');
	setFilter('affiliates');
	setFilter('hosts');

	// define the handle for each writable
	const categoryFilter = getFilter('categories');
	const analogueFilter = getFilter('analogues');
	const licenseFilter = getFilter('licenses');
	const statusFilter = getFilter('statuses');
	const affiliateFilter = getFilter('affiliates');
	const hostFilter = getFilter('hosts');

	// populate the writable with actual data
	$categoryFilter = getSortedFilter(results.tech_lists.category_list);
	$analogueFilter = getSortedFilter(results.tech_lists.analogue_list);
	$licenseFilter = getSortedFilter(results.tech_lists.license_list);
	$statusFilter = getSortedFilter(results.instance_lists.status_list);
	$affiliateFilter = getSortedFilter(results.instance_lists.affiliate_list);
	$hostFilter = getSortedFilter(results.instance_lists.host_list);

	const hosts = results.hosts;
	console.log('host_data: ', results.hosts);
	const affiliates = results.affiliates;
	console.log('affiliate_data: ', results.affiliates);
	const host_colours = hostColours($hostFilter, colours, hosts);
	console.log('host_colours:', host_colours);
	colours.sort();
	const affiliate_colours = affiliateColours($affiliateFilter, colours, affiliates);
	console.log('affiliate_colours:',affiliate_colours);

	let filteredTechnologies;
	let technologies;

	// reactive stuff...
	$: {
		console.log('about to filterTechnologiesByCategoryList');
		filteredTechnologies = filterTechnologiesByCategoryList(results.active_services, flattenFilter($categoryFilter));
		const technologies = sortTechnologies(filteredTechnologies);
	}

	/*function flipOn(list, id) {
	    console.log('list: ' + list + ', id: ' + id);
	    return true;
	}*/

</script>

<div class="webservices">
	<h1>Web Services</h1>

	<div class="introduction">
		<p>This sites exists to provide an 'always-up-to-date', in-depth description of the <a
				href="https://tech.oeru.org/foss-libresoftware-its-about-clarity-and-values"
				title="What do I mean by 'libre' software?">libre software</a> web services <a href="https://davelane.nz" title="Who is this Dave Lane character?">I</a> have set
			up and maintain.
		</p>

	</div>
	<div class="summary">
		<ul>
			<li>Total number of services: {results.total_instances}</li>
		</ul>
	</div>
	<CollapsibleSection  headerText={'Show or Hide Filters'}>
		<div class="filters">
			<CollapsibleSection  headerText={'Show or Hide Software Catergory Filter'}>
				<div class="filter categories">
					<Filterable filterValues={categoryFilter} context={'categories'}>
						<h2 slot="h2">Categories</h2>
					</Filterable>
				</div>
			</CollapsibleSection>
			<CollapsibleSection  headerText={'Show or Hide Software Analogue Filter'}>
				<div class="filter analogues">
					<Filterable filterValues={analogueFilter} context={'analogues'}>
						<h2 slot="h2">Analogues</h2>
					</Filterable>
				</div>
			</CollapsibleSection>
			<CollapsibleSection  headerText={'Show or Hide License Filter'}>
				<div class="filter licenses">
					<Filterable filterValues={licenseFilter} context={'licenses'}>
						<h2 slot="h2">Open Source & Copyleft Licenses</h2>
					</Filterable>
				</div>
			</CollapsibleSection>
			<CollapsibleSection  headerText={'Show or Hide Service Status Filter'}>
				<div class="filter statuses">
					<Filterable filterValues={statusFilter} context={'statuses'}>
						<h2 slot="h2">Statuses</h2>
					</Filterable>
				</div>
			</CollapsibleSection>
			<CollapsibleSection  headerText={'Show or Hide Service Affiliates Filter'}>
				<div class="filter affilates">
					<Filterable filterValues={affiliateFilter} context={'affiliates'}>
						<h2 slot="h2">Affiliates</h2>
					</Filterable>
				</div>
			</CollapsibleSection>
			<CollapsibleSection  headerText={'Show or Hide Service Host filter'}>
				<div class="filter hosts">
					<Filterable filterValues={hostFilter} context={'hosts'}>
						<h2 slot="h2">Hosts</h2>
					</Filterable>
				</div>
			</CollapsibleSection>
		</div>
	</CollapsibleSection>
	<div class="tiles">
		{#each filteredTechnologies as technology}
			<div class="tile technology">
				<div class="links">
					{#if technology.repository}<span class="repository"
							><a
								href={technology.repository}
								title="The source code repository for {technology.name}">R</a
							></span
						>{/if}
					{#if technology.wikipedia}<span class="wikipedia"
							><a href={technology.wikipedia} title="Wikipedia Page for {technology.name}">W</a
							></span
						>{/if}
				</div>
				<h2><a href={technology.website}>{technology.name}</a></h2>
				<p class="description">
					{technology.description}{#if technology.extended_description}<span
							title={technology.extended_description}>i</span
						>{/if}
				</p>
				{#if technology.categories}<p class="categories">
						{pluraliser('Category', 'Categories', technology.categories.length)}:
						<span class="value">{toOxfordCommaString(technology.categories)}</span>
					</p>{/if}
				{#if technology.analogues}<p class="analogues">
						Alternative to <span class="value">{toOxfordCommaString(technology.analogues)}</span>
					</p>{/if}
				{#if technology.license}<p
						class="license"
						title="The libre license for this project is {technology.license}"
					>
						License: <span class="value">{technology.license}</span>
					</p>{/if}
				{#if hasInstances(technology)}
					<div class="instances">
						<p>
							{#each technology.instances as instance}
								<a
									href="https://{instance.domain}"
									title="{technology.name} instance {instance.domain} hosted on '{instance.host}' by {instance.affiliation}"
									><span
										class="marker circle"
										style="background-color: {affiliate_colours[instance.affiliation].colour}"
									></span></a
								>{/each}
						</p>
					</div>
				{:else}
					<div class="instances">Nothing here yet...</div>
				{/if}
			</div>
		{/each}
	</div>
</div>

<style>
	.webservices {
		display: grid;
		margin: 0 auto 3em auto;
		padding: 0;
	}
	.summary {
	    background-color: #ccc;
	    display: block;
	}
	.filters {
		/*background-color: #f1ff94;
        padding: 0.5em;
        border: 3px solid #cbea77;
        margin: 1em 0;*/
		margin-bottom: 1em;
	}
	.filters .tags {
		/*padding: 0.5em;*/
		margin: 1em 0;
		display: block;
	}
	.tag-list {
/*		white-space: normal;
		word-break: normal;
		display: inline;*/
	}
	.tags {
/*		white-space: normal;
		word-break: normal;
	    display: inline;*/
	}
	.tag {
		font-size: 80%;
		margin-right: 0.5em;
		line-height: 2.5;
		padding: 6px 8px;
		border-radius: 10px;
		white-space: nowrap;
		word-break: normal;
		box-shadow: 3px 3px 3px #6a6d6a;
	}
	.tag:hover { box-shadow: 4px 4px 3px #727372;}
	.tag.category {
		background-color: #9aa34d;
		color: #fff;
	}
	.tag.license {
		background-color: #6498a3;
		color: #fff;
	}
	.tag.status {
		background-color: #a369a2;
		color: #fff;
	}
	.tag.affiliate {
		background-color: #fff;
		color: #000;
	}
	.tag.host {
		background-color: #fff;
		color: #000;
	}
	.tag.on {
	    padding-right: 25px;
	    background-image: url('/images/check-black-in-white-20px.png');
	    background-position: right center;
	    background-repeat: no-repeat;
	}
	.tag.off {
	    color: #ccc;
	    opacity: 0.5;
	}
	.tiles {
		display: grid;
		grid-gap: 15px;
		grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
	}
	/* flip card stuff: https://www.w3schools.com/howto/howto_css_flip_card.asp */
	.tile {
		height: 515px;
		min-width: 240px;
		max-width: 300px;
		/*width: 290px;*/
		overflow: hidden;
		box-sizing: border-box;
		box-shadow: 5px 5px 3px #6a6d6a;
		border: solid 3px #1e6831;
		text-overflow: ellipsis;
		overflow: hidden;
		position: relative;
		background-color: #eee;
	}
	.tile .links {
		position: absolute;
		top: 0;
		right: 0;
		padding: 2px 6px;
		font-size: 90%;
	}
	.tile:hover {
		box-shadow: 10px 10px 6px #727372;
	}
	.tile .links h2 a {
		color: #e6c4fc;
	}
	.tile .links a:visited {
		color: #ced0ff;
	}
	.tile .links a:hover,
	.tile:hover h2 a {
		color: #fff !important;
	}
	.tile h2 {
		background-color: #999;
		text-align: center;
		padding: 0.5em;
		margin: 0;
	}
	.tile h2 a {
		color: #e6c4fc;
	}
	.tile h2 a:visited {
		color: #ced0ff;
	}
	.tile h2 a:hover {
		color: #fff;
	}
	.tile .description {
		height: 260px;
		text-overflow: ellipsis;
		overflow: hidden;
	}
	.tile p {
		padding: 0 1em 0.2em 1em;
		font-size: 80%;
		color: #555;
	}
	.instances {
		background-color: #ddd;
		position: absolute;
		bottom: 0;
		height: 3em;
		width: 100%;
	}
	.instances .marker {
		margin-right: 3px;
	}
	.webservices li {
		list-style-type: none;
	}
	.value {
		font-weight: bold;
		color: #000;
	}
	.marker {
		vertical-align: middle;
	}
	.circle {
		height: 20px;
		width: 20px;
		background-color: #555;
		border-radius: 50%;
		display: inline-block;
	}
	.square {
		height: 20px;
		width: 20px;
		background-color: #555;
		display: inline-block;
	}
</style>
