#!/usr/bin/env php
<?php 
$doSubstitution = true;

function resetSpeed() {
	global $speed, $speedslowfactor, $speedmin;
	$speed = 400;
	$speedslowfactor = 1.1;
	$speedmin = 10;
}
resetSpeed();

$encrypt = true;
echo "Encrypt (enter E) or decrypt (enter D)? ";
$encryptInput = stream_get_line(STDIN, 1024, PHP_EOL);
if ($encryptInput == 'd' || $encryptInput == 'D') {
	$encrypt = false;
}

echo "Input password, end with enter. Press Ctrl+D to abort.\n";
$password = stream_get_line(STDIN, 1024, PHP_EOL);
if (!$password) {
	die("Ctrl+D - exiting\n");
}

echo "Input message, end with enter. Press Ctrl+D to abort.\n";
$message = stream_get_line(STDIN, 1024, PHP_EOL);
if (!$message) {
	die("Ctrl+D - exiting\n");
}

$closestSquare = ceil(sqrt(strlen($message)));
$padding = '';
for ($i = 0; $i < pow($closestSquare, 2) - strlen($message) - 1; $i++) {
	$padding .= chr(rand(32, 126));
}
if (strlen($padding) > 0) {
	$padding = ' ' . $padding;
}
$message .= $padding;

if ($encrypt) {
	echo "Starting encryption. Our square is:\n";
	displaySquare($message, $closestSquare);
	echo "Expanding password... ";
	$password = keyExpansion($password);
	echo "New password: $password\n";

	if ($doSubstitution) {
		echo "Applying password to each character...\n";
		for ($i = 0; $i < strlen($message); $i++) {
			$passindex = $i % strlen($password);
			$passchar = $password[$passindex];
			$newchar = chr((((ord($message[$i]) - 32) + (ord($passchar) - 32) + $i) % 95) + 32);
			echo "Char $i (" . $message[$i] . ', #' . (ord($message[$i]) - 32) . ") plus password char $passindex ($passchar, #" . (ord($passchar) - 32) . ") plus position $i results in $newchar (#" . (ord($newchar) - 32) . ")\n";
			$message[$i] = $newchar;
			displaySquare($message, $closestSquare);
		}
		resetSpeed();
		echo "Done applying character substitution. Now let's shift our square around!\n";
	}
	else {
		echo "Skipping substitution as requested...\n";
	}

	$sq = strToSq($message, $closestSquare);
	for ($i = 0; $i < strlen($password) - 1; $i += 2) {
		$passchars = $password[$i] . $password[$i + 1];
		$passvalue = (ord($password[$i]) - 32) * 96 + ord($password[$i + 1]) - 32;
		$dir        = $passvalue & bindec('000000000001');
		$horizshift = $passvalue & bindec('000000000010') >> 1;
		$row        = $passvalue & bindec('000001111100') >> 2;
		$amount     = $passvalue & bindec('111110000000') >> 7;
		$amount    += $i;
		$row       %= $closestSquare;
		$dir        = $dir > 0 ? 1 : -1;
		$horizshift = $horizshift > 0;
		$amount    %= $closestSquare;
		$amount    *= $dir;
		$rowcol     = $horizshift ? 'row' : 'column';
		echo "Password chars $i and " . ($i + 1) .  " ($passchars) make #" . ($passvalue - 32) . " which give us $rowcol $row.\nWe will shift $amount times.\n";
		if ($horizshift) {
			$original = $sq[$row];
			for ($j = 0; $j < $closestSquare; $j++) {
				$index = ($j + $amount) % $closestSquare;
				if ($index < 0) {
					$index += $closestSquare;
				}
				$sq[$row][$j] = $original[$index];
			}
		}
		else {
			$original = [];
			for ($j = 0; $j < $closestSquare; $j++) {
				$original[] = $sq[$j][$row];
			}

			for ($j = 0; $j < $closestSquare; $j++) {
				$index = ($j + $amount) % $closestSquare;
				if ($index < 0) {
					$index += $closestSquare;
				}
				$sq[$j][$row] = $original[$index];
			}
		}
		displaySquare2($sq);
	}

	echo "All done!\n";
	echo "Result: ";
	for ($x = 0; $x < $closestSquare; $x++) {
		for ($y = 0; $y < $closestSquare; $y++) {
			echo $sq[$y][$x];
		}
	}
	echo "\n";
}
else {
	echo "Starting decryption. Our square is:\n";
	$sq = strToSqRev($message, $closestSquare);
	displaySquare2($sq);

	$originalPassword = $password; // We need this later for substitution
	$password = keyExpansion($password);
	echo "Expanding password... new password: $password\n";

	$actionqueue = [];
	for ($i = 0; $i < strlen($password) - 1; $i += 2) {
		$passchars = $password[$i] . $password[$i + 1];
		$passvalue = (ord($password[$i]) - 32) * 96 + ord($password[$i + 1]) - 32;
		$dir        = $passvalue & bindec('000000000001');
		$horizshift = $passvalue & bindec('000000000010') >> 1;
		$row        = $passvalue & bindec('000001111100') >> 2;
		$amount     = $passvalue & bindec('111110000000') >> 7;
		$amount    += $i;
		$row       %= $closestSquare;
		$dir        = $dir > 0 ? 1 : -1;
		$amount    %= $closestSquare;
		$amount    *= $dir;
		$actionqueue[] = [-$amount, $horizshift, $row, $dir, $passchars, $passvalue];
	}

	$actionqueue = array_reverse($actionqueue);

	$i = 0;
	foreach ($actionqueue as $action) {
		$amount = $action[0];
		$horizshift = $action[1];
		$row = $action[2];
		$dir = $action[3];
		$passchars = $action[4];
		$passvalue = $action[5];
		$horizshift = $horizshift > 0 ? true : false;
		$rowcol = $horizshift ? 'row' : 'column';
		echo "Password chars $i and " . ($i + 1) .  " ($passchars) make #" . ($passvalue - 32) . " which give us $rowcol $row.\nWe will shift $amount times.\n";
		$i++;
		if ($dir > 0) {
			$original = $sq[$row];
			for ($j = 0; $j < $closestSquare; $j++) {
				$index = ($j + $amount) % $closestSquare;
				if ($index < 0) {
					$index += $closestSquare;
				}
				$sq[$row][$j] = $original[$index];
			}
		}
		else {
			$original = [];
			for ($j = 0; $j < $closestSquare; $j++) {
				$original[] = $sq[$j][$row];
			}

			for ($j = 0; $j < $closestSquare; $j++) {
				$index = ($j + $amount) % $closestSquare;
				if ($index < 0) {
					$index += $closestSquare;
				}
				$sq[$j][$row] = $original[$index];
			}
		}
		displaySquare2($sq);
	}

	$message = '';
	foreach ($sq as $y) {
		$message .= implode('', $y);
	}

	if ($doSubstitution) {
		resetSpeed();
		echo "Done shifting things around. Now reversing character substitution...\n";

		// Apply substitution
		for ($i = 0; $i < strlen($message); $i++) {
			$passindex = $i % strlen($password);
			$passchar = $password[$passindex];
			$newchar = chr(mod(((ord($message[$i]) - 32) - (ord($passchar) - 32) - $i), 95) + 32);
			echo "Char $i (" . $message[$i] . ', #' . (ord($message[$i]) - 32) . ") minus password char $passindex ($passchar, #" . (ord($passchar) - 32) . ") minus position $i results in $newchar (#" . (ord($newchar) - 32) . ")\n";
			$message[$i] = $newchar;
			displaySquare($message, $closestSquare);
		}
	}
	else {
		echo "Skipping substitution as requested\n";
	}

	// Done!
	echo "Done! Result: $message\n";
	echo "(Note that the last few characters are probably garbage.)\n";
}

function mod($n, $m) {
	$n = $n % $m;
	if ($n > 0)
		return $n;

	return ($n + abs($m)) % $m;
}

// Same as strToSq() except this reverts the x and y axis
function strToSqRev($str, $size) {
	$sq = [];
	for ($x = 0; $x < $size; $x++) {
		$tmpstr = substr($str, $x * $size, $size);
		for ($y = 0; $y < strlen($tmpstr); $y++) {
			if (!isset($sq[$y])) {
				$sq[$y] = [];
			}
			$sq[$y][$x] = $tmpstr[$y];
		}
	}
	return $sq;
}

function strToSq($str, $size) {
	$sq = [];
	for ($y = 0; $y < $size; $y++) {
		$tmpstr = substr($str, $y * $size, $size);
		$sq[$y] = [];
		for ($x = 0; $x < strlen($tmpstr); $x++) {
			$sq[$y][$x] = $tmpstr[$x];
		}
	}
	return $sq;
}

function displaySquare($message, $size) {
	global $speed, $speedslowfactor, $speedmin;
	$outstr = '';
	for ($y = 0; $y < $size; $y++) {
		$str = substr($message, $y * $size, $size);
		for ($i = 0; $i < strlen($str); $i++) {
			$outstr .= $str[$i] . ' ';
		}
		$outstr .= "\n";
	}
	echo $outstr . "\n";
	usleep($speed * 1000);
	$speed = max($speedmin, $speed / $speedslowfactor);
}

function displaySquare2($sq) {
	global $speed, $speedslowfactor, $speedmin;
	$outstr = '';
	foreach ($sq as $y) {
		foreach ($y as $x) {
			$outstr .= $x . ' ';
		}
		$outstr .= "\n";
	}
	echo $outstr . "\n";
	usleep($speed * 1000);
	$speed = max($speedmin, $speed / $speedslowfactor);
}

function keyExpansion($password) {
	$password .= $password;
	$originalPassword = $password;
	$i = ord($password[0]) + 1;
	while (strlen($originalPassword) > 1) {
		$password .= str_repeat($originalPassword . chr(($i % 95) + 32), strlen($originalPassword));
		$originalPassword = substr($originalPassword, 1);
		$i++;
	}
	$a = 43690;
	for ($i = 0; $i < strlen($password) - 1; $i++) {
		$password[$i] = chr((((ord($password[$i]) ^ $a) + $i) % 95) + 32);
		if ($a == 43690) {
			$a = 21845;
		}
		else {
			$a = 43690;
		}
	}
	return $password;
}