public function findAndModify(array $query, array $update = null, array $fields = null, array $options = [])
    {
        $query = TypeConverter::fromLegacy($query);
        try {
            if (isset($options['remove'])) {
                unset($options['remove']);
                $document = $this->collection->findOneAndDelete($query, $options);
            } else {
                $update = is_array($update) ? $update : [];
                if (isset($options['update']) && is_array($options['update'])) {
                    $update = $options['update'];
                    unset($options['update']);
                }

                $update = TypeConverter::fromLegacy($update);

                if (isset($options['new'])) {
                    $options['returnDocument'] = \MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER;
                    unset($options['new']);
                }

                $options['projection'] = TypeConverter::convertProjection($fields);

                if (! \MongoDB\is_first_key_operator($update)) {
                    $document = $this->collection->findOneAndReplace($query, $update, $options);
                } else {
                    $document = $this->collection->findOneAndUpdate($query, $update, $options);
                }
            }
        } catch (\MongoDB\Driver\Exception\ConnectionException $e) {
            throw new MongoResultException($e->getMessage(), $e->getCode(), $e);
        } catch (\MongoDB\Driver\Exception\Exception $e) {
            throw ExceptionConverter::toLegacy($e, 'MongoResultException');
        }

        if ($document) {
            $document = TypeConverter::toLegacy($document);
        }

        return $document;
    }