(function() {

    var initialDelay = 1000 // time from appearance (or outro finished) to intro animation
    var idleDelay = 6000 // time from end of intro to start of outro
    var finalDelay = 4000 // time from end of outro bubbles to end of outro (ie to add a delay between the outro bubbles and the next intro animation)

    var windowTopBuffer = 400
    var windowBottomBuffer = 200

    function PercolatorCircleAnimation(color, startTime) {
        this.fillColor = color;
        this.startTime = parseFloat(startTime);
        this.frames = new Array();
        this.currentFrame = 0;
    }

    function PercolatorCircle(x, y, radius) {
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    function ParseAnimationText(text) {
        var circleAnimations = new Array();

        lines = text.split('\n');
        for (i = 0; i < lines.length; i++) {
            var oneLine = lines[i];
            var frames = oneLine.split(';');

            oneCircleAnimation = new PercolatorCircleAnimation(frames[0], frames[1]);
            for (j = 2; j < frames.length; j = j + 3) {
                circle = new PercolatorCircle(Number(frames[j]), Number(frames[j+1]), Number(frames[j+2]));
                oneCircleAnimation.frames.push(circle);
            }

            circleAnimations.push(oneCircleAnimation);
        }
        return circleAnimations;
    }
    
    var savedData = {}
    var callbacks = {}
    
    function LoadAnimation(name, callback) {
        if(savedData[name]) {
            callback(ParseAnimationText(savedData[name]))
        } else if (callbacks[name]) {
            callbacks[name].push(callback)
        } else {
            var client = new XMLHttpRequest()
            callbacks[name] = [callback]
            
            client.onreadystatechange = function() {
                if (client.readyState == 4) {
                    if (client.status == 200) {
                        savedData[name] = client.responseText
                        for (var i = callbacks[name].length - 1; i >= 0; i--) {
                            callbacks[name][i](ParseAnimationText(savedData[name]))
                        }
                    }
                    delete callbacks[name]
                }
            }
            client.open('GET', name)
            client.send()
        }
    }

    var focusedBlock = null
    var animationBlocks = []
    var idleTimer = null
    
    function PercolatorAnimationBlock(el) {
        this.element = el;
        this.canvas = el.find('canvas')
        this.background_before = el.find('.percolatorBackgroundBefore')
        this.background_after = el.find('.percolatorBackgroundAfter')

        this.state = 'hidden'
        this.kin = new Kinetic_2d(this.canvas.attr('id'));
    }
    
    PercolatorAnimationBlock.prototype.animationYOffset = function() {
        var offset = this.element.data('anim-y-offset')
        if (!offset) {
            offset = 100;
        }
        return offset;
    }
    
    PercolatorAnimationBlock.prototype.animationDuration = function(name) {
        var duration = this.element.data(name + '-duration')
        if (!duration) {
            duration = 4
        }
        return duration
    }
    
    PercolatorAnimationBlock.prototype.runBubbleAnimation = function(filename, duration, callback) {
        var thisBlock = this
        var kin = this.kin
        LoadAnimation(filename, function(circleAnimations) {
            // set drawStage method
            var realRunningTime = 0;
            var virtualRunningTime = 0;
            var lastRealTime = 0;

            kin.setDrawStage(function() {
                if (circleAnimations == null) return;

                realRunningTime = realRunningTime + (kin.getTimeInterval() * 0.001); // "Realtime" elapsed

                // Limit the "realtime" frame rate
                if ((realRunningTime - lastRealTime) < (1.0 / 15.0)) return;
                lastRealTime = realRunningTime;
                //

                virtualRunningTime = virtualRunningTime + (1.0 / 15.0);

                if (virtualRunningTime > duration) {
                    kin.stopAnimation();

                    callback()
                    return; // Done drawing
                }

                // Draw
                kin.clear();
                var context = kin.getContext();

                var yOffset = thisBlock.animationYOffset()

                for (i = 0; i < circleAnimations.length; i++) {
                    var animation = circleAnimations[i];
                    var circle = animation.frames[Math.min(animation.frames.length-1, animation.currentFrame)];

                    if (circle != null && animation.startTime < virtualRunningTime) {           
                        if (circle.radius > 1) {
                            context.fillStyle = animation.fillColor;
                            context.beginPath();
                            context.arc(circle.x, circle.y + yOffset, circle.radius, 0, Math.PI*2, true);
                            context.closePath();
                            context.fill();
                        }

                        animation.currentFrame++;
                    }
                }
            })
            kin.startAnimation()
        })
    }
    
    PercolatorAnimationBlock.prototype.animating = function() {
        return this.kin.animating || 
                this.background_after.queue().length > 0 || 
                this.background_before.queue().length > 0 ||
                this.delayTimeout
    }
    
    PercolatorAnimationBlock.prototype.stopInState = function(state) {
        this.kin.stopAnimation()
        this.kin.setDrawStage()

        if (this.background_after.queue().length > 0) {
            this.background_after.stop()
        }
        
        if (this.background_before.queue().length > 0) {
            this.background_before.stop()
        }
        
        clearTimeout(this.delayTimeout)
        this.delayTimeout = null
        
        this.state = state
    }
    
    PercolatorAnimationBlock.prototype.afterDelay = function(delay, callback) {
        clearTimeout(this.delayTimeout)
        var thisBlock = this
        this.delayTimeout = setTimeout(function() {
            thisBlock.delayTimeout = null
            callback()
        }, delay)
    }
    
    PercolatorAnimationBlock.prototype.runIntro = function() {
        this.stopInState('intro')
        
        this.background_after.css('display', 'none')

        this.background_before.css('display', 'block')
        this.background_before.css('opacity', 1)
        this.canvas.css('opacity', 0)
                
        var thisBlock = this

        this.afterDelay(initialDelay, function() {
            thisBlock.background_before.animate({opacity: 0})

            thisBlock.canvas.css('opacity', 1)
            thisBlock.runBubbleAnimation(thisBlock.element.data('intro-filename'), thisBlock.animationDuration('intro'), function() {
                thisBlock.background_after.css('display', 'block')
                thisBlock.background_after.css('opacity', 0)
                thisBlock.canvas.animate({'opacity': 0})
                thisBlock.background_after.animate({'opacity': 1}, {complete: function() {
                    thisBlock.stopInState('visible')
                    setTimeout(function() { UpdateAnimations(false); }, 1)
                }})
            })
        }) 
    }
    
    PercolatorAnimationBlock.prototype.runOutro = function(target_state) {
        this.stopInState('outro')
        
        this.background_before.css('display', 'none')
     
        this.background_after.css('display', 'block')        
        this.background_after.css('opacity', 1)
        this.background_after.animate({opacity: 0})
        this.canvas.css('opacity', 1)

        var thisBlock = this   
        this.runBubbleAnimation(this.element.data('outro-filename'), this.animationDuration('outro'), function() {
            if (!target_state) {
                target_state = 'hidden'
            }

			//Need to run this after the outro bubble animation is complete, to reset the canvas
			thisBlock.background_before.css('display', 'block')
            thisBlock.background_before.css('opacity', 0)
			thisBlock.canvas.css('opacity', 0)
			thisBlock.background_before.animate({opacity: 1})
			thisBlock.afterDelay(finalDelay, function() {
                thisBlock.stopInState(target_state)
                setTimeout(function() { UpdateAnimations(false); }, 1)
			})
        });
    }
    
    PercolatorAnimationBlock.prototype.hideImmediately = function() {
        this.stopInState('hidden')
        
        this.background_before.css('display', 'block')
        this.background_before.css('opacity', '1')
        this.canvas.css('opacity', '1')
        this.background_after.css('display', 'none')
        this.background_after.css('opacity' , '0')

        this.kin.clear()
    }

    function UpdateAnimations(triggerIdle) {
        var windowTop = $(window).scrollTop()
        var windowBottom = windowTop + $(window).height()

        var bestBlock = null
        $.each(animationBlocks, function() {
            var blockTop = this.element.offset().top
            var blockBottom = blockTop + this.element.height()
            var blockMiddle = (blockBottom + blockTop) / 2
            
            this.top = blockTop
            this.onScreen = (blockTop < windowBottom && blockBottom > windowTop)
            
            if (blockBottom < windowTop + windowTopBuffer || blockTop > windowBottom - windowBottomBuffer) {
                return true; // can't be best if we're near the top or bottom
            }

            if (bestBlock) {
                // the best block is the one that's:
                // * not near the top of the window
                // * on screen
                // * highest 
                if (bestBlock.top > this.top) {
                    bestBlock = this
                }
            } else {
                bestBlock = this
            }
        })
        
        if (bestBlock && bestBlock != focusedBlock) {
            focusedBlock = bestBlock
        }
        
        // Stop all off-screen blocks
        $.each(animationBlocks, function() {
            if (!this.onScreen) {
                this.hideImmediately()
            }
        })

        // Find which block is animating
        var activeBlock
        $.each(animationBlocks, function() {
            if (this.animating()) {
                activeBlock = this
            }
        })
        
        var shouldStartAnimation = true
        
        // If the lightbox is showing, don't start new animations
        if ($('.pp_pic_holder').length) {
            shouldStartAnimation = false
        }
        
        if (shouldStartAnimation) {
            // Start a new animation if there's no active block
            if (!activeBlock) {
                // Run an outro if needed first
                $.each(animationBlocks, function() {
                    if (this != focusedBlock && this.state == 'visible')  {
                        this.runOutro()
                        activeBlock = this
                        return false
                    }
                })
            }

            if (!activeBlock) {
                $.each(animationBlocks, function() {
                    if (this == focusedBlock && this.state == 'hidden') {
                        this.runIntro()
                        activeBlock = this
                        return false
                    }
                })
            }

            if (triggerIdle && focusedBlock) {
                focusedBlock.runOutro()
                activeBlock = focusedBlock
            }
        }
        
        if (activeBlock) {
            clearTimeout(idleTimer)
            idleTimer = null
        } else {
            if (!idleTimer) {
                idleTimer = setTimeout(function() { UpdateAnimations(true); }, idleDelay)
            }
        }
    }

    function Start() {
        $('.animBlock').each(function(index) {
            var block = new PercolatorAnimationBlock($(this))
            animationBlocks.push(block)
        })
        
        $(window).scroll(function() { UpdateAnimations(false); } )
        UpdateAnimations(false)
    }
    
    $(window).load(Start)
})();

