<?php
	# FIXME Compose auslesen

	# Funktionen
	# -------------------------------

	# Unicode Codepoint Funktion von http://iki.fi/hsivonen/php-utf8/
	# (GPL)
	function utf8ToUnicode(&$str)
	{
		$mState = 0;     // cached expected number of octets after the current octet
		// until the beginning of the next UTF8 character sequence
		$mUcs4  = 0;     // cached Unicode character
		$mBytes = 1;     // cached expected number of octets in the current sequence

		$out = array();

		$len = strlen($str);
		for($i = 0; $i < $len; $i++) {
			$in = ord($str{$i});
			if (0 == $mState) {
				// When mState is zero we expect either a US-ASCII character or a
				// multi-octet sequence.
				if (0 == (0x80 & ($in))) {
					// US-ASCII, pass straight through.
					$out[] = $in;
					$mBytes = 1;
				} else if (0xC0 == (0xE0 & ($in))) {
					// First octet of 2 octet sequence
					$mUcs4 = ($in);
					$mUcs4 = ($mUcs4 & 0x1F) << 6;
					$mState = 1;
					$mBytes = 2;
				} else if (0xE0 == (0xF0 & ($in))) {
					// First octet of 3 octet sequence
					$mUcs4 = ($in);
					$mUcs4 = ($mUcs4 & 0x0F) << 12;
					$mState = 2;
					$mBytes = 3;
				} else if (0xF0 == (0xF8 & ($in))) {
					// First octet of 4 octet sequence
					$mUcs4 = ($in);
					$mUcs4 = ($mUcs4 & 0x07) << 18;
					$mState = 3;
					$mBytes = 4;
				} else if (0xF8 == (0xFC & ($in))) {
					/* First octet of 5 octet sequence.
					*
					* This is illegal because the encoded codepoint must be either
					* (a) not the shortest form or
					* (b) outside the Unicode range of 0-0x10FFFF.
					* Rather than trying to resynchronize, we will carry on until the end
					* of the sequence and let the later error handling code catch it.
					*/
					$mUcs4 = ($in);
					$mUcs4 = ($mUcs4 & 0x03) << 24;
					$mState = 4;
					$mBytes = 5;
				} else if (0xFC == (0xFE & ($in))) {
					// First octet of 6 octet sequence, see comments for 5 octet sequence.
					$mUcs4 = ($in);
					$mUcs4 = ($mUcs4 & 1) << 30;
					$mState = 5;
					$mBytes = 6;
				} else {
					/* Current octet is neither in the US-ASCII range nor a legal first
					* octet of a multi-octet sequence.
					*/
					return false;
				}
			} else {
				// When mState is non-zero, we expect a continuation of the multi-octet
				// sequence
				if (0x80 == (0xC0 & ($in))) {
					// Legal continuation.
					$shift = ($mState - 1) * 6;
					$tmp = $in;
					$tmp = ($tmp & 0x0000003F) << $shift;
					$mUcs4 |= $tmp;
					if (0 == --$mState) {
						/* End of the multi-octet sequence. mUcs4 now contains the final
						* Unicode codepoint to be output
						*
						* Check for illegal sequences and codepoints.
						*/
						// From Unicode 3.1, non-shortest form is illegal
						if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
						((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
						((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
						(4 < $mBytes) ||
						// From Unicode 3.2, surrogate characters are illegal
						(($mUcs4 & 0xFFFFF800) == 0xD800) ||
						// Codepoints outside the Unicode range are illegal
						($mUcs4 > 0x10FFFF)) {
							return false;
						}
						if (0xFEFF != $mUcs4) {
							// BOM is legal but we don't want to output it
							$out[] = $mUcs4;
						}
						//initialize UTF8 cache
						$mState = 0;
						$mUcs4  = 0;
						$mBytes = 1;
					}
				} else {
					/* ((0xC0 & (*in) != 0x80) && (mState != 0))
					*
					* Incomplete multi-octet sequence.
					*/
					return false;
				}
			} # if state ...
		} # for-loop
		return $out;
	}

	# Liest alle Tasten, samt zugehörigen Zeichen aus der Referenz
	function getNeoKeys () {
		# Referenz laden
		$reference = file_get_contents('http://neo-layout.org/svn/A-REFERENZ-A/neo20.txt');

		# Haupttastatur finden
		preg_match('/┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────────┐\n(.*)\n└──────┴──────┴──────┴──────────────────────────────────────┴──────┴──────┴──────┴──────┘/s', $reference, $found);
		# Tastaturreihen aufspalten
		$rows = preg_split('/\n├.*\n/', $found[1]);

		$n = 1;
		# Für jede Reihe:
		foreach ($rows as $row) {
			$sub_rows = preg_split('/\n/', $row);

			# Finde Zeichen in beiden Zeilen.
			# U030F ist ein combining-character und tritt zusammen mit einem Leerzeichen auf, damit es angezeigt wird
			preg_match_all('/│(.) (\x{030F} |.) (.)(?=│)/u', $sub_rows[0], $r1);
			preg_match_all('/│(.) (.) (.)(?=│)/u', $sub_rows[1], $r2);

			# Für jede Taste:
			for ($i = 0; $i < count($r1[0]); $i++) {
				# Überspringe Enter-Taste, die nicht dazugehört
				if ($r1[2][$i] == "\xE2\x86\xB2" && $i == 11) {
					$n--;
					continue;
				}
				# Lade die Zeichen der beiden Zeilen in das Array, geordnet nach ihrer Ebene
				$key[$n+$i.'_1'] = $r2[1][$i];
				$key[$n+$i.'_2'] = $r1[1][$i];
				$key[$n+$i.'_3'] = $r2[2][$i];
				$key[$n+$i.'_4'] = $r1[2][$i];
				$key[$n+$i.'_5'] = $r2[3][$i];
				$key[$n+$i.'_6'] = $r1[3][$i];
			}
			$n += $i;
		}
		return $key;
	}

	# Nutzt utf8ToUnicode(), um einen Unicode-Codepoint zu finden, der dann hexadezimal formatiert zurückgegeben wird.
	function getUnicodeCodepoint ($char) {
		$codepoints = utf8ToUnicode($char);
		if (!$codepoints[0]) return false;
		return strtoupper(str_pad(dechex($codepoints[0]), 4, "0", STR_PAD_LEFT));
	}



	# Unicode Daten für die Zeichen-Benennung holen.
	# ----------------------------------------------
	ini_set('memory_limit', '128M');
	# Sehr hoher Speicherbedarf, deshalb wäre es eigentlich besser UnicodeData in einer Datenbank zu haben
	$ucdfull = file_get_contents('http://www.unicode.org/Public/UNIDATA/UnicodeData.txt');
	$ucdfull = explode("\n", $ucdfull);
	# Array erstellen, um leichter auf die Daten zugreifen zu können
	$ucd = array();
	foreach ($ucdfull as $chardata) {
		$chardata = explode(';', $chardata);
		$ucd[$chardata[0]] = $chardata;
	}

	# Erzeugbare Zeichen suchen
	# ---------------------------------------------

	# Alle Tasten mit ihren Zeichen aus der Referenz laden
	$keys = getNeoKeys();

	# Diese Zeichen müssen noch angehäng werden, da die Leertaste nicht eingelesen wird
	# Position der Zeichen ist irrelevant, siehe weiter unten
	$keys[] = ' '; # SPACE
	$keys[] = ' '; # NO-BREAK SPACE
	$keys[] = ' '; # NARROW NO-BREAK SPACE

	# Duplikate entfernen und alles sortieren
	$chars = array_unique($keys);


	sort($chars);

	# Für jedes Zeichen die Kombination(en) auslesen, mit der/denen es erzeugt werden kann
	# ------------------------------------------------

	foreach ($chars as $char) {
		$char = array('char' => $char);

		# Ausnahmen, deren Kombinationen falsch sind, oder uns nicht interessieren:
		# Diese Zeichen werden nicht erstellt, sondern sind in der Referenz nur zur Verdeutlichung da.
		if (in_array($char['char'], array('⇞','⇟','⇠','⇡','⇢','⇣','⇱','⇲','⇥','⌦','⌧','⌫','↲','↶','⎀'))) continue; # Control-Keys
		# Auch die Toten Tasten werden nicht normal erzeugt. Deren Zeichen können aber durch Auslesen der Compose gewonnen werden.
		if (in_array($char['char'], array('˜','ˇ','¯','ˆ','˚','˘',"\xCC\x8F\x20",'`','¨','῾','¸','˝','˙','´','᾿'))) continue; # Dead-Keys

		$char['codepoint'] = 'U+' . getUnicodeCodepoint($char['char']);
		# Namen des Zeichens aus der UnicodeData auslesen
		$char['name'] = $ucd[getUnicodeCodepoint($char['char'])][1];

		# Da $keys mit den Positionen als Index angeordnet ist, bietet es sich an,
		# das Array umzukehren und gleichzeitig nach dem entsprechenden Zeichen zu suchen
		$char_positions = array_keys($keys, $char['char'], true);

		$char['combos'] = array();
		foreach ($char_positions as $pos) {
			# Tastenposition aus Arraywert lesen
			$keypos = substr($pos, 0, strpos($pos, '_'));
			# Keine Toten Tasten absuchen, würde sonst z.B. "<Mod3> + <´>" für SOLIDUS finden
			if (in_array($keypos, array('1', '13', '25'))) continue; # 1 ≙ T1; 13 ≙ T2; 25 ≙ T3

			# Je nach Level werden unterschiedliche Modifier gebraucht
			$level = substr($pos, -1);
			switch ($level) {
				case '1':
					$combo = '<{{{' . $keys[$keypos . '_1'] . '}}}>';
					break;
				case '2':
					$combo = '<Shift> + <{{{' . $keys[$keypos . '_1'] . '}}}>';
					break;
				case '3':
					$combo = '<Mod3> + <{{{' . $keys[$keypos . '_1'] . '}}}>';
					break;
				case '4':
					$combo = '<Mod4> + <{{{' . $keys[$keypos . '_1'] . '}}}>';
					break;
				case '5':
					$combo = '<Shift> + <Mod3> + <{{{' . $keys[$keypos . '_1'] . '}}}>';
					break;
				case '6':
					$combo = '<Mod3> + <Mod4> + <{{{' . $keys[$keypos . '_1'] . '}}}>';
					break;

			}
			$char['combos'][] = $combo;

		}
		# Die Leertaste hat keine Positionierung, deshalb manuelle Namensvergabe
		# Das Leerzeichen tritt in der Referenz leider mehrfach auf (an Stellen, die noch Leer sind):
		if ($char['name'] == 'SPACE') $char['combos'] = array('<Leertaste>');
		if ($char['name'] == 'DIGIT ZERO') $char['combos'][] = '<Mod4> + <Leertaste>';
		if ($char['name'] == 'NO-BREAK SPACE') $char['combos'] = array('<Shift> + <Mod3> + <Leertaste>');
		if ($char['name'] == 'NARROW NO-BREAK SPACE') $char['combos'] = array('<Mod3> + <Mod4> + <Leertaste>');

		# FIXME Sortieren der Combos, einfachste zuerst.

		# Zusammensetzen der Variablen und Ausgabe als wiki-formatierte Tabelle.
		# ------------------------------------------------
		echo '||{{{' . $char['char'] . '}}}||' . $char['codepoint'] . '||' . $char['name'] . '||' .  implode('[[BR]]', $char['combos'])  . '||' . "\n";
	}

?>