protected function createToken($joiner, array $parameters, &$tokens, callable $wrapper)
    {
        list($identifier, $valueA, $valueB, $valueC) = $parameters + array_fill(0, 5, null);

        if (empty($identifier)) {
            //Nothing to do
            return;
        }

        //Where conditions specified in array form
        if (is_array($identifier)) {
            if (count($identifier) == 1) {
                $this->arrayWhere(
                    $joiner == 'AND' ? Compiler::TOKEN_AND : Compiler::TOKEN_OR,
                    $identifier,
                    $tokens,
                    $wrapper
                );

                return;
            }

            $tokens[] = [$joiner, '('];
            $this->arrayWhere(Compiler::TOKEN_AND, $identifier, $tokens, $wrapper);
            $tokens[] = ['', ')'];

            return;
        }

        if ($identifier instanceof \Closure) {
            $tokens[] = [$joiner, '('];
            call_user_func($identifier, $this, $joiner, $wrapper);
            $tokens[] = ['', ')'];

            return;
        }

        if ($identifier instanceof BuilderInterface) {
            //Will copy every parameter from QueryBuilder
            $wrapper($identifier);
        }

        switch (count($parameters)) {
            case 1:
                //AND|OR [identifier: sub-query]
                $tokens[] = [$joiner, $identifier];
                break;
            case 2:
                //AND|OR [identifier] = [valueA]
                $tokens[] = [$joiner, [$identifier, '=', $wrapper($valueA)]];
                break;
            case 3:
                if (is_string($valueA)) {
                    $valueA = strtoupper($valueA);
                    if (in_array($valueA, ['BETWEEN', 'NOT BETWEEN'])) {
                        throw new BuilderException('Between statements expects exactly 2 values');
                    }
                }

                //AND|OR [identifier] [valueA: OPERATION] [valueA]
                $tokens[] = [$joiner, [$identifier, strval($valueA), $wrapper($valueB)]];
                break;
            case 4:
                //BETWEEN or NOT BETWEEN
                if (!is_string($valueA) || !in_array(strtoupper($valueA), ['BETWEEN', 'NOT BETWEEN'])) {
                    throw new BuilderException(
                        'Only "BETWEEN" or "NOT BETWEEN" can define second comparision value'
                    );
                }

                //AND|OR [identifier] [valueA: BETWEEN|NOT BETWEEN] [valueB] [valueC]
                $tokens[] = [$joiner, [$identifier, strtoupper($valueA), $wrapper($valueB), $wrapper($valueC)]];
        }
    }