<?php

namespace YaySMTP\Aws3\Aws\Api;

use Aws;

/**
 * Validates a schema against a hash of input.
 */
class Validator {
  private $path = [];
  private $errors = [];
  private $constraints = [];
  private static $defaultConstraints = ['required' => true, 'min' => true, 'max' => false, 'pattern' => false];
  /**
   * @param array $constraints Associative array of constraints to enforce.
   *                           Accepts the following keys: "required", "min",
   *                           "max", and "pattern". If a key is not
   *                           provided, the constraint will assume false.
   */
  public function __construct(array $constraints = null) {
    static $assumedFalseValues = ['required' => false, 'min' => false, 'max' => false, 'pattern' => false];
    $this->constraints = empty($constraints) ? self::$defaultConstraints : $constraints + $assumedFalseValues;
  }
  /**
   * Validates the given input against the schema.
   *
   * @param string $name  Operation name
   * @param Shape  $shape Shape to validate
   * @param array  $input Input to validate
   *
   * @throws \InvalidArgumentException if the input is invalid.
   */
  public function validate($name, \YaySMTP\Aws3\Aws\Api\Shape $shape, array $input) {
    $this->dispatch($shape, $input);
    if ($this->errors) {
      $message = sprintf("Found %d error%s while validating the input provided for the " . "%s operation:\n%s", count($this->errors), count($this->errors) > 1 ? 's' : '', $name, implode("\n", $this->errors));
      $this->errors = [];
      throw new \InvalidArgumentException($message);
    }
  }
  private function dispatch(\YaySMTP\Aws3\Aws\Api\Shape $shape, $value) {
    static $methods = ['structure' => 'check_structure', 'list' => 'check_list', 'map' => 'check_map', 'blob' => 'check_blob', 'boolean' => 'check_boolean', 'integer' => 'check_numeric', 'float' => 'check_numeric', 'long' => 'check_numeric', 'string' => 'check_string', 'byte' => 'check_string', 'char' => 'check_string'];
    $type = $shape->getType();
    if (isset($methods[$type])) {
      $this->{$methods[$type]}($shape, $value);
    }
  }
  private function check_structure(\YaySMTP\Aws3\Aws\Api\StructureShape $shape, $value) {
    if (!$this->checkAssociativeArray($value)) {
      return;
    }
    if ($this->constraints['required'] && $shape['required']) {
      foreach ($shape['required'] as $req) {
        if (!isset($value[$req])) {
          $this->path[] = $req;
          $this->addError('is missing and is a required parameter');
          array_pop($this->path);
        }
      }
    }
    foreach ($value as $name => $v) {
      if ($shape->hasMember($name)) {
        $this->path[] = $name;
        $this->dispatch($shape->getMember($name), isset($value[$name]) ? $value[$name] : null);
        array_pop($this->path);
      }
    }
  }
  private function check_list(\YaySMTP\Aws3\Aws\Api\ListShape $shape, $value) {
    if (!is_array($value)) {
      $this->addError('must be an array. Found ' . \YaySMTP\Aws3\Aws\describe_type($value));
      return;
    }
    $this->validateRange($shape, count($value), "list element count");
    $items = $shape->getMember();
    foreach ($value as $index => $v) {
      $this->path[] = $index;
      $this->dispatch($items, $v);
      array_pop($this->path);
    }
  }
  private function check_map(\YaySMTP\Aws3\Aws\Api\MapShape $shape, $value) {
    if (!$this->checkAssociativeArray($value)) {
      return;
    }
    $values = $shape->getValue();
    foreach ($value as $key => $v) {
      $this->path[] = $key;
      $this->dispatch($values, $v);
      array_pop($this->path);
    }
  }
  private function check_blob(\YaySMTP\Aws3\Aws\Api\Shape $shape, $value) {
    static $valid = ['string' => true, 'integer' => true, 'double' => true, 'resource' => true];
    $type = gettype($value);
    if (!isset($valid[$type])) {
      if ($type != 'object' || !method_exists($value, '__toString')) {
        $this->addError('must be an fopen resource, a ' . 'YaySMTP\\Aws3\\GuzzleHttp\\Stream\\StreamInterface object, or something ' . 'that can be cast to a string. Found ' . \YaySMTP\Aws3\Aws\describe_type($value));
      }
    }
  }
  private function check_numeric(\YaySMTP\Aws3\Aws\Api\Shape $shape, $value) {
    if (!is_numeric($value)) {
      $this->addError('must be numeric. Found ' . \YaySMTP\Aws3\Aws\describe_type($value));
      return;
    }
    $this->validateRange($shape, $value, "numeric value");
  }
  private function check_boolean(\YaySMTP\Aws3\Aws\Api\Shape $shape, $value) {
    if (!is_bool($value)) {
      $this->addError('must be a boolean. Found ' . \YaySMTP\Aws3\Aws\describe_type($value));
    }
  }
  private function check_string(\YaySMTP\Aws3\Aws\Api\Shape $shape, $value) {
    if ($shape['jsonvalue']) {
      if (!self::canJsonEncode($value)) {
        $this->addError('must be a value encodable with \'json_encode\'.' . ' Found ' . \YaySMTP\Aws3\Aws\describe_type($value));
      }
      return;
    }
    if (!$this->checkCanString($value)) {
      $this->addError('must be a string or an object that implements ' . '__toString(). Found ' . \YaySMTP\Aws3\Aws\describe_type($value));
      return;
    }
    $this->validateRange($shape, strlen($value), "string length");
    if ($this->constraints['pattern']) {
      $pattern = $shape['pattern'];
      if ($pattern && !preg_match("/{$pattern}/", $value)) {
        $this->addError("Pattern /{$pattern}/ failed to match '{$value}'");
      }
    }
  }
  private function validateRange(\YaySMTP\Aws3\Aws\Api\Shape $shape, $length, $descriptor) {
    if ($this->constraints['min']) {
      $min = $shape['min'];
      if ($min && $length < $min) {
        $this->addError("expected {$descriptor} to be >= {$min}, but " . "found {$descriptor} of {$length}");
      }
    }
    if ($this->constraints['max']) {
      $max = $shape['max'];
      if ($max && $length > $max) {
        $this->addError("expected {$descriptor} to be <= {$max}, but " . "found {$descriptor} of {$length}");
      }
    }
  }
  private function checkCanString($value) {
    static $valid = ['string' => true, 'integer' => true, 'double' => true, 'NULL' => true];
    $type = gettype($value);
    return isset($valid[$type]) || $type == 'object' && method_exists($value, '__toString');
  }
  private function checkAssociativeArray($value) {
    if (!is_array($value) || isset($value[0])) {
      $this->addError('must be an associative array. Found ' . \YaySMTP\Aws3\Aws\describe_type($value));
      return false;
    }
    return true;
  }
  private function addError($message) {
    $this->errors[] = implode('', array_map(function ($s) {
      return "[{$s}]";
    }, $this->path)) . ' ' . $message;
  }
  private function canJsonEncode($data) {
    return !is_resource($data);
  }
}
