<?php
 namespace MongoDB;
 use Iterator;
 use MongoDB\Driver\CursorId;
 use MongoDB\Driver\Exception\ConnectionException;
 use MongoDB\Driver\Exception\RuntimeException;
 use MongoDB\Driver\Exception\ServerException;
 use MongoDB\Exception\ResumeTokenException;
 use MongoDB\Model\ChangeStreamIterator;
 use function call_user_func;
 use function in_array;
 class ChangeStream implements Iterator {
 const CURSOR_NOT_FOUND = 43;
 private static $nonResumableErrorCodes = [ 136, 237, 11601, ];
 private $resumeCallable;
 private $iterator;
 private $key = 0;
 private $hasAdvanced = false;
 public function __construct(ChangeStreamIterator $iterator, callable $resumeCallable) {
 $this->iterator = $iterator;
 $this->resumeCallable = $resumeCallable;
 }
 public function current() {
 return $this->iterator->current();
 }
 public function getCursorId() {
 return $this->iterator->getInnerIterator()->getId();
 }
 public function getResumeToken() {
 return $this->iterator->getResumeToken();
 }
 public function key() {
 if ($this->valid()) {
 return $this->key;
 }
 return null;
 }
 public function next() {
 try {
 $this->iterator->next();
 $this->onIteration($this->hasAdvanced);
 }
 catch (RuntimeException $e) {
 $this->resumeOrThrow($e);
 }
 }
 public function rewind() {
 try {
 $this->iterator->rewind();
 $this->onIteration(false);
 }
 catch (RuntimeException $e) {
 $this->resumeOrThrow($e);
 }
 }
 public function valid() {
 return $this->iterator->valid();
 }
 private function isResumableError(RuntimeException $exception) {
 if ($exception instanceof ConnectionException) {
 return true;
 }
 if (! $exception instanceof ServerException) {
 return false;
 }
 if ($exception->hasErrorLabel('NonResumableChangeStreamError')) {
 return false;
 }
 if (in_array($exception->getCode(), self::$nonResumableErrorCodes)) {
 return false;
 }
 return true;
 }
 private function onIteration($incrementKey) {
 if ((string) $this->getCursorId() === '0') {
 $this->resumeCallable = null;
 }
 if (! $this->valid()) {
 return;
 }
 if ($incrementKey) {
 $this->key++;
 }
 $this->hasAdvanced = true;
 }
 private function resume() {
 $this->iterator = call_user_func($this->resumeCallable, $this->getResumeToken(), $this->hasAdvanced);
 $this->iterator->rewind();
 $this->onIteration($this->hasAdvanced);
 }
 private function resumeOrThrow(RuntimeException $exception) {
 if ($this->isResumableError($exception)) {
 $this->resume();
 return;
 }
 throw $exception;
 }
 }
 