<?php

function htmlescape(string|array $s): string|array {
    if (is_array($s)) {
        foreach ($s as $k => $v) {
            $s[$k] = htmlescape($v);
        }
        return $s;
    }
    return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

function sizeString(int $size): string {
    $ks = ['B', 'KiB', 'MiB', 'GiB'];
    foreach ($ks as $i => $k) {
        if ($size < pow(1024, $i + 1)) {
            if ($i == 0)
                return $size.' '.$k;
            return round($size / pow(1024, $i), 2).' '.$k;
        }
    }
    return $size;
}

function extension(string $name): string {
    $expl = explode('.', $name);
    return end($expl);
}

/**
 * @param string $filename
 * @return resource|bool
 */
function imageopen(string $filename) {
    $size = getimagesize($filename);
    $types = [
        1 => 'gif',
        2 => 'jpeg',
        3 => 'png'
    ];
    if (!$size || !isset($types[$size[2]]))
        return null;
    return call_user_func('imagecreatefrom'.$types[$size[2]], $filename);
}

function detectImageType(string $filename) {
    $size = getimagesize($filename);
    $types = [
        1 => 'gif',
        2 => 'jpg',
        3 => 'png'
    ];
    if (!$size || !isset($types[$size[2]]))
        return false;
    return $types[$size[2]];
}

function transliterate(string $string): string {
    $roman = [
        'Sch', 'sch', 'Yo', 'Zh', 'Kh', 'Ts', 'Ch', 'Sh', 'Yu', 'ya', 'yo',
        'zh', 'kh', 'ts', 'ch', 'sh', 'yu', 'ya', 'A', 'B', 'V', 'G', 'D', 'E',
        'Z', 'I', 'Y', 'K', 'L', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'U', 'F',
        '', 'Y', '', 'E', 'a', 'b', 'v', 'g', 'd', 'e', 'z', 'i', 'y', 'k',
        'l', 'm',  'n', 'o', 'p', 'r', 's', 't', 'u', 'f', '', 'y', '', 'e'
    ];
    $cyrillic = [
        'Щ', 'щ', 'Ё', 'Ж', 'Х', 'Ц', 'Ч', 'Ш', 'Ю', 'я', 'ё', 'ж', 'х', 'ц',
        'ч', 'ш', 'ю', 'я', 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'З', 'И', 'Й', 'К',
        'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Ь', 'Ы', 'Ъ', 'Э',
        'а', 'б', 'в', 'г', 'д', 'е', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о',
        'п', 'р', 'с', 'т', 'у', 'ф', 'ь', 'ы', 'ъ', 'э'
    ];
    return str_replace($cyrillic, $roman, $string);
}

/**
 * @param resource $img
 * @param ?int $w
 * @param ?int $h
 * @param ?int[] $transparent_color
 */
function imageresize(&$img, ?int $w = null, ?int $h = null, ?array $transparent_color = null) {
    assert(is_int($w) || is_int($h));

    $curw = imagesx($img);
    $curh = imagesy($img);

    if (!is_int($w) && is_int($h)) {
        $w = round($curw / ($curw / $w));
    } else if (is_int($w) && !is_int($h)) {
        $h = round($curh / ($curh / $h));
    }

    $img2 = imagecreatetruecolor($w, $h);
    if (is_array($transparent_color)) {
        list($r, $g, $b) = $transparent_color;
        $col = imagecolorallocate($img2,  $r, $g, $b);
        imagefilledrectangle($img2, 0, 0, $w, $h, $col);
    } else {
        imagealphablending($img2, false);
        imagesavealpha($img2, true);
        imagefilledrectangle($img2, 0, 0, $w, $h, imagecolorallocatealpha($img2, 255, 255, 255, 127));
    }

    imagecopyresampled($img2, $img, 0, 0, 0, 0, $w, $h, $curw, $curh);
    imagedestroy($img);

    $img = $img2;
}

function rrmdir(string $dir, bool $dont_delete_dir = false): bool {
    if (!is_dir($dir)) {
        logError('rrmdir: '.$dir.' is not a directory');
        return false;
    }

    $objects = scandir($dir);
    foreach ($objects as $object) {
        if ($object != '.' && $object != '..') {
            if (is_dir($dir.'/'.$object)) {
                rrmdir($dir.'/'.$object);
            } else {
                unlink($dir.'/'.$object);
            }
        }
    }

    if (!$dont_delete_dir)
        rmdir($dir);

    return true;
}

function ip2ulong(string $ip): int {
    return sprintf("%u", ip2long($ip));
}

function ulong2ip(int $ip): string {
    $long = 4294967295 - ($ip - 1);
    return long2ip(-$long);
}

function fromCamelCase(string $s): string {
    $buf = '';
    $len = strlen($s);
    for ($i = 0; $i < $len; $i++) {
        if (!ctype_upper($s[$i])) {
            $buf .= $s[$i];
        } else {
            $buf .= '_'.strtolower($s[$i]);
        }
    }
    return $buf;
}

function toCamelCase(string $input, string $separator = '_'): string {
    return lcfirst(str_replace($separator, '', ucwords($input, $separator)));
}

function strReplaceOnce(string $needle, string $replace, string $haystack) {
    $pos = strpos($haystack, $needle);
    if ($pos !== false)
        $haystack = substr_replace($haystack, $replace, $pos, strlen($needle));
    return $haystack;
}

function strgen(int $len): string {
    $buf = '';
    for ($i = 0; $i < $len; $i++) {
        $j = mt_rand(0, 61);
        if ($j >= 36) {
            $j += 13;
        } else if ($j >= 10) {
            $j += 7;
        }
        $buf .= chr(48 + $j);
    }
    return $buf;
}

function sanitizeFilename(string $name): string {
    $name = mb_strtolower($name);
    $name = transliterate($name);
    $name = preg_replace('/[^\w\d\-_\s.]/', '', $name);
    $name = preg_replace('/\s+/', '_', $name);
    return $name;
}

function setperm(string $file): void {
    global $config;

    // chgrp
    $gid = filegroup($file);
    if ($gid != $config['group']) {
        if (!chgrp($file, $config['group'])) {
            logError(__FUNCTION__.": chgrp() failed on $file");
        }
    }

    // chmod
    $perms = fileperms($file);
    $need_perms = is_dir($file) ? $config['dirs_mode'] : $config['files_mode'];
    if (($perms & $need_perms) !== $need_perms) {
        if (!chmod($file, $need_perms)) {
            logError(__FUNCTION__.": chmod() failed on $file");
        }
    }
}

function saltPassword(string $pwd): string {
    global $config;
    return hash('sha256', "{$pwd}|{$config['password_salt']}");
}

function exectime(?string $format = null): string|float {
    $time = round(microtime(true) - START_TIME, 4);
    if (!is_null($format))
        $time = sprintf($format, $time);
    return $time;
}

function formatNumber(int|float $num, string $delim = ' ', bool $short = false): string {
    if ($short) {
        if ($num >= 1000000)
            return floor($num / 1000000).'m';
        if ($num >= 1000)
            return floor($num / 1000).'k';
    }
    return number_format($num, 0, '.', $delim);
}

function jsonEncode($obj): ?string { return json_encode($obj, JSON_UNESCAPED_UNICODE) ?: null; }
function jsonDecode($json) { return json_decode($json, true); }

function pcreNoError(mixed &$result, bool $no_error = false): bool {
    if ($result === null) {
        if (preg_last_error() !== PREG_NO_ERROR) {
            if (!$no_error)
                logError('an error occurred while PCRE regex execution: '.preg_last_error_msg());
            return false;
        }
    }
    return true;
}

function highlightSubstring(string $s, string|array|null $keywords = []): string {
    if (is_null($keywords))
        return htmlescape($s);

    if ($keywords instanceof Stringable)
        $keywords = $keywords->__toString();
    else if (is_array($keywords)) {
        $keywords = array_map(function($s) {
            if ($s instanceof Stringable)
                return $s->__toString();
            return $s;
        }, $keywords);
    }

    if (is_string($keywords))
        $keywords = preg_split('/\s+/', $keywords);

    $charset = 'utf-8';
    $s_len = mb_strlen($s, $charset);

    usort($keywords, function($a, $b) use (&$charset) {
        return mb_strlen($b, $charset) - mb_strlen($a, $charset);
    });

    $all = [];
    foreach ($keywords as $kw) {
        $kw_len = mb_strlen($kw, $charset);
        $offset = 0;
        while (($pos = mb_stripos($s, $kw, $offset, $charset)) !== false) {
            $offset = $pos + 1;
            $all[] = [$pos, $kw_len];
        }
    }

    usort($all, function($a, $b) {
        return $a[0] - $b[0];
    });

    $last_index = 0;
    $buf = '';
    foreach ($all as $range) {
        list($start_pos, $len) = $range;
        if ($start_pos < $last_index) {
            continue;
        }

        if ($start_pos > $last_index) {
            $buf .= htmlescape(mb_substr($s, $last_index, $start_pos - $last_index, $charset));
        }

        $buf .= '<span class="matched">'.htmlescape(mb_substr($s, $start_pos, $len, $charset)).'</span>';
        $last_index = $start_pos + $len;
    }

    if ($last_index < $s_len) {
        $buf .= htmlescape(mb_substr($s, $last_index, $s_len - $last_index, $charset));
    }

    return $buf;
}

function formatTime($ts, array $opts = []) {
    $default_opts = [
        'date_only' => false,
        'day_of_week' => false,
        'seconds' => false,
        'short_months' => false,
        'skip_today_date' => false,
        'skip_old_time' => false,
        'at' => null,
    ];
    $opts = array_merge($default_opts, $opts);

    $is_today = date('Ymd') == date('Ymd', $ts);
    $date = '';
    $skip_old_time = false;

    if (!$is_today || !$opts['skip_today_date']) {
        $y = (int)date('Y', $ts);
        $date .= date('j '.(!$opts['short_months'] ? 'F' : 'M'), $ts);
        if ($y != (int)date('Y')) {
            $date .= ' ' . $y;
            if ($opts['skip_old_time']) {
                $skip_old_time = true;
            }
        }
        if ($opts['day_of_week']) {
            $date .= ', ' . date('l', $ts);
        }
    }

    if (!$opts['date_only'] && !$skip_old_time) {
        if ($date != '') {
            $date .= $opts['at'] ?? ' at ';
        }
        $date .= date('H:i', $ts);
        if ($opts['seconds'])
            $date .= date(':s', $ts);
    }

    return $date;
}

