namespace MyApp\Http\Middlewares;

use Phalcon\Mvc\Micro;
use Phalcon\Events\Event;
use Phalcon\Mvc\User\Plugin;
use Phalcon\Mvc\Micro\MiddlewareInterface;

/**
 * MyApp\Http\Middlewares\CorsMiddleware
 *
 * Provides a base CORS policy.
 *
 * @package MyApp\Http\Middlewares
 */
class CorsMiddleware extends Plugin implements MiddlewareInterface
{
    /**
     * The allowed origins.
     * @var array
     */
    private $allowedOrigins = ['*'];

    /**
     * The allowed methods.
     * @var array
     */
    private $allowedMethods = [
        'GET',
        'POST',
        'PUT',
        'DELETE',
        'HEAD',
        'OPTIONS',
        'PATCH',
        'PURGE',
        'CONNECT',
    ];

    /**
     * The allowed headers.
     * @var array
     */
    private $allowedHeaders = [
        'Origin',
        'Authorization',
        'Content-Type',
        'Content-Range',
        'Content-Disposition',
        'X-Requested-With',
    ];

    public function __construct(
        array $allowedOrigins = null,
        array $allowedMethods = null,
        array $allowedHeaders = null
    ) {
        if ($allowedOrigins !== null) {
            $this->allowedOrigins = $allowedOrigins;
        }

        if ($allowedMethods !== null) {
            $this->allowedMethods = $allowedMethods;
        }

        if ($allowedHeaders !== null) {
            $this->allowedHeaders = $allowedHeaders;
        }
    }

    /**
     * Before anything happens.
     *
     * @param   Event $event
     * @param   Micro $application
     * @returns bool
     */
    public function beforeHandleRoute(Event $event, Micro $application) : bool
    {
        if (!count($this->allowedOrigins) || !$originValue = $this->getOriginValue()) {
            return true;
        }

        $this->response->setHeader('Access-Control-Allow-Origin', $originValue);
        $this->response->setHeader('Access-Control-Max-Age', '86400');

        // Note: wildcard requests are not supported when a request needs credentials.
        // So we set Access-Control-Allow-Credentials header
        $this->response->setHeader('Access-Control-Allow-Credentials', 'true');

        if (count($this->allowedMethods)) {
            $this->response->setHeader('Access-Control-Allow-Methods', implode(',', $this->allowedMethods));
        }

        if (count($this->allowedHeaders)) {
            $this->response->setHeader('Access-Control-Allow-Headers', implode(',', $this->allowedHeaders));
        }

        return true;
    }

    /**
     * {@inheritdoc}
     *
     * @param  Micro $application
     * @return bool
     */
    public function call(Micro $application) : bool
    {
        return true;
    }

    /**
     * Gets ORIGIN value.
     *
     * @return string|null
     */
    protected function getOriginValue()
    {
        $originValue = null;

        if (in_array('*', $this->allowedOrigins)) {
            return '*';
        }

        $origin = $this->request->getHeader('Origin');
        $originDomain = $origin ? parse_url($origin, PHP_URL_HOST) : null;

        if (!$originDomain) {
            return null;
        }

        if ($this->isAllowedOrigin($originDomain)) {
            return $origin;
        }

        return null;
    }

    /**
     * Is the allowed passed domain?
     *
     * @param  string $originDomain
     * @return bool
     */
    protected function isAllowedOrigin($originDomain) : bool
    {
        if (empty($originDomain) || !is_string($originDomain)) {
            return false;
        }

        foreach ($this->allowedOrigins as $allowedOrigin) {
            // First try exact domain
            if ($originDomain == $allowedOrigin) {
                return true;
            }

            // Parse wildcards
            $pattern = '/^' . str_replace('\*', '(.+)', preg_quote($allowedOrigin, '/')) . '$/i';
            if (preg_match($pattern, $originDomain) == 1) {
                return true;
            }
        }

        return false;
    }
}