<?php

/*
 * This file is part of the Phuri package.
 *
 * Copyright © 2014 Erin Millard
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eloquent\Phuri\Generic;

use Eloquent\Pathogen\Factory\PathFactoryInterface;
use Eloquent\Phuri\Generic\Parser\GenericUriComponentsInterface;
use Eloquent\Phuri\Normalization\UriNormalizerInterface;
use Eloquent\Phuri\Parameters\Factory\UriParametersFactoryInterface;
use Eloquent\Phuri\Path\Factory\UriPathFactory;
use Eloquent\Phuri\UriInterface;
use Eloquent\Phuri\Validation\Exception\InvalidUriComponentExceptionInterface;

/**
 * An abstract base class for implementing generic URIs.
 */
abstract class AbstractGenericUri implements GenericUriInterface
{
    /**
     * Construct a new generic URI.
     *
     * @param GenericUriComponentsInterface $components The URI components.
     *
     * @throws InvalidUriComponentExceptionInterface If any of the components are invalid.
     */
    public function __construct(GenericUriComponentsInterface $components)
    {
        $this->username = $components->username();
        $this->password = $components->password();
        $this->host = $components->host();
        $this->port = $components->port();
        $this->path = $components->path();
        $this->fragment = $components->fragment();

        if (null === $components->queryParameters()) {
            $this->hasQueryDelimiter = false;
            $this->queryParameters = static::queryParametersFactory()
                ->createEmpty();
        } else {
            $this->hasQueryDelimiter = true;
            $this->queryParameters = static::queryParametersFactory()
                ->createFromEncodedPairs($components->queryParameters());
        }
    }

    // Implementation of GenericUriInterface ===================================

    /**
     * Returns true if this URI has a username.
     *
     * This method will return false for URIs with empty string usernames.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.2.1
     *
     * @return boolean True if this URI has a username.
     */
    public function hasUsername()
    {
        return null !== $this->encodedUsername() &&
            '' !== $this->encodedUsername();
    }

    /**
     * Get the username.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.2.1
     *
     * @return string|null The username, or null if there is no username.
     */
    public function username()
    {
        if (null === $this->encodedUsername()) {
            return null;
        }

        return static::encoder()->decode($this->encodedUsername());
    }

    /**
     * Get the encoded username.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.2.1
     *
     * @return string|null The encoded username, or null if there is no username.
     */
    public function encodedUsername()
    {
        return $this->username;
    }

    /**
     * Returns true if this URI has a password.
     *
     * This method will return false for URIs with empty string passwords.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.2.1
     *
     * @return boolean True if this URI has a password.
     */
    public function hasPassword()
    {
        return null !== $this->encodedPassword() &&
            '' !== $this->encodedPassword();
    }

    /**
     * Get the password.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.2.1
     *
     * @return string|null The password, or null if there is no password.
     */
    public function password()
    {
        if (null === $this->encodedPassword()) {
            return null;
        }

        return static::encoder()->decode($this->encodedPassword());
    }

    /**
     * Get the encoded password.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.2.1
     *
     * @return string|null The encoded password, or null if there is no password.
     */
    public function encodedPassword()
    {
        return $this->password;
    }

    /**
     * Get the encoded host.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.2.2
     *
     * @return string|null The encoded host, or null if there is no host.
     */
    public function encodedHost()
    {
        return $this->host;
    }

    /**
     * Returns true if this URI has a port.
     *
     * This method will return false for URIs with empty string ports.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.2.3
     *
     * @return boolean True if this URI has a port.
     */
    public function hasPort()
    {
        return null !== $this->encodedPort() && '' !== $this->encodedPort();
    }

    /**
     * Get the port.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.2.3
     *
     * @return integer|null The port, or null if there is no port, or the port is an empty string.
     */
    public function port()
    {
        if ($this->hasPort()) {
            return intval($this->encodedPort());
        }

        return null;
    }

    /**
     * Get the encoded port.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.2.3
     *
     * @return string|null The encoded port, or null if there is no port.
     */
    public function encodedPort()
    {
        return $this->port;
    }

    /**
     * Returns true if this URI has a path.
     *
     * This method will return false for URIs with empty string paths.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.3
     *
     * @return boolean True if this URI has a path.
     */
    public function hasPath()
    {
        return '' !== $this->path();
    }

    /**
     * Get the path.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.3
     *
     * @return string The path.
     */
    public function path()
    {
        return static::encoder()->decode($this->encodedPath());
    }

    /**
     * Get the encoded path.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.3
     *
     * @return string The encoded path.
     */
    public function encodedPath()
    {
        return $this->path;
    }

    /**
     * Get the path as a path object.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.3
     *
     * @return UriPathInterface The path.
     */
    public function pathObject()
    {
        return static::pathFactory()->create($this->path());
    }

    /**
     * Returns true if this URI has a query.
     *
     * This method will return false for URIs with empty string queries.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.4
     *
     * @return boolean True if this URI has a query.
     */
    public function hasQuery()
    {
        return !$this->queryParameters()->isEmpty();
    }

    /**
     * Get the query.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.4
     *
     * @return string|null The query, or null if there is no query.
     */
    public function query()
    {
        if ($this->hasQueryDelimiter()) {
            return static::encoder()->decode($this->encodedQuery());
        }

        return null;
    }

    /**
     * Get the encoded query.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.4
     *
     * @return string|null The encoded query, or null if there is no query.
     */
    public function encodedQuery()
    {
        if ($this->hasQueryDelimiter()) {
            return $this->queryParameters()->string();
        }

        return null;
    }

    /**
     * Get the query parameters.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.4
     *
     * @return UriParametersInterface The query parameters.
     */
    public function queryParameters()
    {
        return $this->queryParameters;
    }

    /**
     * Returns true if this URI has a fragment.
     *
     * This method will return false for URIs with empty string fragments.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.5
     *
     * @return boolean True if this URI has a fragment.
     */
    public function hasFragment()
    {
        return null !== $this->encodedFragment() &&
            '' !== $this->encodedFragment();
    }

    /**
     * Get the fragment.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.5
     *
     * @return string|null The fragment, or null if there is no fragment.
     */
    public function fragment()
    {
        if (null === $this->encodedFragment()) {
            return null;
        }

        return static::encoder()->decode($this->encodedFragment());
    }

    /**
     * Get the encoded fragment.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.5
     *
     * @return string|null The encoded fragment, or null if there is no fragment.
     */
    public function encodedFragment()
    {
        return $this->fragment;
    }

    /**
     * Get the fragment parameters.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-3.5
     *
     * @return UriParametersInterface The fragment parameters.
     */
    public function fragmentParameters()
    {
        if (null === $this->encodedFragment()) {
            return static::queryParametersFactory()->createEmpty();
        }

        return static::queryParametersFactory()
            ->createFromString($this->encodedFragment());
    }

    // Implementation of UriInterface ==========================================

    /**
     * Return a normalized version of this URI.
     *
     * @return UriInterface A normalized version of this URI.
     */
    public function normalize()
    {
        return static::normalizer()->normalize($this);
    }

    /**
     * Get a string representation of this URI.
     *
     * @return string A string representation of this URI.
     */
    public function __toString()
    {
        return $this->string();
    }

    // Implementation details ==================================================

    /**
     * Returns true if this URI has a query delimiter.
     *
     * @return boolean True if this URI has a query delimiter.
     */
    public function hasQueryDelimiter()
    {
        return $this->hasQueryDelimiter;
    }

    /**
     * Get the most appropriate factory for this type of URI.
     *
     * @return Factory\GenericUriFactoryInterface The factory.
     */
    protected static function factory()
    {
        return Factory\GenericUriFactory::instance();
    }

    /**
     * Get the most appropriate path factory for this type of URI.
     *
     * @return PathFactoryInterface The factory.
     */
    protected static function pathFactory()
    {
        return UriPathFactory::instance();
    }

    /**
     * Get the most appropriate query parameters factory for this type of URI.
     *
     * @return UriParametersFactoryInterface The factory.
     */
    protected static function queryParametersFactory()
    {
        return Factory\GenericUriQueryParametersFactory::instance();
    }

    /**
     * Get the most appropriate validator for this type of URI.
     *
     * @return Validation\GenericUriValidatorInterface The validator.
     */
    protected static function validator()
    {
        return Validation\GenericUriValidator::instance();
    }

    /**
     * Get the most appropriate encoder for this type of URI.
     *
     * @return Encoding\GenericUriEncoderInterface The encoder.
     */
    protected static function encoder()
    {
        return Encoding\GenericUriEncoder::instance();
    }

    /**
     * Get the most appropriate normalizer for this type of URI.
     *
     * @return UriNormalizerInterface The normalizer.
     */
    protected static function normalizer()
    {
        return Normalization\GenericUriNormalizer::instance();
    }

    private $username;
    private $password;
    private $host;
    private $port;
    private $path;
    private $hasQueryDelimiter;
    private $queryParameters;
    private $fragment;
}
