<?php

namespace engine\logging;

abstract class Logger
{
    protected bool $enabled = false;
    protected int $counter = 0;
    protected int $recursionLevel = 0;

    /** @var ?callable $filter */
    protected $filter = null;

    public function setErrorFilter(callable $filter): void {
        $this->filter = $filter;
    }

    public function disable(): void {
        $this->enabled = false;
    }

    public function enable(): void {
        static $error_handler_set = false;
        $this->enabled = true;

        if ($error_handler_set)
            return;

        $self = $this;

        set_error_handler(function ($no, $str, $file, $line) use ($self) {
            if (!$self->enabled)
                return;

            if (is_callable($self->filter) && !($self->filter)($no, $file, $line, $str))
                return;

            static::write(LogLevel::ERROR, $str,
                errno: $no,
                errfile: $file,
                errline: $line);
        });

        set_exception_handler(function (\Throwable $e): void {
            static::write(LogLevel::ERROR, get_class($e).': '.$e->getMessage(),
                errfile: $e->getFile() ?: '?',
                errline: $e->getLine() ?: 0,
                stacktrace: $e->getTraceAsString());
            exit(1);
        });

        register_shutdown_function(function () use ($self) {
            if (!$self->enabled || !($error = error_get_last()))
                return;

            if (is_callable($self->filter)
                && !($self->filter)($error['type'], $error['file'], $error['line'], $error['message'])) {
                return;
            }

            static::write(LogLevel::ERROR, $error['message'],
                errno: $error['type'],
                errfile: $error['file'],
                errline: $error['line']);
        });

        $error_handler_set = true;
    }

    public function log(LogLevel $level, ?string $stacktrace = null, ...$args): void {
        if (!isDev() && $level == LogLevel::DEBUG)
            return;
        $this->write($level, Util::strVars($args),
            stacktrace: $stacktrace);
    }

    public function canReport(): bool {
        return $this->recursionLevel < 3;
    }

    protected function write(LogLevel $level,
                             string   $message,
                             ?int     $errno = null,
                             ?string  $errfile = null,
                             ?string  $errline = null,
                             ?string  $stacktrace = null): void {
        $this->recursionLevel++;

        if ($this->canReport())
            $this->writer($level, $this->counter++, $message, $errno, $errfile, $errline, $stacktrace);

        $this->recursionLevel--;
    }

    abstract protected function writer(LogLevel $level,
                                       int      $num,
                                       string   $message,
                                       ?int     $errno = null,
                                       ?string  $errfile = null,
                                       ?string  $errline = null,
                                       ?string  $stacktrace = null): void;
}