#!/usr/bin/php
<?php

final class PwUniqStore
{
    private const PRIMARY_DIR = '/var/lib/pw-uniq';
    private const PRIMARY_FILE = self::PRIMARY_DIR . '/uniq.id';

    public static function loadOrCreate(): string
    {
        $value = self::load();
        if ($value !== null) {
            return $value;
        }

        echo "uniqid file not exists... creating...\r\n";
        return self::create(null, false);
    }

    public static function load(): ?string
    {
        if (!is_file(self::PRIMARY_FILE)) {
            return null;
        }

        $value = trim((string) file_get_contents(self::PRIMARY_FILE));
        if (self::isValid($value)) {
            return $value;
        }

        return null;
    }

    public static function create(?callable $accept = null, bool $showProgress = false): string
    {
        do {
            if ($showProgress) {
                echo '.';
            }
            $value = md5(uniqid('pw-uniq', true));
        } while ($accept !== null && !$accept($value));

        if ($showProgress) {
            echo "\r\n";
        }

        self::write($value);
        return $value;
    }

    public static function percentage(string $value): int
    {
        $hash = $value . sha1($value);
        $number = 0;
        for ($i = 0; $i < strlen($hash); $i += 2) {
            $number += hexdec(substr($hash, $i, 2));
        }

        return $number % 1000;
    }

    private static function write(string $value): void
    {
        self::ensureDir(self::PRIMARY_DIR);
        if (file_put_contents(self::PRIMARY_FILE, $value . "\n") === false) {
            fwrite(STDERR, "failure: cannot write " . self::PRIMARY_FILE . "\r\n");
            exit(255);
        }

        $readBack = trim((string) file_get_contents(self::PRIMARY_FILE));
        if ($readBack !== $value) {
            fwrite(STDERR, "failure: readback mismatch\r\n");
            exit(255);
        }
    }

    private static function ensureDir(string $dir): void
    {
        if (is_dir($dir)) {
            return;
        }
        if (!mkdir($dir, 0755, true) && !is_dir($dir)) {
            fwrite(STDERR, "failure: cannot create " . $dir . "\r\n");
            exit(255);
        }
    }

    private static function isValid(string $value): bool
    {
        return (bool) preg_match('/^[0-9a-f]{32}$/', $value);
    }
}

function usage(): void
{
    echo "Usage: pw-uniq [option]\r\n";
    echo "- ensure: create uniqid only if missing\r\n";
    echo "- setnew: calculate new uniqid\r\n";
    echo "- setnew_perc_max X: X must 1-999, generated perc gets smaller/equal\r\n";
    echo "- setnew_perc_min X: X must 1-999, generated perc gets bigger/equal\r\n";
    echo "- getuniq: load uniqid\r\n";
    echo "- getperc: show my early adopt perc (0-999)\r\n";
    echo "- iftop X: return 1 if we are in top X, return 2 if we are not in top X\r\n";
    echo "\tExample:\r\n";
    echo "\t\tpw-uniq iftop 500 -> 50:50 chance that we are in the 500er group\r\n";
    echo "\t\tpw-uniq iftop 10  -> 1% chance that we are in the top 1% group\r\n";
}

function requireThreshold(array $argv): int
{
    if (!isset($argv[2]) || !ctype_digit((string) $argv[2])) {
        echo "X must between 1-999 (%0 - promille a)\r\n";
        exit(255);
    }

    $threshold = (int) $argv[2];
    if ($threshold < 1 || $threshold >= 999) {
        echo "X must between 1-999 (%0 - promille b)\r\n";
        exit(255);
    }
    if($threshold>999)$threshold=999;

    return $threshold;
}

$command = $argv[1] ?? '';

if ($command === 'ensure') {
    $value = PwUniqStore::loadOrCreate();
    echo "Your uniq is: " . $value . "\r\n";
    exit(0);
}

if ($command === 'setnew') {
    echo "set new uniqid...\r\n";
    $value = PwUniqStore::create();
    echo "Your uniq is: " . $value . "\r\n";
    echo "ok\r\n";
    exit(0);
}

if ($command === 'setnew_perc_max' || $command === 'setnew_perc_min') {
    $threshold = requireThreshold($argv);
    echo "set new uniqid...";
    $value = PwUniqStore::create(
        static function (string $candidate) use ($command, $threshold): bool {
            $percentage = PwUniqStore::percentage($candidate);
            if ($command === 'setnew_perc_max') {
                return $percentage <= $threshold;
            }
            return $percentage >= $threshold;
        },
        true
    );
    echo "Your uniq is: " . $value . "\r\n";
    echo "ok\r\n";
    exit(0);
}

if ($command === 'getuniq') {
    echo "Your uniq is: " . PwUniqStore::loadOrCreate() . "\r\n";
    exit(0);
}

if ($command === 'getperc') {
    $value = PwUniqStore::loadOrCreate();
    echo "Your perc is: " . PwUniqStore::percentage($value) . "\r\n";
    exit(0);
}

if ($command === 'iftop') {
    $threshold = requireThreshold($argv);
    $value = PwUniqStore::loadOrCreate();
    $percentage = PwUniqStore::percentage($value);
    if ($percentage <= $threshold) {
        echo "You are in top " . $threshold . " - return 1\r\n";
        exit(1);
    }

    echo "You are NOT in top " . $threshold . " - return 2\r\n";
    exit(2);
}

usage();
exit(255);
