/*
 * Lazy Line Painter
 * SVG Stroke animation.
 *
 * https://github.com/camoconnell/lazy-line-painter
 * http://www.camoconnell.com
 *
 * Licensed under the MIT license.
 *
 */

(function($, window, undefined) {

    "use strict";

    var dataKey = 'lazyLinePainter';
    var methods = {

        /*
            PUBLIC : SETUP LAZY LINE DATA
        */
        init: function(_options) {

            return this.each(function() {

                var $this = $(this);
                var data = $this.data(dataKey);
                $this.addClass('lazy-line');

                // If the plugin hasn't been initialized yet
                if (!data) {

                    // Collect settings, define defaults
                    var options = $.extend({
                        'width': null,
                        'height': null,
                        'strokeWidth': 2,
                        'strokeColor': '#000',
                        'strokeOverColor': null,
                        'strokeCap': 'round',
                        'strokeJoin': 'round',
                        'strokeOpacity': 1,
                        'arrowEnd': 'none',
                        'strokeDash': null,
                        'onComplete': null,
                        'delay': null,
                        'overrideKey': null,
                        'drawSequential': true,
                        'speedMultiplier': 1
                    }, _options);

                    // Set up path information
                    // if overrideKey has been defined - use overrideKey as key within the svgData object.
                    // else - use the elements id as key within the svgData object.
                    var target = options.overrideKey ? options.overrideKey : $this.attr('id').replace('#', '');
                    var w = options.svgData[target].dimensions.width;
                    var h = options.svgData[target].dimensions.height;

                    // target stroke path
                    options.svgData = options.svgData[target].strokepath;

                    // Create svg element and set dimensions
                    if (options.width === null) {
                        options.width = w;
                    }
                    if (options.height === null) {
                        options.height = h;
                    }
                    var svg = getSVGElement({
                        viewBox: '0 0 ' + w + ' ' + h,
                        preserveAspectRatio: 'xMidYMid'
                    });
                    options.svg = $(svg);
                    $this.append(options.svg);
                    $this.data(dataKey, options);
                }
            });
        },

        /*
            PUBLIC : PAINT LAZY LINE DATA
        */
        paint: function() {
            return this.each(function() {
                var $this = $(this);
                var data = $this.data(dataKey);
                var init = function() {
                    // Set width / height of container element
                    $this.css({
                        'width': data.width,
                        'height': data.height
                    });

                    // Build array of path objects
                    data.paths = [];
                    data.longestDuration = 0;
                    data.playhead = 0;

                    // Loop paths
                    for (var i = 0; i < data.svgData.length; i++) {

                        var path = getPath(data, i);
                        var length = path.getTotalLength();
                        path.style.strokeDasharray = length + ' ' + length;
                        path.style.strokeDashoffset = length;
                        path.style.display = 'block';
                        path.getBoundingClientRect();

                        var duration = data.svgData[i].duration * data.speedMultiplier;
                        if (duration > data.longestDuration) {
                            data.longestDuration = duration;
                        }

                        data.paths.push({
                            'duration': duration,
                            'drawStartTime': data.playhead,
                            'path': path,
                            'length': length
                        });
                        data.playhead += duration;
                    }

                    data.totalDuration = (data.drawSequential) ? data.playhead : data.longestDuration;
                    data.rAF = requestAnimationFrame(function(timestamp) {
                        draw(timestamp, data);
                    });
                };

                // if delay isset
                if (data.delay === null) {
                    init();
                } else {
                    setTimeout(init, data.delay);
                }
            });
        },

        /*
            TOGGLE PAUSE/RESUME ANIMATION
        */
        pauseResume: function() {

            return this.each(function() {

                var data = $(this).data(dataKey);

                if (!data.paused) {
                    data.paused = true;
                    cancelAnimationFrame(data.rAF);
                } else {
                    data.paused = false;
                    // resume
                    requestAnimationFrame(function(timestamp) {
                        adjustStartTime(timestamp, data)
                    });
                }
            });
        },
        /*
            ERASE LAZY LINE DATA
        */
        erase: function() {

            return this.each(function() {
                var $this = $(this);
                $this.removeData(dataKey);
                $this.find('svg').empty();

            });
        },

        /*
            DESTROY LAZY LINE DATA & ELEMENT
        */
        destroy: function() {

            return this.each(function() {

                var $this = $(this);
                $this.removeData(dataKey);
                $this.remove();
            });
        },
        /*
            STAMP LAZY LINE DATA
        */
        stamp: function() {

            return this.each(function() {

                var $this = $(this),
                    d = $this.data(dataKey);

                var init = function() {

                    // Set width / height of container element
                    $this.css({
                        'width': d.width,
                        'height': d.height
                    });

                    // Loop paths
                    //$.each(d.svgData, function (i, val) {
                    for (i = 0; i < d.svgData.length; i++) {
                        d.paper.path(d.svgData[i].path).attr(applyStyles(d, d.svgData[i]));
                    }

                };

                // if delay isset
                if (d.delay === null)
                    init();
                else
                    setTimeout(init, d.delay);
            });
        }
    };

    var adjustStartTime = function(timestamp, o) {
        o.startTime = timestamp - o.elapsed_time;
        requestAnimationFrame(function(timestamp) {
            draw(timestamp, o);
        });
    }

    var draw = function(timestamp, o) {

        if (o.startTime == null) {
            o.startTime = timestamp;
        }
        o.elapsed_time = timestamp - o.startTime;

        for (var i = 0; i < o.paths.length; i++) {

            var path_elapsed_time;
            if (o.drawSequential) {
                path_elapsed_time = o.elapsed_time - o.paths[i].drawStartTime;
                if (path_elapsed_time < 0) path_elapsed_time = 0;
            } else {
                path_elapsed_time = o.elapsed_time;
            }

            // don't redraw paths that are finished or paths that aren't up yet
            if (path_elapsed_time < o.paths[i].duration && path_elapsed_time > 0) {
                var frame_length = path_elapsed_time / o.paths[i].duration * o.paths[i].length;
                o.paths[i].path.style.strokeDashoffset = o.paths[i].length - frame_length;
            } else if (path_elapsed_time > o.paths[i].duration) {
                o.paths[i].path.style.strokeDashoffset = 0;
            }
        }

        // whether to continue
        if (o.elapsed_time < o.totalDuration) {
            o.rAF = requestAnimationFrame(function(timestamp) {
                draw(timestamp, o);
            });
        } else {
            if (o.onComplete != null) o.onComplete();
        }
    }

    var applyStyles = function(data, value) {

        var styles = {
            "stroke": (!value.strokeColor) ? data.strokeColor : value.strokeColor,
            "fill-opacity": 0,
            "stroke-dasharray": (!value.strokeDash) ? data.strokeDash : value.strokeDash,
            "stroke-opacity": (!value.strokeOpacity) ? data.strokeOpacity : value.strokeOpacity,
            "stroke-width": (!value.strokeWidth) ? data.strokeWidth : value.strokeWidth,
            "stroke-linecap": (!value.strokeCap) ? data.strokeCap : value.strokeCap,
            "stroke-linejoin": (!value.strokeJoin) ? data.strokeJoin : value.strokeJoin
        };

        return styles;
    };

    /*
        PRIVATE : SET PATH DATA
    */
    var getPath = function(data, i) {
        var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        var $path = $(path);
        data.svg.append($path);
        $path.attr(getAttributes(data, data.svgData[i]));
        return path;
    };

    /*
        PRIVATE : GET STYLE DATA
    */
    var getAttributes = function(data, value) {
        var attributes = {
            "d": value.path,
            "stroke": (!value.strokeColor) ? data.strokeColor : value.strokeColor,
            "fill-opacity": 0,
            "stroke-dasharray": (!value.strokeDash) ? data.strokeDash : value.strokeDash,
            "stroke-opacity": (!value.strokeOpacity) ? data.strokeOpacity : value.strokeOpacity,
            "stroke-width": (!value.strokeWidth) ? data.strokeWidth : value.strokeWidth,
            "stroke-linecap": (!value.strokeCap) ? data.strokeCap : value.strokeCap,
            "stroke-linejoin": (!value.strokeJoin) ? data.strokeJoin : value.strokeJoin,
            "arrow-end": (!value.arrowEnd) ? data.arrowEnd : value.arrowEnd,
            "markerWidth": "4",
            "markerHeight": "3",
            "orient": "auto"
        };
        return attributes;
    };

    /*
        PRIVATE : GET STYLE DATA
    */
    var getSVGElement = function(attr) {
        var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        svg.setAttributeNS(null, 'viewBox', attr.viewBox);
        svg.setAttributeNS(null, 'preserveAspectRatio', attr.preserveAspectRatio);
        svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
        return svg;
    };

    $.fn.lazylinepainter = function(method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            // error
        }
    };

})(jQuery, window);