public function generateUri($referenceScheme, $referenceHost, $referencePort, $forceCanonical)
    {
        $url    = '';
        $scheme = $referenceScheme;
        $port   = $this->port;

        if ($forceCanonical || $this->scheme !== null && $referenceScheme !== $this->scheme) {
            $url .= ($scheme = $this->scheme ?: $referenceScheme) . ':';
            $forceCanonical = true;
        }

        if (null === $this->port) {
            if ($scheme === $referenceScheme && (null === $this->host || $this->host === $referenceHost)) {
                $port = $referencePort;
            } else {
                $port = 'http' === $scheme ? 80 : 443;
            }
        }

        if ($forceCanonical || $this->host !== null && $referenceHost !== $this->host || $port !== $referencePort) {
            $url .= '//' . ($this->host ?: $referenceHost);

            if ('http' === $scheme && 80 !== $port || 'https' === $scheme && 443 !== $port) {
                $url .= ':' . $port;
            }
        }

        $url .= strtr(rawurlencode($this->path), static::$allowedPathChars);

        if ($this->query !== null) {
            $url .= '?' . http_build_query($this->query, '', '&');
        }

        if ($this->fragment !== null) {
            $url .= '#' . $this->fragment;
        }

        return $url;
    }