<?php

declare(strict_types=1);

namespace apexl\Io\modules\logger\services;

class progressBar
{
    private int $start = 0;

    private array $output;

    public function __construct(
        private readonly int $total,
        private readonly string $outputFile = 'php://stdout',
        private readonly int $size = 30
    ) {
    }

    function update(int $done): void
    {
        if ($this->outOfBounds($done)) {
            return;
        }

        $this->startTimer();

        $now = time();

        $this->output = [];
        $this->output[] = $this->bar($done);
        $this->output[] = $this->progress($done);
        $this->output[] = $this->timing($now, $done);
        $this->output[] = "\n";

        $this->sendOutput();
    }

    private function outOfBounds(int $done): bool
    {
        return ($done > $this->total);
    }

    private function startTimer(): void
    {
        if (!$this->start) {
            $this->start = time();
        }
    }

    private function bar(int $done): string
    {
        $bar = (int) floor($this->percent($done) * $this->size);

        $chunks = [];
        $chunks[] = '[';
        $chunks[] = str_repeat("=", $bar);
        if ($bar < $this->size) {
            $chunks[] = ">";
            $chunks[] = str_repeat(' ', $this->size - $bar);
        } else {
            $chunks[] = "=";
        }
        $chunks[] = ']';

        return implode('', $chunks);
    }

    private function percent(int $done): float
    {
        return (float) ($done / $this->total);
    }

    private function progress(int $done): string
    {
        $percent = $this->percent($done);

        $percentDisplay = number_format($percent * 100);

        return sprintf('%d%%  %s/%s.', $percentDisplay, number_format($done), number_format($this->total));
    }

    private function timing(int $now, int $done): string
    {
        $elapsed = $now - $this->start;

        return implode(' ', [
            sprintf(
                'Elapsed: %s.',
                date('H:i:s', $elapsed),
            ),
            sprintf(
                'Remaining: %s.',
                date('H:i:s', $this->eta($now, $done)),
            ),
        ]);
    }

    private function eta(int $now, int $done): int
    {
        $rate = ($now - $this->start) / $done;
        $left = $this->total - $done;

        return (int) round($rate * $left);
    }

    private function sendOutput(): void
    {
        $output = sprintf("\r%s", implode(' ', $this->output));

        $handle = fopen($this->outputFile, 'w');
        fwrite($handle, $output);
        fclose($handle);
    }
}
