<?php
namespace nur\sery;

use RuntimeException;
use Throwable;

/**
 * Class UserException: une exception qui peut en plus contenir un message
 * utilisateur
 */
class UserException extends RuntimeException {
  /** @param Throwable|ExceptionShadow $e */
  static function get_user_message($e): ?string {
    if ($e instanceof self) return $e->getUserMessage();
    else return null;
  }

  /** @param Throwable|ExceptionShadow $e */
  static final function get_user_summary($e): string {
    $parts = [];
    $first = true;
    while ($e !== null) {
      $message = self::get_user_message($e);
      if (!$message) $message = "(no message)";
      if ($first) $first = false;
      else $parts[] = "caused by ";
      $parts[] = get_class($e) . ": " . $message;
      $e = $e->getPrevious();
    }
    return implode(", ", $parts);
  }

  /** @param Throwable|ExceptionShadow $e */
  static function get_message($e): ?string {
    $message = $e->getMessage();
    if (!$message && $e instanceof self) $message = $e->getUserMessage();
    return $message;
  }

  /** @param Throwable|ExceptionShadow $e */
  static final function get_summary($e): string {
    $parts = [];
    $first = true;
    while ($e !== null) {
      $message = self::get_message($e);
      if (!$message) $message = "(no message)";
      if ($first) $first = false;
      else $parts[] = "caused by ";
      if ($e instanceof ExceptionShadow) $class = $e->getClass();
      else $class = get_class($e);
      $parts[] = "$class: $message";
      $e = $e->getPrevious();
    }
    return implode(", ", $parts);
  }

  /** @param Throwable|ExceptionShadow $e */
  static final function get_traceback($e): string {
    $tbs = [];
    $previous = false;
    while ($e !== null) {
      if (!$previous) {
        $efile = $e->getFile();
        $eline = $e->getLine();
        $tbs[] = "at $efile($eline)";
      } else {
        $tbs[] = "~~ caused by: " . self::get_summary($e);
      }
      $tbs[] = $e->getTraceAsString();
      $e = $e->getPrevious();
      $previous = true;
      #XXX il faudrait ne pas réinclure les lignes communes aux exceptions qui
      # ont déjà été affichées
    }
    return implode("\n", $tbs);
  }

  function __construct($userMessage, $techMessage=null, $code=0, ?Throwable $previous=null) {
    $this->userMessage = $userMessage;
    if ($techMessage === null) $techMessage = $userMessage;
    parent::__construct($techMessage, $code, $previous);
  }

  /** @var ?string */
  protected $userMessage;

  function getUserMessage(): ?string {
    return $this->userMessage;
  }
}
