<?php if (!defined('PmWiki')) exit();

$RecipeInfo['GistEmbed']['Version'] = '2018-10-19';

## (:gist-embed:)
Markup('gist-embed', '<fulltext', '/\\(:gist-embed\\s+(.+?)\\s*:\\)/', 'GistEmbed');

SDV($GistEmbedHighlightStyle, "background-color: yellow;");

function GistEmbed($m) {
	static $id = 1;
	
	## Parse arguments to the markup.
	$parsed = ParseArgs($m[1]);

	## These are the "bare" arguments (ones which don't require a key, just value(s)).	
	$args = $parsed[''];
	$gist_id = $args[0];
	$noJS = in_array('no-js', $args);
	$noFooter = in_array('nofooter', $args);
	$noLineNumbers = in_array('nolinenums', $args);
	$raw = in_array('raw', $args);
	$noPre = in_array('no-pre', $args);

	## Check whether specific files are being specified.
	$files = array();
	if ($args[1] && !in_array($args[1], array('no-js', 'nofooter', 'nolinenums', 'raw', 'no-pre')))
		$files = explode(',',$args[1]);
	
	## Convert the comma-delimited line ranges to an array containing each line to be
	## included as values.
	## Note that the line numbers will be zero-indexed (for use with raw text, etc.).
	$line_ranges = $parsed['lines'] ? explode(',', $parsed['lines']) : array();
	$line_numbers = array();
	$to_end_from = -1;
	foreach ($line_ranges as $key => $line_range) {
		if (preg_match("/([0-9]+)[-–]([0-9]+)/", $line_range, $m)) {
			$line_numbers = array_merge($line_numbers, range(--$m[1],--$m[2]));
		} else if (preg_match("/([0-9]+)[-–]$/", $line_range, $m)) {
			$line_numbers[] = $to_end_from = --$m[1];
		} else {
			$line_numbers[] = --$line_range;
		}
	}
	
	## Same thing, but for highlighted line ranges.
	$hl_line_ranges = $parsed['hl'] ? explode(',', $parsed['hl']) : array();
	$hl_line_numbers = array();
	$hl_to_end_from = -1;
	foreach ($hl_line_ranges as $key => $hl_line_range) {
		if (preg_match("/([0-9]+)[-–]([0-9]+)/", $hl_line_range, $m)) {
			$hl_line_numbers = array_merge($hl_line_numbers, range(--$m[1],--$m[2]));
		} else if (preg_match("/([0-9]+)[-–]$/", $hl_line_range, $m)) {
			$hl_line_numbers[] = $hl_to_end_from = --$m[1];
		} else {
			$hl_line_numbers[] = --$hl_line_range;
		}
	}
	
	$embed_js_url = "https://gist.github.com/$gist_id.js";
	$embed_raw_url = "https://gist.github.com/$gist_id/raw/";
	$embed_json_url = "https://gist.github.com/$gist_id.json";
	
	$out = "<span class='gist-embed-error'>Unknown error.</span>";
	
	if ($raw) {
		## If no filenames have been specified, we'll have to retrieve the file list from
		## the server; otherwise, we'll have no idea what files to request, and just 
		## retrieving the 'raw' URL for a multi-file gist (with no file specified) gets
		## the first file only...
		if (empty($files)) {
			$full_gist_data = json_decode(file_get_contents($embed_json_url),true);
			$files = $full_gist_data['files'];
		}
		
		## The raw text of each file of a multi-file gist must be retrieved individually.
		$out = array();
		foreach ($files as $filename) {
			$raw_text = file_get_contents($embed_raw_url.$filename);
			if (!$raw_text) return Keep("<span class='gist-embed-error'>Could not retrieve gist!</span>");
			
			$raw_lines = explode("\n", $raw_text);
			## Convert HTML entities.
			if (!$noPre) {
				foreach ($raw_lines as $line)
					$line = PVSE($line);
			}
			## Highlighting only works if no-pre is NOT enabled AND if we're displaying a 
			## single file only.
			if (!empty($hl_line_numbers) && !$noPre && count($files) == 1) {
				if ($hl_to_end_from >= 0)
					$hl_line_numbers = array_merge($hl_line_numbers, range($hl_to_end_from, count($raw_lines) - 1));
				foreach ($hl_line_numbers as $l) {
					$raw_lines[$l] = "<span class='gist-embed-highlighted-line'>" . rtrim($raw_lines[$l]) . "</span>";
				}
			}
			## Specifying line numbers only works if we're displaying a single file only.
			if (!empty($line_numbers) && count($files) == 1) {			
				if ($to_end_from >= 0)
					$line_numbers = array_merge($line_numbers, range($to_end_from, count($raw_lines) - 1));
				$raw_lines = array_intersect_key($raw_lines, array_flip($line_numbers));
			}
			$raw_text = implode("\n", $raw_lines);
			
			## The 'no-pre' option means we shouldn't wrap the text in a <pre> tag.
			$out[] = $noPre ? $raw_text : Keep("<pre class='escaped gistRaw' id='gistEmbed_$id_$filename'>\n" . $raw_text . "\n</pre>\n");
		}
		$out = implode($noPre ? "\n\n" : "", $out);
	} else if ($noJS) {
		include_once('simple_html_dom.php');
	
		$json_content = json_decode(file_get_contents($embed_json_url),true);
				
		## The style sheet.
		global $HTMLHeaderFmt;
		$HTMLHeaderFmt[] = "<link rel='stylesheet' type='text/css' href='" . $json_content['stylesheet'] . "' />\n";
		
		## The HTML.
		$content_html = str_get_html(stripcslashes($json_content['div']));
		$content = $content_html->find("div.gist", 0);
		$content->id = "gistEmbed_$id";
		
		## If specific files are specified, we simply delete the div.gist-file containers
		## that contain files we don't want.
		if (!empty($files)) {
			$file_ids = preg_replace("/\./", "-", $files);
			$gist_file_blocks = $content_html->find("div.gist-file");
			foreach ($gist_file_blocks as $gist_file_block) {
				if (!in_array(substr($gist_file_block->find("div.file", 0)->id, 5), $file_ids))
					$gist_file_block->outertext = '';
			}
		}
		
		## Specifying line numbers only works if we're displaying a single file only.
		$displayed_gist_files = array_filter($content_html->find("div.gist-file"), function ($d) { return $d->outertext; });
		if (!empty($line_numbers) && count($displayed_gist_files) == 1) {
			$lines = reset($displayed_gist_files)->find(".js-file-line-container tr");
			if ($to_end_from >= 0)
				$line_numbers = array_merge($line_numbers, range($to_end_from, count($lines) - 1));
			foreach ($lines as $l) {
				$line_num =$l->childNodes(0)->getAttribute('data-line-number');
				if (!in_array(--$line_num, $line_numbers))
					$l->outertext = '';
			}
		}
		
		## Highlighting specific line numbers only works if we're display a single file 
		## only.
		if (!empty($hl_line_numbers) && count($displayed_gist_files) == 1) {
			$lines = reset($displayed_gist_files)->find(".js-file-line-container tr");
			if ($hl_to_end_from >= 0)
				$hl_line_numbers = array_merge($hl_line_numbers, range($hl_to_end_from, count($lines) - 1));
			foreach ($lines as $i => $line) {
				if (in_array($i, $hl_line_numbers)) {
					$line->children(1)->class .= " gist-embed-highlighted-line";
				}
			}
		}
		
		$out = Keep($content);
	} else {
		$out = Keep("<script id='gistEmbedScript_$id' src='$embed_js_url'></script>");
		
		## If specific files are specified, we'll delete the div.gist-file containers 
		## that contain files we don't want (this script will run right after the script
		## that adds the content in the first place).
		if (!empty($files)) {
			$files_js = preg_replace("/\./", "-", "[ '".implode("', '",$files)."' ]");
			$out .= Keep("
<script>
	var files = $files_js;
	document.querySelector('#gistEmbedScript_$id').parentElement.nextSibling.querySelectorAll('div.gist-file').forEach(function (gist_file_block) {
		if (files.indexOf(gist_file_block.querySelector('div.file').id.substring(5)) == -1)
			gist_file_block.parentElement.removeChild(gist_file_block);
	});	
</script>
			");
		}
		
		## Specifying line numbers only works if we're displaying a single file only.
		if (!empty($line_numbers) || !empty($hl_line_numbers)) {
			$line_numbers_js = "[ ".implode(", ",$line_numbers)." ]";
			$hl_line_numbers_js = "[ ".implode(", ",$hl_line_numbers)." ]";
			$out .= Keep("
<script>
	if (document.querySelector('#gistEmbedScript_$id').parentElement.nextSibling.querySelectorAll('div.gist-file').length == 1) {
		var num_lines = document.querySelector('#gistEmbedScript_$id').parentElement.nextSibling.querySelector('div.gist-file').querySelectorAll('.js-file-line-container tr').length;

		var line_numbers = $line_numbers_js;
		var to_end_from = $to_end_from;
		if (to_end_from >= 0)
			line_numbers = [...line_numbers, ...[...Array(num_lines - to_end_from)].map((_, i) => to_end_from + i)];

		var hl_line_numbers = $hl_line_numbers_js;
		var hl_to_end_from = $hl_to_end_from;
		if (hl_to_end_from >= 0)
			hl_line_numbers = [...hl_line_numbers, ...[...Array(num_lines - hl_to_end_from)].map((_, i) => hl_to_end_from + i)];

		document.querySelector('#gistEmbedScript_$id').parentElement.nextSibling.querySelector('div.gist-file').querySelectorAll('.js-file-line-container tr').forEach(function (line, i) {
			// Highlight specified line ranges (if any have been specified via the hl= parameter).
			if (hl_line_numbers.indexOf(i) != -1)
				line.children[1].className += ' gist-embed-highlighted-line';

			// Filter specified line ranges (if any have been specified via the lines= parameter).
			if (line_numbers.length > 0 && line_numbers.indexOf(i) == -1)
				line.parentElement.removeChild(line);
		});
	}
</script>
			");
		}
		
		GistEmbedAppendFooter();
	}
	
	global $HTMLStylesFmt;
	if (!$raw && $noFooter) {
		$HTMLStylesFmt['gist-embed'][] = "#gistEmbed_$id .gist-meta { display: none; }\n";
		$HTMLStylesFmt['gist-embed'][] = "#gistEmbed_$id .gist-data { border-bottom: none; border-radius: 2px; }\n";
	}
	if (!$raw && $noLineNumbers) {
		$HTMLStylesFmt['gist-embed'][] = "#gistEmbed_$id td.js-line-number { display: none; }\n";
	}

	GistEmbedInjectStyles();
	
	$id++;
	return $out;
}

function GistEmbedAppendFooter() {
	static $ran_once = false;
	if (!$ran_once) {
		global $HTMLFooterFmt;
		$HTMLFooterFmt[] = 
"<script>
	document.querySelectorAll('div.gist').forEach(function (embed) {
		if (embed.previousSibling && embed.previousSibling.tagName == 'P') {
			embed.id = 'gistEmbed_' + embed.previousSibling.firstChild.id.substring(16);
		}
	});
</script>\n";
	}
	$ran_once = true;
}

function GistEmbedInjectStyles() {
	static $ran_once = false;
	if (!$ran_once) {
		global $HTMLStylesFmt, $GistEmbedHighlightStyle;
		$styles = "
.gistRaw .gist-embed-highlighted-line { $GistEmbedHighlightStyle display: inline-block; width: calc(100% + 4px); padding-left: 4px; margin-left: -4px; }
.gist tr .gist-embed-highlighted-line { $GistEmbedHighlightStyle }
";
		$HTMLStylesFmt['gist-embed'][] = $styles;
	}
	$ran_once = true;
}