(function(common) {
    common.element = function(name) {
        return document.getElementById(name);
    };    

    common.slice = (function(slice) {
        return function(object) {
            return slice.apply(object, slice.call(arguments, 1));
        };
    })(Array.prototype.slice);

    // Identity
    common.identity = function (x) { return x; };
    
    // Constant
    common.constant = function (x) { return function() { return x; } };

    // Curry
    common.curry = function(f) {
        var args = common.slice(arguments, 1);
        return function() {
            return f.apply(this, args.concat(common.slice(arguments, 0)));
        };
    };

    common.format = function(string) {
        var args = common.slice(arguments, 1);
        return string.replace(/{(\d+)}/g, function(match, i) {
            return (typeof args[i] == 'undefined' && '{' + i + '}')  || args[i];
        });
    };
    
    common.time = function() {
        return (new Date().valueOf() * 0.001) >> 0;
    };
    
    common.wiggle = function(array, type) {
        var count = {};
        var p = function(ta, tb) {
            return count[ta] / (count[ta] + count[tb]); 
        };
        
        array.map(function(a) {
            var t = type(a);
            if (t in count)
               ++count[t];
            else
	       count[t] = 1;
        });
        
        var i, j, ta, tb, temp;
        for (i = 0; i < array.length; ++i) {
            ta = type(array[i]);
            if (!ta)
                continue;
            for (j = i + 1; j < array.length; ++j) {
                tb = type(array[j]);
                if (!tb)
                    continue;
                if (ta != tb && p(ta, tb) > Math.random()) {
                    temp = array[i];
                    array[i] = array[j];
                    array[j] = temp;
                    i = j + 1;
                    break;
                }
            }
        } 
        return array;
    };
})(window.common = window.common || {});

// TODO
//
// Add feed filter window:
// - add 'clear' button to filter window (easier)
//
// Close button on contact, instructions.
(function(core, ballpool, stream, common, undefined) {
    var clicked, radius;
    var queue = [], toss = false, clicks = 4, add = 5;
    
    var initial = (common.mobile? 10 : 20);
    var wiggle = common.wiggle;
    
    var width, height;
    var parent = common.element('item');
    var children = ['title', 'content', 'feed', 'age'];
    
    ballpool.size = function(label) {
        var k = (common.mobile? .5 : 1); 
        return k * ((Math.random() * 80 >> 0) + 40);
    };
    
    // Mouse interactions
    
    ballpool.dragstart = function(x, y) {
        if (ignore(x, y))
            return false;      
        ballpool.drag = function(x, y) {
            if (ignore(x, y)) 
                return true;
            if (queue.length < 5) 
                stream.prev(10);
            if (queue.length > 0 && bool(0.75)) {
                if (--add == 0)
                    instructions();
                ball(queue.shift(), x, y);
            }
            return true;
        };
        return true;
    };
    
    ballpool.dragend = function() {
        ballpool.drag = common.constant(false);
    };
    
    ballpool.circles = common.constant(0);

    ballpool.click = function (x, y, guid) {
        if (ignore(x, y))
            return true;
        if (guid)
            info(clicked != guid && guid);
        return false;
    };
    
    ballpool.doubleclick = function(x, y, guid) {
        if (ignore(x, y))
            return;
        if (!guid)
            ballpool.reset();
    };

    ballpool.color = function(guid) {
        return { 
            'lastfm': ['#a84dd5', '#E3E3E3'],
            'reddit': ['#0099cc', '#FF4500'],
            'youtube': ['#FF0000'],
            'vimeo': ['#ffcc00', '#86c9ef'],
            'vimeolikes': ['#ffcc00', '#86c9ef'],
            'github': ['#78a5e4', '#fff2c0'],
            'flickr': ['#0063DC', '#ff5db1'],
            'soundcloud': ['#FF6600', '#aaaaaa'],
            'wikipedia': ['#15c691', '#eeeeee']
        }[stream.items[guid].feed];
    };

    ballpool.move = function (guid, x, y) {
        if (clicked != guid)
            return;
        info(x, y);
    };

    
    stream.callback = function (item) {
        if (initial && initial-- > 0)
            ball(item);
        else {
            queue.push(item);
            if (queue.length > 20) {
                queue = wiggle(queue, function(item) { return item.feed; });
                wiggle = common.identity;
            }
        }
    };

    function move(w, h, r, x0, y0) {
        var x2 = document.width;
        var y2 = 0;
        
        var x1 = x0 + 1.60 * r;
        var y1 = y0 - 0.95 * h;
        
        if (x1 + w >= x2)
            x1 -= 2 * (x1 - x0) + w;
        
        if (y1 <= y2)
            y1 += (y0 - y1);
        if (y1 + h >= document.innerHeight)
            y1 -= h;
         
        position(parent, x1, y1);
        return true;
    };

    core.move = common.constant(false);

    function info(x, y) {
        if (!x) {
            // Remove info window
            children.map(function(c) { hide(c, null); });
            hide(parent);
            clicked = null;
            if (typeof toss == 'number')
                toss = false;
            core.move = common.constant(false);
            return;
        }
        else if (x && !y) {
            clicked = x;
            var item = stream.items[clicked];

            parent.className = item.feed;
            item.age = age(item.date);          
            children.map(function(c) {
                var value = item[c];
                if (!value)
                    hide(c, null);
                else 
                    show(c, value);
            });        

            if (--clicks == 0)
                instructions();
            if (typeof toss == 'number')
                toss = false;
            show(parent);
            return;
        }
        if (toss === false)
            toss = x;
        else if (typeof toss == 'number' && Math.abs(toss - x) > 100) {
            toss = true;
            instructions();
        }        
        if (!core.move(x, y))
            // Curry the move function to reduce calls to .client{Width,Height}
            (core.move = common.curry(move, parent.clientWidth, 
                                            parent.clientHeight,
                                            ballpool.radius(clicked)))(x, y);
    }
    
    var ignore = common.curry(function(a, b, c, x, y) {
        return hovering(a, x, y) || hovering(b, x, y) || hovering(c, x, y);
    }, common.element('item'), common.element('instructions'), 
       common.element('contact'));
    
    function location(object) {
        var x = y = 0;
        if (object.offsetParent) {
                x = object.offsetLeft;
                y = object.offsetTop;
                while (object = object.offsetParent) {
                        x += object.offsetLeft;
                        y += object.offsetTop;
                }
        }
        return [x, y];
    }
    
    function hovering(div, x, y) {
        if (!div || div.style.display === 'none')
            return false;
        var x0, y0, x1, y1;
        if (div.style.left && div.style.top) {
            x0 = parseInt(div.style.left);
            y0 = parseInt(div.style.top);
        }
        else {
            var pos = location(div);
            x0 = pos[0];
            y0 = pos[1];
        }
        x1 = x0 + div.clientWidth;
        y1 = y0 + div.clientHeight;
        return (x0 <= x && x <= x1) && (y0 <= y && y <= y1);        
    }
    
    function position(element, x, y) {
        element.style.left = (x >> 0) + 'px';
        element.style.top = (y >> 0) + 'px';
    }
    
    function display(object, mode, html) {
        if (typeof object == 'string')
            object = common.element(object);
        if (!object)
            return;
        object.style.display = mode;
        if (typeof html != 'undefined')
            object.innerHTML = html;
    }
    
    var hide = function(element, html) {
        display(element, 'none', html);
    };
    
    var show = function(element, html, mode) {
        display(element, mode || 'block', html);
    };
    
    function ball(item, x, y) {
        ballpool.create(item.guid, x, y);
    }

    function bool(probability) {
        return Math.random() < (probability || 0.5);
    }
    
    function age(seconds) {
        var string, date;
        var a = common.time() - seconds;
        date = new Date(seconds * 1000);
        
        var plural = function(n) { return (n > 1? 's': ''); };
        
        if (a <= 60*60) {
            var minutes = Math.floor(a / 60);
            string = common.format('{0} minute{1} ago', 
                                   minutes, plural(minutes));
        }
        else if (a < 24*60*60) {
            var hours = Math.floor(a / (60*60)); 
            string = common.format('{0} hour{1} ago', hours, plural(hours));
        }
        else {            
            var month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 
                         'Jun', 'Jul', 'Aug', 'Sep', 'Oct',
                         'Nov', 'Dec'][date.getMonth()];
            string = common.format("{1} {0}", month, date.getDate());
	    if (a > 365*24*60*60)
	        string = common.format('{0} {1}', string, date.getFullYear());
        }
        return common.format('<time title="{0}" datetime="{1}">{2}</time>',
                             date.toGMTString(), date.toISOString(), string);
    }
    
    var instructions = function() {
        var t = clicks + add;
        if (toss === true)
            hide('toss', null);
        if (clicks <= 0)
            hide('info', null);
        if (add <= 0)
            hide('add', null);
        if (t <= 0 && toss == true) {
            hide('instructions', null);
            instructions = common.constant();
        }
    };
    
    core.init = function() {
        stream.init((core.mobile && 30) || 60, function() {
            ballpool.init('canvas');
            ballpool.play();
        });
        events.map(function(e) {
             document.removeEventListener(e, core.init, true);   
        });
        common.element('close').addEventListener('click', 
                                                 function() { info(false); });
        delete(events);
        delete(core.init);
    };
 
    var events = ['DOMContentLoaded', 'load'];    
    events.map(function(e) { document.addEventListener(e, core.init, true); });
})(window.core = window.core || {}, window.ballpool, window.stream, common);
	


