(function (window, ko) {
    
   'use strict';
   
    var knockouch = function (library, options) {
        knockouch.options = options || {};
        knockouch.setTouchLib(library);
    };

    knockouch.touchLib = null;
    knockouch.touchLibs = {};
    knockouch.touchEvents = ['tap', 'doubletap', 'hold', 'rotate',
                       'drag', 'dragleft', 'dragright', 'dragup',
                       'dragdown', 'transform', 'transformstart',
                       'transformend', 'swipe', 'swipeleft', 'swiperight',
                       'swipeup', 'swipedown', 'pinch', 'pinchin', 'pinchout'];

    knockouch.makeTouchHandlerShortcut = function (touchEventName) {
        ko.bindingHandlers[touchEventName] = {
            init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
                var handler = valueAccessor();
                var allBindings = allBindingsAccessor();
                
                var wrappedHandler = function(e) {
                    handler(viewModel, e);
                };
                
                knockouch.touchLib.wrapper(element, touchEventName, wrappedHandler, allBindings);
            }
        };
    };

    knockouch.searchTouchLib = function () {
        var touchlib;
    
        for (touchlib in knockouch.touchLibs) {
            if (knockouch.touchLibs.hasOwnProperty(touchlib) && knockouch.touchLibs[touchlib].isLoaded() ) {
                knockouch.touchLib = knockouch.touchLibs[touchlib];
                return;
            }
        }
    
        //TODO: there`s a case where lib was setup before and iteration over default touchLibs will not throw exception
        throw new ReferenceError('could not find any touch library');
    };
    
    //TODO: we need to put it into documentation on how to add another touch library
    knockouch.setTouchLib = function (library) {
        if (knockouch.touchLibs[library].isLoaded()) {
            knockouch.touchLib = knockouch.touchLibs[library];
        }
        else {
            throw new Error('failed to select ' + library +
            ' check there\'s no typos in ' + library +
            ' To make sure it\'s supported refer to our documentation.');
        }
    };

    knockouch.unifyEventName = function (eventName, eventSubstitutes) {
        if (eventSubstitutes[eventName] !== undefined) {
            return eventSubstitutes[eventName];
        }
        else {
            throw "library you've selected doesn't support " + eventName + ' event';
        }
    };

    knockouch.init = function () {
        var i;
        var eventName;
    
        for (i in knockouch.touchEvents) {
            if (knockouch.touchEvents.hasOwnProperty(i)) {
                eventName = knockouch.touchEvents[i];
                knockouch.makeTouchHandlerShortcut(eventName);
            }
        }
        knockouch.searchTouchLib();
    };

    /*
     *Wrappers to libraries that are used to provide touch handling. Currently hammer, zepto and jquery.mobile.
     *Every wrapper consists of the following: 
     * 1.method taht checks if library was loaded (isLoaded method by your captain obvious), 
     * 2.provide eventSubstitutes object that contains mapping 
     *   between library we need to use and knockouch and hammerjs events naming.
     * 3.wrapper function that provides unified interface to bind touch events for knockoutjs bindings.
     *   For details see knockouch.MakeTouchHandlerShortcut method.
     *
     * To add your own wrapper for touch library see our documentation
     */
    knockouch.touchLibs.Hammer = {
        isLoaded: function () {
            return window.Hammer ? true : false;
        },
        optionsList: ['doubletap_distance', 'doubletap_interval', 'drag',
                        'drag_block_horizontal', 'drag_block_vertical', 'drag_lock_to_axis',
                        'drag_max_touches', 'drag_min_distance', 'hold',
                        'hold_threshold', 'hold_timeout', 'prevent_default',
                        'release', 'show_touches', 'stop_browser_behavior',
                        'swipe', 'swipe_max_touches', 'swipe_velocity',
                        'tap', 'tap_max_distance', 'tap_max_touchtime',
                        'touch', 'transform', 'transform_always_block',
                        'transform_min_rotation', 'transform_min_scale'],
        setMoreOptions: function (bindings) {
            var extendedOptions = knockouch.options;
            var i;
            var optionName;
    
            for (i in this.optionsList) {
                if (this.optionsList.hasOwnProperty(i)) {
                    optionName = this.optionsList[i];
                    if (bindings[optionName] !== undefined && bindings[optionName].constructor !== Function) {
                        knockouch.options[optionName] = bindings[optionName];
                    }
                }
            }
            return extendedOptions;
        },
        wrapper: function (element, touchEventName, handler, bindings) {
            var extendedOptions = this.setMoreOptions(bindings);
            
            Hammer(element, extendedOptions).on(touchEventName, handler);
        }
    };

    knockouch.touchLibs.jQueryMobile = {
        isLoaded: function () {
            return window.jQuery && window.jQuery.mobile ? true : false;
        },
        eventSubstitutes: {
            'swipeleft': 'swipeleft',
            'swiperight': 'swiperight',
            'hold': 'taphold',
            'tap': 'tap',
            'swipe': 'swipe'
        },
        wrapper: function (element, touchEventName, handler) {
            touchEventName = knockouch.unifyEventName(touchEventName, this.eventSubstitutes);
            jQuery(element).bind(touchEventName, handler);
        }
    };

    knockouch.touchLibs.Zepto = {
        isLoaded: function () {
            return window.Zepto ? true : false;
        },
        eventSubstitutes: {
            'swipeleft': 'swipeLeft',
            'swiperight': 'swipeRight',
            'swipeup': 'swipeUp',
            'swipedown': 'swipeDown',
            'doubletap': 'doubleTap',
            'hold': 'longTap',
            'tap': 'tap',
            'swipe': 'swipe'
        },
        wrapper: function (element, touchEventName, handler) {
            touchEventName = knockouch.unifyEventName(touchEventName, this.eventSubstitutes);
            Zepto(element)[touchEventName](handler);
        }
    };

    //Setting one of the predefined libraries as selected touch library.
    knockouch.init();
    
    window.knockouch = knockouch;

}(window, ko));