<?php

declare(strict_types=1);

namespace StaticPHP\Util\System;

class LinuxUtil extends UnixUtil
{
    /** @var null|string libc version cache */
    public static ?string $libc_version = null;

    /**
     * Get current linux distro name and version.
     *
     * @return array{dist: string, ver: string, family: string} Linux distro info (unknown if not found)
     */
    public static function getOSRelease(): array
    {
        $ret = [
            'dist' => 'unknown',
            'ver' => 'unknown',
            'family' => 'unknown',
        ];

        if (file_exists('/etc/centos-release') || file_exists('/etc/redhat-release')) {
            $is_centos = file_exists('/etc/centos-release');
            $file = $is_centos ? '/etc/centos-release' : '/etc/redhat-release';
            $lines = file($file);

            foreach ($lines as $line) {
                if (preg_match('/release\s+(\d*(\.\d+)*)/', $line, $matches)) {
                    $ret['dist'] = $is_centos ? 'centos' : 'redhat';
                    $ret['ver'] = $matches[1];
                }
            }
        } elseif (file_exists('/etc/os-release')) {
            $lines = file('/etc/os-release');

            foreach ($lines as $line) {
                if (preg_match('/^ID=(.*)$/', $line, $matches)) {
                    $ret['dist'] = $matches[1];
                }
                if (preg_match('/^ID_LIKE=(.*)$/', $line, $matches)) {
                    $ret['family'] = $matches[1];
                }
                if (preg_match('/^VERSION_ID=(.*)$/', $line, $matches)) {
                    $ret['ver'] = $matches[1];
                }
            }

            $ret['dist'] = trim($ret['dist'], '"\'');
            $ret['ver'] = trim($ret['ver'], '"\'');
            $ret['family'] = trim($ret['family'], '"\'');

            if (strcasecmp($ret['dist'], 'centos') === 0) {
                $ret['dist'] = 'redhat';
            }
        }
        return $ret;
    }

    /**
     * Check if current linux distro is musl-based (alpine).
     */
    public static function isMuslDist(): bool
    {
        return static::getOSRelease()['dist'] === 'alpine';
    }

    /**
     * Get CPU core count.
     */
    public static function getCpuCount(): int
    {
        $ncpu = 1;

        if (is_file('/proc/cpuinfo')) {
            $cpuinfo = file_get_contents('/proc/cpuinfo');
            preg_match_all('/^processor/m', $cpuinfo, $matches);
            $ncpu = count($matches[0]);
        }

        return $ncpu;
    }

    /**
     * Get fully-supported linux distros.
     *
     * @return string[] List of supported Linux distro name for doctor
     */
    public static function getSupportedDistros(): array
    {
        return [
            // debian-like
            'debian', 'ubuntu', 'Deepin',
            // rhel-like
            'redhat',
            // centos
            'centos',
            // alpine
            'alpine',
            // arch
            'arch', 'manjaro',
        ];
    }

    /**
     * Check if current linux distro is debian-based.
     */
    public static function isDebianDist(): bool
    {
        $dist = static::getOSRelease()['dist'];
        $family = explode(' ', static::getOSRelease()['family']);
        return in_array($dist, ['debian', 'ubuntu', 'Deepin', 'neon']) || in_array('debian', $family);
    }

    /**
     * Get libc version string from ldd.
     */
    public static function getLibcVersionIfExists(?string $libc = null): ?string
    {
        if (self::$libc_version !== null) {
            return self::$libc_version;
        }
        if ($libc === 'glibc') {
            $result = shell()->execWithResult('ldd --version', false);
            if ($result[0] !== 0) {
                return null;
            }
            // get first line
            $first_line = $result[1][0];
            // match ldd version: "ldd (some useless text) 2.17" match 2.17
            $pattern = '/ldd\s+\(.*?\)\s+(\d+\.\d+)/';
            if (preg_match($pattern, $first_line, $matches)) {
                self::$libc_version = $matches[1];
                return self::$libc_version;
            }
            return null;
        }
        if ($libc === 'musl') {
            if (self::isMuslDist()) {
                $result = shell()->execWithResult('ldd 2>&1', false);
            } elseif (is_file('/usr/local/musl/lib/libc.so')) {
                $result = shell()->execWithResult('/usr/local/musl/lib/libc.so 2>&1', false);
            } else {
                $arch = php_uname('m');
                $result = shell()->execWithResult("/lib/ld-musl-{$arch}.so.1 2>&1", false);
            }
            // Match Version * line
            // match ldd version: "Version 1.2.3" match 1.2.3
            $pattern = '/Version\s+(\d+\.\d+\.\d+)/';
            if (preg_match($pattern, $result[1][1] ?? '', $matches)) {
                self::$libc_version = $matches[1];
                return self::$libc_version;
            }
        }
        return null;
    }
}
