/*global Firmin */
 
define(function(require, exports, module) {
    main.consumes = ["Plugin", "settings", "util"];
    main.provides = ["anims"];
    return main;

    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var settings = imports.settings;
        var util = imports.util;
        
        require("./lib_firmin");
                
        var EventEmitter = require("events").EventEmitter;
        
        /***** Initialization *****/
        
        var plugin = new Plugin("Ajax.org", main.consumes);
        var emit = plugin.getEmitter();
        
        var animating = false;
        
        /***** Methods *****/
        
        var pfx = apf.isWebkit ? "webkit" : (apf.isIE ? "MS" : "");
        function cssAnimate(el, options, finish) {
            if (options.splitbox)
                return util.nextFrame($cssAnimate.bind(null, el, options, finish));
            // todo using $cssAnimate instead of firmin displays spurious animations when dragging tabs
            Firmin.animate(el, options, options && options.duration || 0.2, function() {
                el.style[apf.CSSPREFIX + "Transition"] = "";
                finish && finish();
            });
        }
        
        function $cssAnimate(el, options, finish) {
            // Duration
            var duration = options.duration || "0.2s";
            if (typeof duration == "string") {
                duration = parseFloat(duration) 
                    * (duration[duration.length - 2] == "m" ? 1 : 0.001);
            }
            el.style[apf.CSSPREFIX + "TransitionDuration"] = duration + "s";
            
            // Delay
            var delay = options.delay || "0s";
            el.style[apf.CSSPREFIX + "TransitionDelay"] = 
                typeof delay == "string" ? delay : delay + "s";
            
            // Timing Function
            var timingFunction = options.timingFunction || 'linear';
            if (timingFunction)
                el.style[apf.CSSPREFIX + "TransitionTimingFunction"] = 
                    timingFunction;
            
            // Properties
            var props = [];
            for (var prop in options){
                if (/duration|delay|timingFunction|immediate/.test(prop)) continue;
                props.push(prop);
            }
            el.style[apf.CSSPREFIX + "TransitionProperty"] = props.join(",");
            
            var eventName = pfx ? pfx + "TransitionEnd" : "transitionend";
            var wait = el.wait = function(e) {
                if (!wait) return;
                el.removeEventListener(eventName, wait, false);
                el.style[apf.CSSPREFIX + "Transition"] = "";
                if (el.wait !== wait)
                    return;
                wait = el.wait = null;
                finish && finish();
            };
            el.addEventListener(eventName, wait, false);
            // fallback in case there is no event
            setTimeout(function() {
                wait && wait();
            }, duration * 1000 + 10);

            props.forEach(function(name){ el.style[name] = options[name] });
        }
        
        function animateMultiple(tweens, finish) {
            var shouldAnimate = settings.getBool("user/general/@animateui");
    
            if (shouldAnimate) {
                animating = true;
                
                var duration = 0, called;
                tweens.forEach(function(options) {
                    var node = options.node;
                    cssAnimate(node.$ext || node, options, function() {
                        if (options.duration == duration && !called) {
                            called = true;
                            finish && finish();
                        }
                        animating = false;
                    });
                    duration = Math.max(duration, options.duration || 0.2);
                });
            }
            else {
                //@todo set value
    
                finish && finish();
            }
        }
    
        function animate(aNode, options, finish) {
            var shouldAnimate = settings.getBool("user/general/@animateui");
    
            if (shouldAnimate) {
                animating = true;
                
                cssAnimate(aNode.$ext || aNode, options, function() {
                    finish && finish(); //setTimeout(finish, 30);
                    
                    animating = false;
                });
            }
            else {
                var htmlNode = aNode.$ext || aNode;
                for (var prop in options) {
                    if (prop == "duration" || prop == "timingFunction")
                        continue;
                    htmlNode.style[prop] = options[prop];
                }
                
                //@todo set value
                finish && finish();
            }
        }
    
        function animateSplitBoxNode(aNode, options, finish) {
            var shouldAnimate = settings.getBool("user/general/@animateui");
    
            var pNode = aNode.parentNode;
            if (!pNode.$box) return oNode && animate(oNode, options, finish);
            
            var firstChild = pNode.getFirstChild();
            var lastChild = pNode.getSecondChild();
            var isFirst, oNode = (isFirst = aNode == firstChild) ? lastChild : firstChild;
            if (oNode == aNode || !oNode.visible)
                throw new Error("animating object that has no partner");
    
            var to2 = { 
                timingFunction : options.timingFunction,
                duration: options.duration
            };
            if (pNode.$vbox) {
                if (isFirst)
                    to2.top = (parseInt(options.height, 10) + pNode.$edge[0] + pNode.padding) + "px";
                else
                    to2.bottom = (parseInt(options.height, 10) + pNode.$edge[2] + pNode.padding) + "px";
            }
            else {
                if (isFirst)
                    to2.left = (parseInt(options.width, 10) + pNode.$edge[3] + pNode.padding) + "px";
                else
                    to2.right = (parseInt(options.width, 10) + pNode.$edge[1] + pNode.padding) + "px";
            }
            
            // Create Event Object
            var e = new EventEmitter();
            e.type = "splitbox";
            e.which = aNode;
            e.other = oNode;
            e.options = options;
            e.options2 = to2;
            e.duration = options.duration || 0.2;
            
            // Emit animate event
            emit("animate", e);

            if (shouldAnimate && !options.immediate) {
                animating = true;

                options.splitbox = to2.splitbox = true;
                cssAnimate(aNode.$ext, options);
                cssAnimate(oNode.$ext, to2, function(){
                    if (aNode.parentNode) {
                        if (pNode.$vbox)
                            aNode.setHeight(parseInt(options.height, 10));
                        else
                            aNode.setWidth(parseInt(options.width, 10));
                    }
                    
                    finish && finish();
                    e.emit("finish");
    
                    animating = false;
                });
            }
            else {
                var dir;
                if (pNode.$vbox) {
                    aNode.setHeight(options.height);
                    dir = isFirst ? "top" : "bottom";
                }
                else {
                    aNode.setWidth(options.width);
                    dir = isFirst ? "left" : "right";
                }
                oNode.$ext.style[dir] = to2[dir];
                
                finish && finish();
                e.emit("finish");
            }
        }
        
        function emitAnimate(e) {
            emit("animate", e);
        }
        
        /***** Register and define API *****/
        
        /**
         * Animation API for Cloud9. Use this object to animate HTML 
         * elements and APF elements. The implementation uses CSS animations
         * to animate the HTML elements.
         * 
         *     anims.animate(someDiv, { 
         *         width    : "200px", 
         *         duration : 0.2 
         *     }, function(){
         *         console.log("done");
         *     });
         * 
         * @singleton
         **/
        plugin.freezePublicAPI({
            /**
             * Specifies whether at least one animationg is running at this moment.
             * @property {Boolean} animating
             */
            get animating(){ return animating; },
            
            _events: [
                /**
                 * Fires when an animation starts
                 * @event animate
                 * @param {Object}                 e
                 * @param {String}                 e.type             The animation type. Possible values are "splitbox", and others.
                 * @param {HTMLElement/AMLElement} e.which            The element that is animated
                 * @param {HTMLElement/AMLElement} e.other            This is only relevant for a splitbox resize, where there is a 2nd element that is animated.
                 * @param {Object}                 e.options          The options passed to the {@link #method-animate} method.
                 * @param {Object}                 e.options2         This is only relevant for a splitbox resize. There are the options for the 2nd animation.
                 * @param {Number}                 [e.duration=0.2]   The duration of the animation expressed in seconds
                 */
                "animate"
            ],
            
            /**
             * Animate multiple elements and/or multiple properties at the
             * same time. Note that each tween object can specify multiple 
             * css properties by adding the names of the css property as a key
             * and the value as it's value.
             * 
             * Example:
             * 
             *     anims.animateMultiple([{
             *         node   : someDiv,
             *         width  : "200px",
             *         height : "300px"
             *     }, {
             *         node   : anotherDiv,
             *         width  : "100px",
             *         height : "100px"
             *     }], function(){});
             * 
             * @param {Array}                  tweens                   Array of tween elements.
             * @param {HTMLElement/AMLElement} tweens.node              The element that is animated
             * @param {Number}                 [tweens.duration=0.2]    The duration of the animation expressed in seconds.
             * @param {String}                 [tweens.timingFunction]  The [CSS timing function](https://developer.mozilla.org/en-US/docs/Web/CSS/timing-function).
             * @param {Function}               finish                   Called when all animations have completed.
             */
            animateMultiple: animateMultiple,
            
            /**
             * Animates a single element and one or more properties.
             * Note that the tween object can contain multiple 
             * css properties by adding the names of the css property as a key
             * and the value as it's value.
             * 
             * Example:
             * 
             *     anims.animate(node, {
             *         width  : "200px",
             *         height : "300px"
             *     }, function(){});
             * 
             * @param {HTMLElement/AMLElement} node                    The element that is animated
             * @param {Object}                 tween
             * @param {Number}                 [tween.duration=0.2]    The duration of the animation expressed in seconds.
             * @param {String}                 [tween.timingFunction]  The [CSS timing function](https://developer.mozilla.org/en-US/docs/Web/CSS/timing-function).
             * @param {Function}               finish                  Called when all animations have completed.
             */
            animate: animate,
            
            /**
             * This method is dedicated to animating APF Elements that are
             * part of a splitbox layout element. A splitbox is a box that can 
             * be split in 2 by adding 2 children to it. If you are using a
             * splitbox in your UI and need to animate a child element, then 
             * use this function.
             *
             * Note that the tween object can contain multiple 
             * css properties by adding the names of the css property as a key
             * and the value as it's value.
             * 
             * Example:
             * 
             *     anims.animateSplitBoxNode(panel, {
             *         duration : 0.5,
             *         width    : "200px"
             *     }, function(){})
             * 
             * @param {AMLElement} node                    The element that is animated
             * @param {Object}     tween
             * @param {Number}     [tween.duration=0.2]    The duration of the animation expressed in seconds.
             * @param {String}     [tween.timingFunction]  The [CSS timing function](https://developer.mozilla.org/en-US/docs/Web/CSS/timing-function).
             * @param {Function}   finish                  Called when all animations have completed.
             */
            animateSplitBoxNode: animateSplitBoxNode,
            
            /**
             * Emits the animate event, forcing a resize amongst editors.
             * @param {Object} e
             * @private
             */
            emitAnimate: emitAnimate
        });
        
        register(null, {
            anims: plugin
        });
    }
});