<?php
namespace nur\b;

use nur\A;

/**
 * Class ValueException: exception lancée quand une valeur n'est pas au format
 * attendu
 */
class ValueException extends UserException {
  /** Retourner $value si elle n'est pas false. sinon lancer une exception */
  static final function check_nf($value, $message=null, bool $required=true) {
    if ($value !== false || !$required) return $value;
    if ($message === null) $message = "value must not be false";
    throw new self($message);
  }

  /** Retourner $value si elle n'est pas null. sinon lancer une exception */
  static final function check_nn($value, $message=null, bool $required=true) {
    if ($value !== null || !$required) return $value;
    if ($message === null) $message = "value must not be null";
    throw new self($message);
  }

  /** Retourner $value si elle n'est ni null ni false. sinon lancer une exception */
  static final function check_nz($value, $message=null, bool $required=true) {
    if (($value !== false && $value !== null) || !$required) return $value;
    if ($message === null) $message = "value must not be false nor null";
    throw new ValueException($message);
  }

  static final function check_max_one(array $values, string $kind="value", ?string $prefix=null) {
    if ($prefix) $prefix = "$prefix: ";
    $count = count($values);
    if ($count == 0) return null;
    elseif ($count == 1) return $values[0];
    else throw new self("${prefix}more than one $kind was found");
  }

  static final function check_one(array $values, string $kind="value", ?string $prefix=null) {
    if ($prefix) $prefix = "$prefix: ";
    $count = count($values);
    if ($count == 1) return $values[0];
    if ($count > 1) {
      throw new self("${prefix}more than one $kind was found");
    } else {
      throw new self("${prefix}unable to find any $kind");
    }
  }

  static final function get_value_msg($value): string {
    if (is_object($value)) {
      return "object(".get_class($value).")";
    } elseif (is_array($value)) {
      $values = $value;
      $parts = [];
      foreach ($values as $value) {
        $parts[] = self::get_value_msg($value);
      }
      return "array(".implode(", ", $parts).")";
    } else {
      return var_export($value, true);
    }
  }

  static final function get_invalid_value_msg($value, string $kind="value", ?string $prefix=null): string {
    $msg = self::get_value_msg($value).": invalid $kind";
    if ($prefix) $msg = "$prefix: $msg";
    return $msg;
  }

  static final function invalid_value($value, string $kind="value", ?string $prefix=null): ValueException {
    return new self(self::get_invalid_value_msg($value, $kind, $prefix));
  }

  static final function get_unexpected_value_msg($value, ?array $allowed_values=null, string $kind="value", ?string $prefix=null): string {
    $msg = "unexpected $kind";
    if ($allowed_values !== null) {
      $msg .= ": must be one of ".self::get_value_msg($allowed_values);
    }
    if ($value !== null) $msg = self::get_value_msg($value).": $msg";
    if ($prefix) $msg = "$prefix: $msg";
    return $msg;
  }

  static final function unexpected_value($value, ?array $allowed_values=null, string $kind="value", ?string $prefix=null): ValueException {
    return new self(self::get_unexpected_value_msg($value, $allowed_values, $kind, $prefix));
  }

  static final function get_unexpected_type_msg($expected_type, $value=null, ?string $prefix=null): string {
    $msg = "expected a value ";
    if (is_array($expected_type)) {
      $msg .= "of a type among (".implode(", ", $expected_type).")";
    } else {
      $msg .= "of type $expected_type";
    }
    if ($value !== null) {
      $msg .= ", got ";
      $msg .= self::get_value_msg($value);
    } else {
      $msg .= ", got null";
    }
    if ($prefix) $msg = "$prefix: $msg";
    return $msg;
  }

  static final function unexpected_type($expected_type, $value=null, ?string $prefix=null): ValueException {
    return new self(self::get_unexpected_type_msg($expected_type, $value, $prefix));
  }

  static final function get_unexpected_class_msg($expected_class, $actual_class=null, ?string $prefix=null): string {
    $msg = "expected ";
    if (is_array($expected_class)) {
      $msg .= "one of (".implode(", ", $expected_class).")";
    } else {
      $msg .= $expected_class;
    }
    if ($actual_class !== null) {
      $msg .= ", got ";
      $msg .= self::get_value_msg($actual_class);
    }
    if ($prefix) $msg = "$prefix: $msg";
    return $msg;
  }

  static final function unexpected_class($expected_class, $actual_class=null, ?string $prefix=null): ValueException {
    return new self(self::get_unexpected_class_msg($expected_class, $actual_class, $prefix));
  }

  static final function check_class($class, $expected_class, ?string $prefix=null): void {
    if (is_object($class)) $class = get_class($class);
    $found = false;
    foreach (A::with($expected_class) as $ec) {
      if ($class === $ec || is_subclass_of($class, $ec)) {
        $found = true;
        break;
      }
    }
    if (!$found) {
      throw self::unexpected_class($expected_class, $class, $prefix);
    }
  }
}
