#!/usr/bin/env php
<?php

ini_set('error_reporting', E_ALL);

function td(...$a) { foreach ($a as $v) var_dump($v); die('td()'); }
function tp(...$a) { foreach ($a as $v) var_dump($v); echo 'tp()'; if (count($a)===1) return $a[0]; }

function die_with_error(int $code, string $str)
{
	fputs(STDERR, $str .PHP_EOL);
	die($code);
}

function terminal_output(string $str)
{
	$hinfo = STDERR;
	fputs($hinfo, $str);
}

function terminal_enqueue_flush(string $str = null, bool $do_flush = true) : int
{
	static $last_line_len = 0;
	static $buffer;

	if ($str !== null)
		$buffer .= $str;

	if ($do_flush) {
		$last_line_len = strlen($buffer);
		terminal_output($buffer);
		$buffer = ''; }

	return $last_line_len;
}

function terminal_last_line_len() : int
{
	return terminal_enqueue_flush(null, false);
}

function terminal_enqueue(string $str)
{
	terminal_enqueue_flush($str, $do_flush = false);
}

function terminal_flush_line()
{
	terminal_enqueue_flush(null, $do_flush = true);
}

function progress_num(int $pos, int $processed_bytes, int $total_bytes = null) : string
{
	$R = function(int $n = null) : ?int { if ($n === null) return $n; return $n / 1024; };
	$F2 = function(int $n = null) : string { if ($n === null) return '?'; return sprintf('%dk', $n); };
	return sprintf("%s / %s", $F2($R($processed_bytes)), $F2($R($total_bytes)));
}

function progress_bar(int $pos, int $processed_bytes, int $total_bytes = null) : string
{
	$endpA_marker = '[';
	$endpB_marker = ']';
	$vesegment_maker = '=';
	$vpsegment_marker = '>';
	$bsegment_marker = ' ';

	if ($total_bytes === null) {
		$v = 1.0;
		$perc_msg = '??%'; }
	else {
		$v = 1.0 * $processed_bytes / $total_bytes;
		$perc_msg = sprintf(' % 5.1f%%', $v * 100); }

	$perc_msg_width = strlen($perc_msg);
	$fudge = ' ';
	$fudge_width = strlen($fudge);

	$bar_width = terminal_nr_columns() - $pos - $perc_msg_width - $fudge_width;
	$endp_markers_width = strlen($endpA_marker .$endpB_marker);
	$vpsegment_marker_width = strlen($vpsegment_marker);
	$total_segments = $bar_width - $endp_markers_width;
	$total_segments = max(0, $total_segments);
	$total_segments = min($total_segments, $bar_width);

	$vsegments = floor($total_segments * $v);
	$vpsegments = ($vsegments > 0);
	$vesegments = $vsegments - ($vpsegments * $vpsegment_marker_width);
	$bsegments = $total_segments - $vsegments;

	return $endpA_marker .str_repeat($vesegment_maker, $vesegments) .str_repeat($vpsegment_marker, $vpsegments)
		.str_repeat($bsegment_marker, $bsegments) .$endpB_marker
		.$perc_msg;
}

function terminal_nr_columns() : int
{
	static $tput_n = null;
	if ($tput_n === null)
			# cast to INT to avoid untrusted data (zero is fine rly)
		$tput_n = (int)exec('tput cols');

		# doesn't seem to work?
		# the var exists in bash, but doesn't get exported to processes - why?
	$n = getenv('COLUMNS');
	if (empty($n))
		$n = $tput_n;
	if (empty($n))
		$n = 80;
	return $n;
}

function show_progress(int $processed_bytes, int $total_bytes = null) {
	$pos = 0;
	$XPOS = function(string $str) use(&$pos) : string
	{
		$pos += strlen($str);
		return $str;
	};

	if (1)
		terminal_op_clear_line();
	else
		terminal_output("\n");

	terminal_enqueue($XPOS(progress_num($pos, $processed_bytes, $total_bytes)));
	terminal_enqueue($XPOS($separator = ' '));
	terminal_enqueue($XPOS(progress_bar($pos, $processed_bytes, $total_bytes)));
	terminal_flush_line();
}

function adjust_quant(int $read_quant, int $processed_bytes) : int
{
	$a = [ $read_quant ];
	if ($processed_bytes > 128 * 1024)
		$a[] = 128 * 1024;
#	if ($processed_bytes > 1024*1024)
#		$a[] = 1024 * 1024;
	return max($a);
}

function terminal_is_lineaddr() : bool
{
	static $ret = null;

	if ($ret === null) {
		switch (getenv('TERM')) {
		default:
		case 'dumb':
			$ret = false;
			break;
		case 'linux':
		case 'xterm':
			$ret = true; } }

	return $ret;
}

function terminal_op_clear_line()
{
	if (terminal_is_lineaddr())
		terminal_lineaddr_op_clear_line();
	else
		terminal_dumb_op_clear_line();
}

function terminal_dumb_op_clear_line()
{
	$cnt = terminal_last_line_len();
	$rubout = "\x08";
	terminal_output(str_repeat($rubout, $cnt));
}

function terminal_lineaddr_op_clear_line()
{
	$str = "\r";
	terminal_output($str);
}

$hin = STDIN;
$hout = STDOUT;

$read_quant = 16 * 1024;
$processed_bytes = 0;
$total_bytes = null;

$a = fstat($hin);
if ($a['size'] > 0)
	$total_bytes = $a['size'];

$continue_copy = true;
while ($continue_copy) {
	if (feof($hin)) {
		$continue_copy = false;
		break; }
	if (feof($hout))
		die_with_error(1, 'Output write error');

	$data = fread($hin, $read_quant);
	if ($data === false)
		die_with_error(2, 'Input read error');

	$v = fwrite($hout, $data);
	if ($v === false)
		die_with_error(1, 'Output write error');

	$processed_bytes += strlen($data);
	show_progress($processed_bytes, $total_bytes);
	$read_quant = adjust_quant($read_quant, $processed_bytes);
}

terminal_output("\n");
