/*!
 * Hash utility v0.4
 * 2010, Kurilov Stas
 *
 * Вспомогательный объект для работы с window.location.hash (частью URL, следующей за символом решетки #)
 * setParam - устанавливает параметр
 * getParam - получает параметр
 * hasParam - проверяет, есть ли параметр в хеше
 * getAllParams - получает все параметры и их значения
 * change - вешает обработчик на событие изменения хэша
 * getHash - получает строку хэша
 * setHash - устанавливает строку хэша
 * setOption - устанавливает параметры
 * getLocation - получает часть URL
 * setLocation - устанавливает часть URL
 *
 * TODO: Конвертация объекта
 * TODO: Конвертация массива
 * TODO: JS-injection
 */
var hash = (function() 
{
    var that = 
    {
        /**
         * Настройки по умолчанию
         * Могут быть перекрыты методом setOption
         */
        options: {
            window: window,
            convert_value: true,
            save_history: false,
            check_hash_delay: 100,
            params_separator: '&',
            pair_separator: '='
        },


        /**
         * Устанавливает или переопределяет опции
         * @param {Object|String} config настройки Object{опция: значение}
         * @param {String} val значение опции
         */
        setOption: function(config, val)
        {
            if (typeof config === 'object')
            {
                for (var param in config)
                {
                    if (param in that.options)
                        that.options[param] = config[param];
                }
            }
            else if (typeof config === 'string' && val)
            {
                if (config in that.options)
                    that.options[config] = val;
            }
        },


        /**
         * Получает значение опции
         * @param {String} option
         * @return {String}
         */
        getOption: function(option) {
            return (option in that.options) ? that.options[option] : undefined;
        },


        /**
         * Парсит строку "param=value"
         * @param {String} param_string строка формата "param=value"
         * @return {Object} {param: имя_параметра, value: значение_параметра} 
         */
        parseParamString: function(param_string) {
            var pair = param_string.split(that.options.pair_separator);
            return {
                'param': pair[0],
                'value': pair[1] ? pair[1] : ''
            }
        },


        /**
         * Проверяет не был ли изменен хэш. Используется в браузерах где нет нативной поддержки onhashchange.
         * @param {Function} callback функция, которая будет вызвана если хэш был изменен
         * @return {Boolean}
         */
        checkNewHash: function(callback) {
            if (arguments.callee.oldhash === undefined)
                arguments.callee.oldhash = that.options.window.location.hash;

            var loc = that.options.window.location.hash;
            if (loc && loc != arguments.callee.oldhash)
            {
                arguments.callee.oldhash = loc;
                callback(that.getHash());
            }
            return false;
        },


        /**
         * Получает все параметры и их значения
         * @param {String} params_string строка параметров. Если не передана, то в качестве строки используется текущий якорь  
         * @return {Object}
         */
    	getAllParams: function(params_string) {
            var params = params_string || that.getHash();
            var params_hash = {};
            var has_param_pair = (params && params.search(that.options.pair_separator) != -1);

            if (has_param_pair)
            {
                var params = params.split(that.options.params_separator);
                var params_count = params.length; 
                for (var i = 0; i < params_count; i++)
                {
                    var pair = that.parseParamString(params[i]);
                    if (that.options.convert_value)
                        pair.value = that.convertValue(pair.value);
    
                    params_hash[pair.param] = pair.value;
                }
                return params_hash;
            }
            else
                return false;
        },


        /**
         * Конвертирует значение параметра
         * @param {String} value значение параметра
         */
        convertValue: function(value, mode) {
            if (typeof value === 'boolean')
                return value;

            var mode = mode || 'get';
            var converted_value = Number(value); // попытка преобразовать значение в число

            // значение параметра - не число
            if (isNaN(converted_value))
            {
                switch (typeof value)
                {
                    case 'string':
                    default:
                        // получаемое значение из хэша всегда строковое
                        // этот код пытается определить какого типа значение и преобразовать его.
                        // Например: из строки '[1,2,3]' получить массив 1,2,3
                        var first_char = value.charAt(0),
                            last_char = value.charAt(value.length-1);

                        // array
                        if (first_char == '[' && last_char == ']')
                        {
                            var converted_value = [];
                            var array_pieces = value.substring(1, value.length-1).split(',');
                            for (var i = 0, len = array_pieces.length; i < len; i++)
                            {
                                converted_value.push(array_pieces[i]);
                            }
                            return converted_value;
                        }
                        // object
                        else if (first_char == '{' && last_char == '}')
                        {
                             return (value).toString();
                        }
                        // string
                        else
                        {
                            if (value === 'true' || value === 'false')
                                return Boolean(value);

                            return value;
                        }
                    break;

                    case 'object':
                        if (value instanceof Array && typeof o.splice === 'function')
                            return '[' + value.toString() + ']';
                            /*
                        else if (value instanceof)
                            return 
                            */
                        else
                            return value.toString();
                    break;
                }
            }
            return converted_value;
        },


        /**
         * Получает текущий URL, без якоря
         * @param {String} window_name
         */
        getLocation: function(url_part, window_object) {
            var url_part = url_part || 'href';
            var piece; // свойство window.location 
            var _window = window_object || that.options.window;
    
            switch (url_part)
            {
                case 'protocol':
                case 'port':
                    piece = _window.location[url_part];
                    break;
    
                case 'host':
                case 'server':
                case 'domain':
                    piece = _window.location.hostname;
                    break;
    
                case 'path':
                    piece = _window.location.pathname;
                    break;
    
                case 'query':
                case 'query_string':
                    piece = _window.location.search.substring(1);
                    break;
    
                case 'hash':
                case 'anchor':
                    piece = _window.location.hash.substring(1);
                    break;
    
                default:
                    piece = _window.location.href;
                    break;
            }
            return piece;
        },


        /**
         * Устанавливает новый URL или его часть
         * @param {String} newloc URL, по которому надо перейти или его часть
         */
        setLocation: function(loc, window_object) {
            var newpath = '';
            var new_params = {};
            var _window = window_object || that.options.window;
            var _location = {
                protocol: _window.location.protocol + '//',
                hostname: _window.location.hostname,
                pathname: _window.location.pathname,
                search: _window.location.search,
                hash: _window.location.hash
            };

            var piece; // название свойства объекта location
            var val; // значения свойства location

            // установка переданного URL
            if (typeof loc == 'string')
            {
                new_params['href'] = loc;
            }
            // замена переданных частей URL
            else if (typeof loc == 'object')
            {
                for (var key in loc)
                {
                    switch (key)
                    {
                        case 'protocol':
                            piece = 'protocol';
                            val = loc[key];
                            break;
    
                        case 'host':
                        case 'server':
                        case 'domain':
                            piece = 'hostname';
                            val = loc[key];
                            break;
    
                        case 'path':
                            piece = 'pathname';
                            val = (!!loc[key] == false) ? '' : '/'+loc[key];
                            break;
    
                        case 'query':
                        case 'query_string':
                            piece = 'search';
                            val = (!!loc[key] == false) ? '' : '?'+loc[key];
                            break;
    
                        case 'hash':
                        case 'anchor':
                            piece = 'hash';
                            val = (!!loc[key] == false) ? '' : '#'+loc[key];
                            break;
    
                        default:
                            piece = 'href';
                            val = loc;
                            break;
                    }
                    new_params[piece] = val;
                }
            }

            if ('href' in new_params)
            {
                newpath = new_params.href;
            }
            else
            {
                for (var piece in _location)
                {
                    if (piece in new_params)
                        newpath += new_params[piece];
                    else
                        newpath += _location[piece];
                }
            }

            if (that.options.track_history)
                _window.location.assign(newpath);
            else 
                _window.location.replace(newpath);

            return newpath;
        },


        /**
         * Получает строку хэша
         * @return {String}
         */
        getHash: function(window_object) {
            var _window = window_object || that.options.window;
            var hash = _window.location.hash;
            if (!!(hash.charAt(1)) === true)
                return hash.substring(1);

            return '';
        },


        /**
         * Устанавливает хэш
         * @param {String} newhash хэш
         * @return {String}
         */
        setHash: function(newhash) {
            var newhash = newhash || '';
            that.setLocation({hash: newhash});
        },


        /**
         * Определяет есть ли параметр в строке хеша
         * @param {String} param имя параметра
         * @return {Boolean}
        */
        hasParam: function(param) {
            var current_hash = that.getHash();

            if (current_hash && current_hash.indexOf(param + that.options.pair_separator) > -1)
                return true;

            return false;
        },


        /**
         * Получает значение параметра
         * Если опция convert_value установлена в true возвращается значение в зависимости от типа (Array, String или Integer)
         * @param {String} param имя параметра
         * @param {} default_value возвращаемое значение по умолчанию.
         *        Если параметр не найден, либо строка адреса неверно сформирована 
         *        будет возвращено это значение  
         */
        getParam: function(param, default_value) {
            var params = that.getAllParams();

            if (typeof arguments[0] === 'undefined')
                return params;

            var pair; // param=value
            var default_value = default_value || false;

            if (params)
            {
                for (var param_name in params)
                {
                    if (param_name == param)
                        return params[param_name];
                }
                return default_value; // параметр не найден
            }
            return false; // строка параметров пуста или неверного формата
        },


        /**
         * Устанавливает параметр param в значение value
         * Если параметр существует он перезаписывается.
         * Если параметр не установлен, он добавляется в конец строки списка параметров.
         * @param {String} param имя параметра
         * @param {String} value значение параметра. Если value не передан, то используется значение param
         */
        setParam: function(param, value) {
            var newval;
            var newhash = '';
            var value = value || param; // значение по умолчанию
            var params = that.getAllParams() || {};

            if (typeof param === 'object')
            {
                for (var key in param)
                    params[key] = param[key];
            }
            else if (typeof param === 'string')
            {
                params[param] = value;                
            }

            for (var param_name in params)
            {
                newval = (that.options.convert_value) 
                            ? that.convertValue(params[param_name]) 
                            : params[param_name];

                newhash += param_name + that.options.pair_separator + newval + that.options.params_separator;
            }
            newhash = newhash.substr(0, newhash.length-1);

            that.setHash(newhash);
            return true;
        },


        /**
         * Навешивает обработчик на событие изменения хэша
         * @param {Function} callback
        */
        change: function(callback) 
        {
            var _window = that.options.window;

            // если браузер поддерживает событие hashchange 
            if ('onhashchange' in _window) 
            {
                if (_window.addEventListener) 
                {
                    _window.addEventListener('hashchange', function() 
                    {
                        callback(that.getHash());
                    }, false);
                }
            }
            else
            {
                setInterval(function() {
                    that.checkNewHash(callback)
                }, that.options.check_hash_delay);
            }
        },


        clear: function() {
            that.options.window.location.hash = '';
        }
    };


    return {
        init: that.setOption,
        setOption: that.setOption,
        getOption: that.getOption,

        convertValue: that.convertValue,
        getLocation: that.getLocation, 
        setLocation: that.setLocation,
        getHash: that.getHash,
        setHash: that.setHash,

        hasParam: that.hasParam,
        getParam: that.getParam,
        setParam: that.setParam,
        getAllParams: that.getAllParams,
        change: that.change,
        clear: that.clear
    }

})();
