/**
 * @license Highcharts JS v3.0.6 (2013-10-04)
 * MooTools adapter
 *
 * (c) 2010-2013 Torstein Hønsi
 *
 * License: www.highcharts.com/license
 */

// JSLint options:
/*global Fx, $, $extend, $each, $merge, Events, Event, DOMEvent */

(function () {

    var win = window,
        doc = document,
        mooVersion = win.MooTools.version.substring(0, 3), // Get the first three characters of the version number
        legacy = mooVersion === '1.2' || mooVersion === '1.1', // 1.1 && 1.2 considered legacy, 1.3 is not.
        legacyEvent = legacy || mooVersion === '1.3', // In versions 1.1 - 1.3 the event class is named Event, in newer versions it is named DOMEvent.
        $extend = win.$extend || function () {
                return Object.append.apply(Object, arguments);
            };

    win.HighchartsAdapter = {
        /**
         * Initialize the adapter. This is run once as Highcharts is first run.
         * @param {Object} pathAnim The helper object to do animations across adapters.
         */
        init: function (pathAnim) {
            var fxProto = Fx.prototype,
                fxStart = fxProto.start,
                morphProto = Fx.Morph.prototype,
                morphCompute = morphProto.compute;

            // override Fx.start to allow animation of SVG element wrappers
            /*jslint unparam: true*//* allow unused parameters in fx functions */
            fxProto.start = function (from, to) {
                var fx = this,
                    elem = fx.element;

                // special for animating paths
                if (from.d) {
                    //this.fromD = this.element.d.split(' ');
                    fx.paths = pathAnim.init(
                        elem,
                        elem.d,
                        fx.toD
                    );
                }
                fxStart.apply(fx, arguments);

                return this; // chainable
            };

            // override Fx.step to allow animation of SVG element wrappers
            morphProto.compute = function (from, to, delta) {
                var fx = this,
                    paths = fx.paths;

                if (paths) {
                    fx.element.attr(
                        'd',
                        pathAnim.step(paths[0], paths[1], delta, fx.toD)
                    );
                } else {
                    return morphCompute.apply(fx, arguments);
                }
            };
            /*jslint unparam: false*/
        },

        /**
         * Run a general method on the framework, following jQuery syntax
         * @param {Object} el The HTML element
         * @param {String} method Which method to run on the wrapped element
         */
        adapterRun: function (el, method) {

            // This currently works for getting inner width and height. If adding
            // more methods later, we need a conditional implementation for each.
            if (method === 'width' || method === 'height') {
                return parseInt($(el).getStyle(method), 10);
            }
        },

        /**
         * Downloads a script and executes a callback when done.
         * @param {String} scriptLocation
         * @param {Function} callback
         */
        getScript: function (scriptLocation, callback) {
            // We cannot assume that Assets class from mootools-more is available so instead insert a script tag to download script.
            var head = doc.getElementsByTagName('head')[0];
            var script = doc.createElement('script');

            script.type = 'text/javascript';
            script.src = scriptLocation;
            script.onload = callback;

            head.appendChild(script);
        },

        /**
         * Animate a HTML element or SVG element wrapper
         * @param {Object} el
         * @param {Object} params
         * @param {Object} options jQuery-like animation options: duration, easing, callback
         */
        animate: function (el, params, options) {
            var isSVGElement = el.attr,
                effect,
                complete = options && options.complete;

            if (isSVGElement && !el.setStyle) {
                // add setStyle and getStyle methods for internal use in Moo
                el.getStyle = el.attr;
                el.setStyle = function () { // property value is given as array in Moo - break it down
                    var args = arguments;
                    this.attr.call(this, args[0], args[1][0]);
                };
                // dirty hack to trick Moo into handling el as an element wrapper
                el.$family = function () { return true; };
            }

            // stop running animations
            win.HighchartsAdapter.stop(el);

            // define and run the effect
            effect = new Fx.Morph(
                isSVGElement ? el : $(el),
                $extend({
                    transition: Fx.Transitions.Quad.easeInOut
                }, options)
            );

            // Make sure that the element reference is set when animating svg elements
            if (isSVGElement) {
                effect.element = el;
            }

            // special treatment for paths
            if (params.d) {
                effect.toD = params.d;
            }

            // jQuery-like events
            if (complete) {
                effect.addEvent('complete', complete);
            }

            // run
            effect.start(params);

            // record for use in stop method
            el.fx = effect;
        },

        /**
         * MooTool's each function
         *
         */
        each: function (arr, fn) {
            return legacy ?
                $each(arr, fn) :
                Array.each(arr, fn);
        },

        /**
         * Map an array
         * @param {Array} arr
         * @param {Function} fn
         */
        map: function (arr, fn) {
            return arr.map(fn);
        },

        /**
         * Grep or filter an array
         * @param {Array} arr
         * @param {Function} fn
         */
        grep: function (arr, fn) {
            return arr.filter(fn);
        },

        /**
         * Return the index of an item in an array, or -1 if not matched
         */
        inArray: function (item, arr, from) {
            return arr ? arr.indexOf(item, from) : -1;
        },

        /**
         * Get the offset of an element relative to the top left corner of the web page
         */
        offset: function (el) {
            var offsets = el.getPosition(); // #1496
            return {
                left: offsets.x,
                top: offsets.y
            };
        },

        /**
         * Extends an object with Events, if its not done
         */
        extendWithEvents: function (el) {
            // if the addEvent method is not defined, el is a custom Highcharts object
            // like series or point
            if (!el.addEvent) {
                if (el.nodeName) {
                    el = $(el); // a dynamically generated node
                } else {
                    $extend(el, new Events()); // a custom object
                }
            }
        },

        /**
         * Add an event listener
         * @param {Object} el HTML element or custom object
         * @param {String} type Event type
         * @param {Function} fn Event handler
         */
        addEvent: function (el, type, fn) {
            if (typeof type === 'string') { // chart broke due to el being string, type function

                if (type === 'unload') { // Moo self destructs before custom unload events
                    type = 'beforeunload';
                }

                win.HighchartsAdapter.extendWithEvents(el);

                el.addEvent(type, fn);
            }
        },

        removeEvent: function (el, type, fn) {
            if (typeof el === 'string') {
                // el.removeEvents below apperantly calls this method again. Do not quite understand why, so for now just bail out.
                return;
            }

            if (el.addEvent) { // If el doesn't have an addEvent method, there are no events to remove
                if (type) {
                    if (type === 'unload') { // Moo self destructs before custom unload events
                        type = 'beforeunload';
                    }

                    if (fn) {
                        el.removeEvent(type, fn);
                    } else if (el.removeEvents) { // #958
                        el.removeEvents(type);
                    }
                } else {
                    el.removeEvents();
                }
            }
        },

        fireEvent: function (el, event, eventArguments, defaultFunction) {
            var eventArgs = {
                type: event,
                target: el
            };
            // create an event object that keeps all functions
            event = legacyEvent ? new Event(eventArgs) : new DOMEvent(eventArgs);
            event = $extend(event, eventArguments);

            // When running an event on the Chart.prototype, MooTools nests the target in event.event
            if (!event.target && event.event) {
                event.target = event.event.target;
            }

            // override the preventDefault function to be able to use
            // this for custom events
            event.preventDefault = function () {
                defaultFunction = null;
            };
            // if fireEvent is not available on the object, there hasn't been added
            // any events to it above
            if (el.fireEvent) {
                el.fireEvent(event.type, event);
            }

            // fire the default if it is passed and it is not prevented above
            if (defaultFunction) {
                defaultFunction(event);
            }
        },

        /**
         * Set back e.pageX and e.pageY that MooTools has abstracted away. #1165, #1346.
         */
        washMouseEvent: function (e) {
            if (e.page) {
                e.pageX = e.page.x;
                e.pageY = e.page.y;
            }
            return e;
        },

        /**
         * Stop running animations on the object
         */
        stop: function (el) {
            if (el.fx) {
                el.fx.cancel();
            }
        }
    };

}());
