<?php

/**
 * EXHIBIT A. Common Public Attribution License Version 1.0
 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the “License”);
 * you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://www.oxwall.org/license. The License is based on the Mozilla Public License Version 1.1
 * but Sections 14 and 15 have been added to cover use of software over a computer network and provide for
 * limited attribution for the Original Developer. In addition, Exhibit A has been modified to be consistent
 * with Exhibit B. Software distributed under the License is distributed on an “AS IS” basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language
 * governing rights and limitations under the License. The Original Code is Oxwall software.
 * The Initial Developer of the Original Code is Oxwall Foundation (http://www.oxwall.org/foundation).
 * All portions of the code written by Oxwall Foundation are Copyright (c) 2011. All Rights Reserved.

 * EXHIBIT B. Attribution Information
 * Attribution Copyright Notice: Copyright 2011 Oxwall Foundation. All rights reserved.
 * Attribution Phrase (not exceeding 10 words): Powered by Oxwall community software
 * Attribution URL: http://www.oxwall.org/
 * Graphic Image as provided in the Covered Code.
 * Display of Attribution Information is required in Larger Works which are defined in the CPAL as a work
 * which combines Covered Code or portions thereof with code not governed by the terms of the CPAL.
 */

/**
 * The class is used to create custom routes (URIs).
 * 
 * @author Sardar Madumarov <madumarov@gmail.com>
 * @package ow_core
 * @since 1.0
 */
class OW_Route
{
    const PARAM_OPTION_DEFAULT_VALUE = 'default';
    const PARAM_OPTION_HIDDEN_VAR = 'var';
    const PARAM_OPTION_VALUE_REGEXP = 'regexp';
    const DISPATCH_ATTRS_CTRL = 'controller';
    const DISPATCH_ATTRS_ACTION = 'action';
    const DISPATCH_ATTRS_VARLIST = 'vars';

    /**
     * Route name.
     *
     * @var string
     */
    private $routeName;
    /**
     * Route URI pattern with vars (simple string for static routes). 
     *
     * @var string
     */
    private $routePath;
    /**
     * Decomposed URI parts.
     *
     * @var array
     */
    private $routePathArray;
    /**
     * Flag indicating if route path is static.
     * 
     * @var boolean
     */
    private $isStatic = false;
    /**
     * Result attributes for dispatching process.
     *
     * @var array
     */
    private $dispatchAttrs = array();
    /**
     * Default route params.
     * 
     * @var array
     */
    private $routeParamOptions = array();

    /**
     * @return string
     */
    public function getRouteName()
    {
        return $this->routeName;
    }

    /**
     * @return array
     */
    public function getDispatchAttrs()
    {
        return $this->dispatchAttrs;
    }

    /**
     * @return string
     */
    public function getRoutePath()
    {
        return $this->routePath;
    }

    /**
     * @param string $routeName
     */
    public function setRouteName( $routeName )
    {
        if ( $routeName )
        {
            $this->routeName = trim($routeName);
        }
    }

    /**
     * @param string $routePath
     */
    public function setRoutePath( $routePath )
    {
        if ( $routePath )
        {
            $this->routePath = UTIL_String::removeFirstAndLastSlashes(trim($routePath));
            $this->routePathArray = explode('/', $this->routePath);
        }
    }

    /**
     * @param array $dispatchAttrs
     */
    public function setDispatchAttrs( array $dispatchAttrs )
    {
        if ( !empty($dispatchAttrs['controller']) && !empty($dispatchAttrs['action']) )
        {
            $this->dispatchAttrs = $dispatchAttrs;
        }
    }

    /**
     * @param array $routeParamOptions
     */
    public function setRouteParamOptions( array $routeParamOptions )
    {
        $this->routeParamOptions = $routeParamOptions;
    }

    /**
     * @return boolean
     */
    public function isStatic()
    {
        return $this->isStatic;
    }

    /**
     * Constructor.
     *
     * @throws InvalidArgumentException
     * @param string $routeName
     * @param string $routePath
     * @param string $controller
     * @param string $action
     * @param array $paramOptions
     */
    public function __construct( $routeName, $routePath, $controller, $action, array $paramOptions = array() )
    {
        if ( empty($routeName) || empty($routePath) || empty($controller) || empty($action) )
        {
            throw new InvalidArgumentException('Invalid route params provided!');
        }
        
        $this->routeParamOptions = $paramOptions;
        $this->routeName = trim($routeName);
        $this->setRoutePath($routePath);
        $this->dispatchAttrs[self::DISPATCH_ATTRS_CTRL] = trim($controller);
        $this->dispatchAttrs[self::DISPATCH_ATTRS_ACTION] = trim($action);

        // if there are no dynamic parts in route path -> set flag and return
        if ( !mb_strstr($this->routePath, ':') )
        {
            $this->isStatic = true;
            return;
        }
    }

    /**
     * Adds options to route params.
     *
     * @param string $paramName
     * @param string $option
     * @param mixed $optionValue
     */
    public function addParamOption( $paramName, $option, $optionValue )
    {
        if ( empty($this->routeParamOptions[$paramName]) )
        {
            $this->routeParamOptions[$paramName] = array();
        }

        $this->routeParamOptions[$paramName][$option] = $optionValue;
    }

    /**
     * Generates route path uri for provided set of params.
     *
     * @throws InvalidArgumentException
     * @param array $params
     * @return string
     */
    public function generateUri( $params = array() )
    {
        // if route path is static we can return it without params processing 
        if ( $this->isStatic )
        {
            return $this->routePath;
        }

        $generatedUri = '';

        foreach ( $this->routePathArray as $value )
        {
            if ( mb_substr($value, 0, 1) !== ':' )
            {
                $generatedUri .= $value . '/';
            }
            else
            {
                $varName = mb_substr($value, 1);

                if ( !isset($params[$varName]) && !isset($this->routeParamOptions[$varName][self::PARAM_OPTION_DEFAULT_VALUE]) )
                {
                    trigger_error('Empty var for route provided. VarName - `' . $varName . '`!', E_USER_WARNING);
                    return 'INVALID_URI';
                }

                if ( isset($this->routeParamOptions[$varName][self::PARAM_OPTION_VALUE_REGEXP]) && !preg_match($this->routeParamOptions[$varName][self::PARAM_OPTION_VALUE_REGEXP], $params[$varName]) )
                {
                    trigger_error('Invalid var for route provided. VarName - `' . $varName . '`!', E_USER_WARNING);
                    return 'INVALID_URI';
                }

                $generatedUri .= urlencode(( isset($params[$varName]) ? $params[$varName] : $this->routeParamOptions[$varName][self::PARAM_OPTION_DEFAULT_VALUE])) . '/';
            }
        }

        return mb_substr($generatedUri, 0, -1);
    }

    /**
     * Tries to match route path and provided URI.
     *
     * @param string $uri
     * @return boolean
     */
    public function match( $uri )
    {
        $uri = UTIL_String::removeFirstAndLastSlashes(trim($uri));

        $this->dispatchAttrs[self::DISPATCH_ATTRS_VARLIST] = array();

        foreach ( $this->routeParamOptions as $paramName => $paramArray )
        {
            if ( isset($paramArray[self::PARAM_OPTION_HIDDEN_VAR]) )
            {
                $this->dispatchAttrs[self::DISPATCH_ATTRS_VARLIST][$paramName] = $paramArray[self::PARAM_OPTION_HIDDEN_VAR];
            }
        }

        if ( $this->isStatic )
        {
            $pathArray = explode('/', $this->routePath);
            $pathArray = array_map('urlencode', $pathArray);
            $tempPath = implode('/', $pathArray);

            return ( mb_strtoupper($uri) === mb_strtoupper($tempPath));
        }

        $uriArray = explode('/', $uri);

        if ( sizeof($uriArray) !== sizeof($this->routePathArray) )
        {
            return false;
        }

        foreach ( $this->routePathArray as $key => $value )
        {
            if ( !mb_strstr($value, ':') )
            {
                if ( mb_strtoupper(urlencode($value)) !== mb_strtoupper($uriArray[$key]) )
                {
                    return false;
                }
            }
            else
            {
                if ( isset($this->routeParamOptions[mb_substr($value, 1)][self::PARAM_OPTION_VALUE_REGEXP]) && !preg_match($this->routeParamOptions[mb_substr($value, 1)][self::PARAM_OPTION_VALUE_REGEXP], $uriArray[$key]) )
                {
                    return false;
                }

                $this->dispatchAttrs[self::DISPATCH_ATTRS_VARLIST][mb_substr($value, 1)] = $uriArray[$key];
            }
        }

        return true;
    }
}
