public function find($query = [], $fields = [], $findOne = false)
    {
        $postFind = false;

        if ($query instanceof Closure) {
            $callback = $query;

            $query = new Query\Find();

            // set the fields to select
            $query->fields($fields);
            $query->one($findOne);
            $callback($query);
        }

        if ($query instanceof Query\Find) {
            $findOne = $query->getFindOne();
            $fields = $query->getFields();
            $postFind = $query->getPostFindActions();
            $query = $query->getWhere();
        }

        if (! is_array($query) || ! is_array($fields)) {
            throw new \InvalidArgumentException('Find params $query and $fields must be arrays.');
        }

        // Prepare the find arguments
        $arguments = [];
        $arguments[] = $query;
        $arguments[] = $fields;

        // Wrap the find function so it is callable
        $function = [
            $this->getCollection(),
            ($findOne && ! $postFind) ? 'findOne' : 'find',
        ];

        $result = call_user_func_array($function, $arguments);

        // Trigger any post find actions.
        if ($postFind) {
            foreach ($postFind as $arguments) {
                $method = array_shift($arguments);

                $result = call_user_func_array([$result, $method], $arguments);
            }
        }

        // When there were post-find actions, we used normal find
        // so we can sort, skip, and limit. Now we'll need to retrieve
        // the first result.
        if ($findOne && $postFind) {
            $result = $result->getNext();
        }

        return $findOne ? $result : new Cursor($result, $this);
    }