<?php

/**
 * Error Hadler logging
 *
 * Standard: PSR-2
 *
 * @link http://www.php-fig.org/psr/psr-2 Full Documentation
 *
 * @package SC\DUPX\Log
 */

namespace Duplicator\Installer\Utils\Log;

use Duplicator\Installer\Core\Bootstrap;

class LogHandler
{
    const MODE_OFF         = 0; // don't write in log
    const MODE_LOG         = 1; // write errors in log file
    const MODE_VAR         = 2; // put php errors in $varModeLog static var
    const SHUTDOWN_TIMEOUT = 'tm';
    const ERRNO_EXCEPTION  = 1073741824; // 31 pos of bit mask

    /**
     * Set error handler
     *
     * @return void
     */
    public static function initErrorHandler(): void
    {
        Bootstrap::disableBootShutdownFunction();

        set_error_handler([self::class, 'error']);
        register_shutdown_function([self::class, 'shutdown']);
    }

    /**
     *
     * @var array<string, mixed>
     */
    private static $shutdownReturns = ['tm' => 'timeout'];

    /**
     *
     * @var int
     */
    private static $handlerMode = self::MODE_LOG;

    /**
     *
     * @var bool // print code reference and errno at end of php error line  [CODE:10|FILE:test.php|LINE:100]
     */
    private static $codeReference = true;

    /**
     *
     * @var bool // print prefix in php error line [PHP ERR][WARN] MSG: .....
     */
    private static $errPrefix = true;

    /**
     *
     * @var string PHP errors in MODE_VAR
     */
    private static $varModeLog = '';

    /**
     * Error handler
     *
     * @param integer $errno   Error level
     * @param string  $errstr  Error message
     * @param string  $errfile Error file
     * @param integer $errline Error line
     *
     * @return bool
     */
    public static function error($errno, $errstr, $errfile, $errline): bool
    {
        switch (self::$handlerMode) {
            case self::MODE_OFF:
                if ($errno == E_ERROR) {
                    $log_message = self::getMessage($errno, $errstr, $errfile, $errline);
                    Log::error($log_message);
                }
                break;
            case self::MODE_VAR:
                self::$varModeLog .= self::getMessage($errno, $errstr, $errfile, $errline) . "\n";
                break;
            case self::MODE_LOG:
            default:
                switch ($errno) {
                    case E_ERROR:
                        $log_message = self::getMessage($errno, $errstr, $errfile, $errline);
                        Log::error($log_message);
                        break;
                    case E_NOTICE:
                    case E_WARNING:
                    default:
                        $log_message = self::getMessage($errno, $errstr, $errfile, $errline);
                        Log::info($log_message);
                        break;
                }
        }

        return true;
    }

    /**
     * Get error message from erro data
     *
     * @param int    $errno   error code
     * @param string $errstr  error message
     * @param string $errfile file
     * @param int    $errline line
     *
     * @return string
     */
    private static function getMessage($errno, $errstr, $errfile, $errline): string
    {
        $result = '';

        if (self::$errPrefix) {
            $result = '[PHP ERR][' . self::errnoToString($errno) . '] MSG:';
        }

        $result .= $errstr;

        if (self::$codeReference) {
            $result .= ' [CODE:' . $errno . '|FILE:' . $errfile . '|LINE:' . $errline . ']';
            if (Log::isLevel(Log::LV_DEBUG)) {
                // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
                $result .= "\n" . Log::traceToString(debug_backtrace(), 1, 1);
            }
        }

        return $result;
    }

    /**
     * Errno code to string
     *
     * @param int $errno error code
     *
     * @return string
     */
    public static function errnoToString($errno): string
    {
        switch ($errno) {
            case E_PARSE:
                return 'E_PARSE';
            case E_ERROR:
                return 'E_ERROR';
            case E_CORE_ERROR:
                return 'E_CORE_ERROR';
            case E_COMPILE_ERROR:
                return 'E_COMPILE_ERROR';
            case E_USER_ERROR:
                return 'E_USER_ERROR';
            case E_WARNING:
                return 'E_WARNING';
            case E_USER_WARNING:
                return 'E_USER_WARNING';
            case E_COMPILE_WARNING:
                return 'E_COMPILE_WARNING';
            case E_NOTICE:
                return 'E_NOTICE';
            case E_USER_NOTICE:
                return 'E_USER_NOTICE';
            case self::ERRNO_EXCEPTION:
                return 'EXCEPTION';
            default:
                break;
        }
        if (defined('E_RECOVERABLE_ERROR') && $errno === E_RECOVERABLE_ERROR) {
            return 'E_RECOVERABLE_ERROR';
        }
        if (defined('E_DEPRECATED') && $errno === E_DEPRECATED) {
            return 'E_DEPRECATED';
        }
        if (defined('E_USER_DEPRECATED') && $errno === E_USER_DEPRECATED) {
            return 'E_USER_DEPRECATED';
        }
        return 'E_UNKNOWN CODE: ' . $errno;
    }

    /**
     * if setMode is called without params set as default
     *
     * @param int  $mode          log mode
     * @param bool $errPrefix     print prefix in php error line [PHP ERR][WARN] MSG: .....
     * @param bool $codeReference print code reference and errno at end of php error line  [CODE:10|FILE:test.php|LINE:100]
     *
     * @return void
     */
    public static function setMode($mode = self::MODE_LOG, $errPrefix = true, $codeReference = true): void
    {
        switch ($mode) {
            case self::MODE_OFF:
            case self::MODE_VAR:
                self::$handlerMode = $mode;
                break;
            case self::MODE_LOG:
            default:
                self::$handlerMode = self::MODE_LOG;
        }

        self::$varModeLog    = '';
        self::$errPrefix     = $errPrefix;
        self::$codeReference = $codeReference;
    }

    /**
     *
     * @return string // return var log string in MODE_VAR
     */
    public static function getVarLog()
    {
        return self::$varModeLog;
    }

    /**
     *
     * @return string // return var log string in MODE_VAR and clean var
     */
    public static function getVarLogClean()
    {
        $result           = self::$varModeLog;
        self::$varModeLog = '';
        return $result;
    }

    /**
     * Set shutdown print string
     *
     * @param string $status status type
     * @param string $str    string to print if is shouddown status
     *
     * @return void
     */
    public static function setShutdownReturn($status, $str): void
    {
        self::$shutdownReturns[$status] = $str;
    }

    /**
     * Shutdown handler
     *
     * @return void
     */
    public static function shutdown(): void
    {
        if (($error = error_get_last())) {
            if (preg_match('/^Maximum execution time (?:.+) exceeded$/i', $error['message'])) {
                echo self::$shutdownReturns[self::SHUTDOWN_TIMEOUT];
            }
            self::error($error['type'], $error['message'], $error['file'], $error['line']);
        }
    }
}
