/*!

* Waves v0.7.5
* http://fian.my.id/Waves
*
* Copyright 2014-2016 Alfiana E. Sibuea and other contributors
* Released under the MIT license
* https://github.com/fians/Waves/blob/master/LICENSE
*/

;(function(window, factory) {

'use strict';

// AMD. Register as an anonymous module.  Wrap in function so we have access
// to root via `this`.
if (typeof define === 'function' && define.amd) {
    define([], function() {
        return factory.apply(window);
    });
}

// Node. Does not work with strict CommonJS, but only CommonJS-like
// environments that support module.exports, like Node.
else if (typeof exports === 'object') {
    module.exports = factory.call(window);
}

// Browser globals.
else {
    window.Waves = factory.call(window);
}

})(typeof global === 'object' ? global : this, function() {

'use strict';

var Waves            = Waves || {};
var $$               = document.querySelectorAll.bind(document);
var toString         = Object.prototype.toString;
var isTouchAvailable = 'ontouchstart' in window;

// Find exact position of element
function isWindow(obj) {
    return obj !== null && obj === obj.window;
}

function getWindow(elem) {
    return isWindow(elem) ? elem : elem.nodeType === 9 && elem.defaultView;
}

function isObject(value) {
    var type = typeof value;
    return type === 'function' || type === 'object' && !!value;
}

function isDOMNode(obj) {
    return isObject(obj) && obj.nodeType > 0;
}

function getWavesElements(nodes) {
    var stringRepr = toString.call(nodes);

    if (stringRepr === '[object String]') {
        return $$(nodes);
    } else if (isObject(nodes) && /^\[object (Array|HTMLCollection|NodeList|Object)\]$/.test(stringRepr) && nodes.hasOwnProperty('length')) {
        return nodes;
    } else if (isDOMNode(nodes)) {
        return [nodes];
    }

    return [];
}

function offset(elem) {
    var docElem, win,
        box = { top: 0, left: 0 },
        doc = elem && elem.ownerDocument;

    docElem = doc.documentElement;

    if (typeof elem.getBoundingClientRect !== typeof undefined) {
        box = elem.getBoundingClientRect();
    }
    win = getWindow(doc);
    return {
        top: box.top + win.pageYOffset - docElem.clientTop,
        left: box.left + win.pageXOffset - docElem.clientLeft
    };
}

function convertStyle(styleObj) {
    var style = '';

    for (var prop in styleObj) {
        if (styleObj.hasOwnProperty(prop)) {
            style += (prop + ':' + styleObj[prop] + ';');
        }
    }

    return style;
}

var Effect = {

    // Effect duration
    duration: 750,

    // Effect delay (check for scroll before showing effect)
    delay: 200,

    show: function(e, element, velocity) {

        // Disable right click
        if (e.button === 2) {
            return false;
        }

        element = element || this;

        // Create ripple
        var ripple = document.createElement('div');
        ripple.className = 'waves-ripple waves-rippling';
        element.appendChild(ripple);

        // Get click coordinate and element width
        var pos       = offset(element);
        var relativeY = 0;
        var relativeX = 0;
        // Support for touch devices
        if('touches' in e && e.touches.length) {
            relativeY   = (e.touches[0].pageY - pos.top);
            relativeX   = (e.touches[0].pageX - pos.left);
        }
        //Normal case
        else {
            relativeY   = (e.pageY - pos.top);
            relativeX   = (e.pageX - pos.left);
        }
        // Support for synthetic events
        relativeX = relativeX >= 0 ? relativeX : 0;
        relativeY = relativeY >= 0 ? relativeY : 0;

        var scale     = 'scale(' + ((element.clientWidth / 100) * 3) + ')';
        var translate = 'translate(0,0)';

        if (velocity) {
            translate = 'translate(' + (velocity.x) + 'px, ' + (velocity.y) + 'px)';
        }

        // Attach data to element
        ripple.setAttribute('data-hold', Date.now());
        ripple.setAttribute('data-x', relativeX);
        ripple.setAttribute('data-y', relativeY);
        ripple.setAttribute('data-scale', scale);
        ripple.setAttribute('data-translate', translate);

        // Set ripple position
        var rippleStyle = {
            top: relativeY + 'px',
            left: relativeX + 'px'
        };

        ripple.classList.add('waves-notransition');
        ripple.setAttribute('style', convertStyle(rippleStyle));
        ripple.classList.remove('waves-notransition');

        // Scale the ripple
        rippleStyle['-webkit-transform'] = scale + ' ' + translate;
        rippleStyle['-moz-transform'] = scale + ' ' + translate;
        rippleStyle['-ms-transform'] = scale + ' ' + translate;
        rippleStyle['-o-transform'] = scale + ' ' + translate;
        rippleStyle.transform = scale + ' ' + translate;
        rippleStyle.opacity = '1';

        var duration = e.type === 'mousemove' ? 2500 : Effect.duration;
        rippleStyle['-webkit-transition-duration'] = duration + 'ms';
        rippleStyle['-moz-transition-duration']    = duration + 'ms';
        rippleStyle['-o-transition-duration']      = duration + 'ms';
        rippleStyle['transition-duration']         = duration + 'ms';

        ripple.setAttribute('style', convertStyle(rippleStyle));
    },

    hide: function(e, element) {
        element = element || this;

        var ripples = element.getElementsByClassName('waves-rippling');

        for (var i = 0, len = ripples.length; i < len; i++) {
            removeRipple(e, element, ripples[i]);
        }
    }
};

/**
 * Collection of wrapper for HTML element that only have single tag
 * like <input> and <img>
 */
var TagWrapper = {

    // Wrap <input> tag so it can perform the effect
    input: function(element) {

        var parent = element.parentNode;

        // If input already have parent just pass through
        if (parent.tagName.toLowerCase() === 'i' && parent.classList.contains('waves-effect')) {
            return;
        }

        // Put element class and style to the specified parent
        var wrapper       = document.createElement('i');
        wrapper.className = element.className + ' waves-input-wrapper';
        element.className = 'waves-button-input';

        // Put element as child
        parent.replaceChild(wrapper, element);
        wrapper.appendChild(element);

        // Apply element color and background color to wrapper
        var elementStyle    = window.getComputedStyle(element, null);
        var color           = elementStyle.color;
        var backgroundColor = elementStyle.backgroundColor;

        wrapper.setAttribute('style', 'color:' + color + ';background:' + backgroundColor);
        element.setAttribute('style', 'background-color:rgba(0,0,0,0);');

    },

    // Wrap <img> tag so it can perform the effect
    img: function(element) {

        var parent = element.parentNode;

        // If input already have parent just pass through
        if (parent.tagName.toLowerCase() === 'i' && parent.classList.contains('waves-effect')) {
            return;
        }

        // Put element as child
        var wrapper  = document.createElement('i');
        parent.replaceChild(wrapper, element);
        wrapper.appendChild(element);

    }
};

/**
 * Hide the effect and remove the ripple. Must be
 * a separate function to pass the JSLint...
 */
function removeRipple(e, el, ripple) {

    // Check if the ripple still exist
    if (!ripple) {
        return;
    }

    ripple.classList.remove('waves-rippling');

    var relativeX = ripple.getAttribute('data-x');
    var relativeY = ripple.getAttribute('data-y');
    var scale     = ripple.getAttribute('data-scale');
    var translate = ripple.getAttribute('data-translate');

    // Get delay beetween mousedown and mouse leave
    var diff = Date.now() - Number(ripple.getAttribute('data-hold'));
    var delay = 350 - diff;

    if (delay < 0) {
        delay = 0;
    }

    if (e.type === 'mousemove') {
        delay = 150;
    }

    // Fade out ripple after delay
    var duration = e.type === 'mousemove' ? 2500 : Effect.duration;

    setTimeout(function() {

        var style = {
            top: relativeY + 'px',
            left: relativeX + 'px',
            opacity: '0',

            // Duration
            '-webkit-transition-duration': duration + 'ms',
            '-moz-transition-duration': duration + 'ms',
            '-o-transition-duration': duration + 'ms',
            'transition-duration': duration + 'ms',
            '-webkit-transform': scale + ' ' + translate,
            '-moz-transform': scale + ' ' + translate,
            '-ms-transform': scale + ' ' + translate,
            '-o-transform': scale + ' ' + translate,
            'transform': scale + ' ' + translate
        };

        ripple.setAttribute('style', convertStyle(style));

        setTimeout(function() {
            try {
                el.removeChild(ripple);
            } catch (e) {
                return false;
            }
        }, duration);

    }, delay);
}

/**
 * Disable mousedown event for 500ms during and after touch
 */
var TouchHandler = {

    /* uses an integer rather than bool so there's no issues with
     * needing to clear timeouts if another touch event occurred
     * within the 500ms. Cannot mouseup between touchstart and
     * touchend, nor in the 500ms after touchend. */
    touches: 0,

    allowEvent: function(e) {

        var allow = true;

        if (/^(mousedown|mousemove)$/.test(e.type) && TouchHandler.touches) {
            allow = false;
        }

        return allow;
    },
    registerEvent: function(e) {
        var eType = e.type;

        if (eType === 'touchstart') {

            TouchHandler.touches += 1; // push

        } else if (/^(touchend|touchcancel)$/.test(eType)) {

            setTimeout(function() {
                if (TouchHandler.touches) {
                    TouchHandler.touches -= 1; // pop after 500ms
                }
            }, 500);

        }
    }
};

/**
 * Delegated click handler for .waves-effect element.
 * returns null when .waves-effect element not in "click tree"
 */
function getWavesEffectElement(e) {

    if (TouchHandler.allowEvent(e) === false) {
        return null;
    }

    var element = null;
    var target = e.target || e.srcElement;

    while (target.parentElement !== null) {
        if (target.classList.contains('waves-effect') && (!(target instanceof SVGElement))) {
            element = target;
            break;
        }
        target = target.parentElement;
    }

    return element;
}

/**
 * Bubble the click and show effect if .waves-effect elem was found
 */
function showEffect(e) {

    // Disable effect if element has "disabled" property on it
    // In some cases, the event is not triggered by the current element
    // if (e.target.getAttribute('disabled') !== null) {
    //     return;
    // }

    var element = getWavesEffectElement(e);

    if (element !== null) {

        // Make it sure the element has either disabled property, disabled attribute or 'disabled' class
        if (element.disabled || element.getAttribute('disabled') || element.classList.contains('disabled')) {
            return;
        }

        TouchHandler.registerEvent(e);

        if (e.type === 'touchstart' && Effect.delay) {

            var hidden = false;

            var timer = setTimeout(function () {
                timer = null;
                Effect.show(e, element);
            }, Effect.delay);

            var hideEffect = function(hideEvent) {

                // if touch hasn't moved, and effect not yet started: start effect now
                if (timer) {
                    clearTimeout(timer);
                    timer = null;
                    Effect.show(e, element);
                }
                if (!hidden) {
                    hidden = true;
                    Effect.hide(hideEvent, element);
                }
            };

            var touchMove = function(moveEvent) {
                if (timer) {
                    clearTimeout(timer);
                    timer = null;
                }
                hideEffect(moveEvent);
            };

            element.addEventListener('touchmove', touchMove, false);
            element.addEventListener('touchend', hideEffect, false);
            element.addEventListener('touchcancel', hideEffect, false);

        } else {

            Effect.show(e, element);

            if (isTouchAvailable) {
                element.addEventListener('touchend', Effect.hide, false);
                element.addEventListener('touchcancel', Effect.hide, false);
            }

            element.addEventListener('mouseup', Effect.hide, false);
            element.addEventListener('mouseleave', Effect.hide, false);
        }
    }
}

Waves.init = function(options) {
    var body = document.body;

    options = options || {};

    if ('duration' in options) {
        Effect.duration = options.duration;
    }

    if ('delay' in options) {
        Effect.delay = options.delay;
    }

    if (isTouchAvailable) {
        body.addEventListener('touchstart', showEffect, false);
        body.addEventListener('touchcancel', TouchHandler.registerEvent, false);
        body.addEventListener('touchend', TouchHandler.registerEvent, false);
    }

    body.addEventListener('mousedown', showEffect, false);
};

/**
 * Attach Waves to dynamically loaded inputs, or add .waves-effect and other
 * waves classes to a set of elements. Set drag to true if the ripple mouseover
 * or skimming effect should be applied to the elements.
 */
Waves.attach = function(elements, classes) {

    elements = getWavesElements(elements);

    if (toString.call(classes) === '[object Array]') {
        classes = classes.join(' ');
    }

    classes = classes ? ' ' + classes : '';

    var element, tagName;

    for (var i = 0, len = elements.length; i < len; i++) {

        element = elements[i];
        tagName = element.tagName.toLowerCase();

        if (['input', 'img'].indexOf(tagName) !== -1) {
            TagWrapper[tagName](element);
            element = element.parentElement;
        }

        if (element.className.indexOf('waves-effect') === -1) {
            element.className += ' waves-effect' + classes;
        }
    }
};

/**
 * Cause a ripple to appear in an element via code.
 */
Waves.ripple = function(elements, options) {
    elements = getWavesElements(elements);
    var elementsLen = elements.length;

    options          = options || {};
    options.wait     = options.wait || 0;
    options.position = options.position || null; // default = centre of element

    if (elementsLen) {
        var element, pos, off, centre = {}, i = 0;
        var mousedown = {
            type: 'mousedown',
            button: 1
        };
        var hideRipple = function(mouseup, element) {
            return function() {
                Effect.hide(mouseup, element);
            };
        };

        for (; i < elementsLen; i++) {
            element = elements[i];
            pos = options.position || {
                x: element.clientWidth / 2,
                y: element.clientHeight / 2
            };

            off      = offset(element);
            centre.x = off.left + pos.x;
            centre.y = off.top + pos.y;

            mousedown.pageX = centre.x;
            mousedown.pageY = centre.y;

            Effect.show(mousedown, element);

            if (options.wait >= 0 && options.wait !== null) {
                var mouseup = {
                    type: 'mouseup',
                    button: 1
                };

                setTimeout(hideRipple(mouseup, element), options.wait);
            }
        }
    }
};

/**
 * Remove all ripples from an element.
 */
Waves.calm = function(elements) {
    elements = getWavesElements(elements);
    var mouseup = {
        type: 'mouseup',
        button: 1
    };

    for (var i = 0, len = elements.length; i < len; i++) {
        Effect.hide(mouseup, elements[i]);
    }
};

/**
 * Deprecated API fallback
 */
Waves.displayEffect = function(options) {
    console.error('Waves.displayEffect() has been deprecated and will be removed in future version. Please use Waves.init() to initialize Waves effect');
    Waves.init(options);
};

return Waves;

});