<?php

class ErrorHandler
{
  public string $Encoding;
  public bool $ShowError;
  public int $UserErrors;
  public $OnError;

  function __construct()
  {
    $this->Encoding = 'utf-8';
    $this->ShowError = false;
    $this->UserErrors = E_ALL;  //E_ERROR | E_WARNING | E_PARSE;
    $this->OnError = array(array($this, 'ShowDefaultError'));
  }

  function Start(): void
  {
    set_error_handler(array($this, 'ErrorHandler'));
    set_exception_handler(array($this, 'ExceptionHandler'));
  }

  function Stop(): void
  {
    restore_error_handler();
    restore_exception_handler();
  }

  function ErrorHandler(int $Number, string $Message, string $FileName, int $LineNumber, array $Variables = array()): bool
  {
    $ErrorType = array
    (
      1 => 'Error',
      2 => 'Warning',
      4 => 'Parsing Error',
      8 => 'Notice',
      16 => 'Core Error',
      32 => 'Core Warning',
      64 => 'Compile Error',
      128 => 'Compile Warning',
      256 => 'User Error',
      512 => 'User Warning',
      1024 => 'User Notice'
    );

    if (($this->UserErrors & $Number))
    {
      // Error was suppressed with the @-operator
      if (0 === error_reporting())
      {
        return false;
      }
      $Backtrace = debug_backtrace();
      $Backtrace[0]['function'] = $Message;
      $Backtrace[0]['args'] = '';
      $Backtrace[0]['file'] = $FileName;
      $Backtrace[0]['line'] = $LineNumber;
      $this->Report($Backtrace);
      //if ((E_ERROR | E_PARSE) & $Number)
      die();
    }
  }

  function ExceptionHandler(Throwable $Exception): void
  {
    $Backtrace = $Exception->getTrace();
    array_unshift($Backtrace, array(
      'function' => $Exception->getMessage(),
      'file' => $Exception->getFile(),
      'line' => $Exception->getLine(),
    ));
    $this->Report($Backtrace);
    die();
  }

  function ShowDefaultError(string $Message): void
  {
    $Output = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head>'."\n".
      '<meta http-equiv="Content-Language" content="en">'."\n".
      '<meta http-equiv="Content-Type" content="text/html; charset='.$this->Encoding.'"></head><body>'."\n".
      'An internal error occurred!<br/>'.
      'Administrator will be notified and the problem will be investigated and fixed soon.'.'<br/><br/>';
    if ($this->ShowError == true)
      $Output .= '<pre>'.$Message.'</pre><br/>';
    $Output .= '</body></html>';
    echo($Output);
  }

  function Report(array $Backtrace): void
  {
    $Date = date('Y-m-d H:i:s');
    $Error = '# '.$Date."\n";
    foreach ($Backtrace as $Item)
    {
      if (!array_key_exists('line', $Item)) $Item['line'] = '';
      if (!array_key_exists('file', $Item)) $Item['file'] = '';

      $Error .= ' '.$Item['file'].'('.$Item['line'].")\t".$Item['function'];
      $Arguments = '';
      if (array_key_exists('args', $Item) and is_array($Item['args']))
        foreach ($Item['args'] as $Item)
        {
          if (is_object($Item)) ;
          else if (is_array($Item)) $Arguments .= "'".serialize($Item)."',";
          else $Arguments .= "'".$Item."',";
        }
        if (strlen($Arguments) > 0) $Error .= '('.substr($Arguments, 0, -1).')';
        $Error .= "\n";
    }
    $Error .= "\n";

    foreach ($this->OnError as $OnError)
      call_user_func($OnError, $Error);
  }
}
