var Crowd = function() {

    var p5; // set in sketchProc
    var id; // set in init()
     
    var theBundleData;	
		    
    var bubbles = [];
    var allBubbles = [];
   
    var theImages = [];	 
    var active = false;
    var highlight = '';
    var firstCreationTime = 0;
    var lastCreationTime = 0;

    var prevMouseParent = null;
    var prevMouseChild = null;

    var selectedParent = null;
    var selectedChild = null;
    var tx = 0;
    var ty = 0;
    var sc = 1;

    window.sc = function() { if (arguments.length == 1) { sc = arguments[0] } else { return sc } };
    
    function Bubble(id, text, email, name, twitter) {
        this.id = id;
        this.text = text;
	this.email = email;
	this.name = name;
        this.twitter = twitter;
	this.x = this.y = this.vx = this.vy = 0;
        this.children = []; // sub-bubbles
        this.creationTime = p5.millis();
        this.lock = false;
        this.c = p5.color(0);
    }
    

     function ImageObj(email,image){
        this.email = email;
        this.image = image;
    }	


    function RadiusBounds(r) {
        return function(p) {
            var currentR = p5.mag(p.x,p.y);
            var maxR = r-p.r;
            if (currentR > maxR) {
                var newR = currentR + (maxR-currentR)/10.0;
                var a = p5.atan2(p.y,p.x);
                var scaleFactor = 0.5;
                p.vx += ((newR * p5.cos(a))-p.x)/scaleFactor;
                p.vy += ((newR * p5.sin(a))-p.y)/scaleFactor;
            }
        }
    } 
   
    	
	 
    function RectBounds(x, y, w, h) {
        return function(p) {
            if (p.x < x+p.r) {
                p.vx += (x+p.r-p.x)/20.0;
            }
            else if (p.x > x+w-p.r) {
                p.vx -= (p.x-(x+w-p.r))/20.0;
            }
            if (p.y < y+p.r) {
                p.vy += (y+p.r-p.y)/20.0;
            }
            else if (p.y > y+h-p.r) {
                p.vy -= (p.y-(y+h-p.r))/20.0;
            }
        }
    }  
    
    function addBubble(data, parentId) {
        if (parentId == null) {
            var p = new Bubble(data.id, data.text);
            p.r = data.size;
            p.x = p5.random(p.r, p5.width-p.r*2);
            p.y = p5.random(30+p.r, p5.height-50-p.r*2);
            p.c = data.color || p5.color(textHue(data.text),50,50);
	    bubbles.push(p);
            allBubbles.push(p);
        }
        else {
            var parent = getBubble(parentId);
            if (parent == null) {
                return false;
            }
            var bubble = new Bubble(data.id, data.text, data.email, data.name, data.twitter);
            allBubbles.push(bubble);
            if (data.time > 0) {
                bubble.creationTime = data.time;
            }
            if (firstCreationTime == 0) {
                firstCreationTime = bubble.creationTime;
                lastCreationTime = bubble.creationTime;
            }
            else {
                firstCreationTime = p5.min(firstCreationTime, bubble.creationTime);
                lastCreationTime = p5.max(lastCreationTime, bubble.creationTime);
            }
            bubble.r = data.size;
            var r = p5.random(parent.r - bubble.r);
            var a = p5.random(p5.TWO_PI);
            bubble.x = r * p5.cos(a);
            bubble.y = r * p5.sin(a);
            var bubbleHue = p5.constrain(p5.hue(parent.c)+p5.random(-20,20),0,255);
            var bubbleHue = p5.hue(parent.c);
            bubble.c = data.color || p5.color(bubbleHue, 100, 255);
            parent.children.push(bubble);
        }
    }
        
    function getBubble(id) {
        for (var i = 0; i < bubbles.length; i++) {
            var bubble = bubbles[i];
            if (bubble.id == id) {
                return bubble;
            }
        }
        // TODO: search children
        return null;
    }
  
     function getImage(email) {
        for (var i = 0; i < theImages.length; i++) {
            var theImage = theImages[i];
            if (theImage.email == email) {
                return theImage;
            }
        }
        return null;
    }	  
    

    function textHue(name) {
        var total = 0;
        for (var i = 0; i < name.length; i++) {
            total += name.charCodeAt(i);
        }
        return total % 255;
    }
        
    function circlePack(particles, bounds, attractX, attractY) {
        var len = particles.length;
        for (var i = 0; i < len-1; i++) {
            var p1 = particles[i];
            for (var j = i+1; j < len; j++) {
                var p2 = particles[j];
                var d = p5.dist(p1.x,p1.y,p2.x,p2.y);
                var minD = p1.r+p2.r;
                var tightness = 1.05; // 0.95 gives tight packing, 1.1 gives a border                
                if (d < minD * tightness) {
                    var f = (1.0 - p5.sqrt(d/(minD*tightness)));
                    //var f = 1.0/50.0;
                    var dx = (p2.x-p1.x)*f;
                    var dy = (p2.y-p1.y)*f;
                    // XXX hack for collisions when dragging
                    if (p2.lock) {
                        p1.vx -= dx*2;
                        p1.vy -= dy*2;
                    }
                    else if (p1.lock) {
                        p2.vx += dx*2;
                        p2.vy += dy*2;
                    }
                    else {
                        p1.vx -= dx/18;
                        p1.vy -= dy/18;
                        p2.vx += dx/18;
                        p2.vy += dy/18;
                    }
                }
            }
        }
        for (var i = 0; i < len; i++) {
            var p = particles[i];
            bounds(p);
            if (p.lock) { 
                p.vx = p.vy = 0;
                continue;
            }        
            // move to attraction point
            p.vx += (attractX - (p.x))/1000.0;
            p.vy += (attractY - (p.y))/1000.0;
            // apply velocity
            p.x += p5.constrain(p.vx/(1.0+p.children.length), -100.0, 100.0);
            p.y += p5.constrain(p.vy/(1.0+p.children.length), -100.0, 100.0);
            // drag for next frame
            p.vx *= 0.95;
            p.vy *= 0.95;
        }
        // now apply to children
        for (var i = 0; i < len; i++) {
            var p = particles[i];
            circlePack(p.children, RadiusBounds(p.r), 0, 0);
        }    
    }        
    
    function applyWorld() {
        p5.translate(p5.width/2,p5.height/2);
        p5.scale(sc);
        p5.translate(-p5.width/2,-p5.height/2);
        p5.translate(tx,ty);    
    }
    
    function worldX(x) {
        x -= p5.width/2.0;
        x /= sc;
        x += p5.width/2.0;
        x -= tx;
        return x;
    }

    function worldY(y) {
        y -= p5.height/2.0;
        y /= sc;
        y += p5.height/2.0;
        y -= ty;
        return y;
    }
    
    function sketchProc(processing) {

        p5 = processing;
                    
        var startMillis;
        var font, bfont, theImage;
       	var loopCounter = 0;
	var online; 

        p5.setup = function() {
            
	   	
	    /* @pjs preload="http://a0.twimg.com/profile_images/1267700018/Jamie_Sketch_bigger.jpg"; */ 	
	    /* @pjs preload="http://images.crowdscanner.com/artist.png"; */ 
	
	    var url = "http://images.crowdscanner.com/artist";
  	    online = p5.loadImage(url, "png");
	
	    var widget = document.getElementById('widget');
            p5.size(widget.offsetWidth, 700);
             
            p5.frameRate(30);
            p5.background(255);
            
            font = p5.loadFont("Helvetica");
            bfont = p5.loadFont("Helvetica-Bold");
            
            p5.colorMode(p5.HSB);
            
            bubbles = [];
            allBubbles = [];
            
            selectedParent = null;
            selectedChild = null;
            tx = 0;
            ty = 0;
            sc = 1;            
            
            firstCreationTime = 0;
            lastCreationTime = 0;
            
            startMillis = p5.millis();
            var numOfAnswers = theBundleData.allanswers.length;
	    for (var j = 0; j<numOfAnswers; j++){

                for(var r=0; r<theBundleData.allanswers[j].matchProfiles.length; r++){
                        var theImage = p5.loadImage(theBundleData.allanswers[j].matchProfiles[r].url);
                        var imageObject = new ImageObj(theBundleData.allanswers[j].matchProfiles[r].email, theImage);
                        theImages.push(imageObject);
            	}
	    }	
	}
                
        p5.draw = function() {
            p5.colorMode(p5.RGB);
            p5.background(239,234,224);
            p5.colorMode(p5.HSB);
            if (!active) {
                if(loopCounter < theBundleData.allanswers.length) {
                    var theAnswer = decodeURIComponent(theBundleData.allanswers[loopCounter].encodedTextualAnswer);
		    theAnswer = theAnswer.replace(/\+/g, " ");
		    var parent = { id: loopCounter, text: theAnswer, size: p5.random(50.0, 50.0) };
                    addBubble(parent);
		    //var numPosts = p5.floor(p5.random(2,5));
		    for (var i = 0; i < theBundleData.allanswers[loopCounter].matchProfiles.length ; i++) {
    		        var bubble = { id: allBubbles.length, text: "(example bubble)", size: p5.random(38.0, 38.0),email:theBundleData.allanswers[loopCounter].matchProfiles[i].email, name:theBundleData.allanswers[loopCounter].matchProfiles[i].name, twitter: theBundleData.allanswers[loopCounter].matchProfiles[i].username};
			var loopTwo = loopCounter + 0.1;		                	 
                    	addBubble(bubble, loopCounter);
                    }
		loopCounter++;
                }
            }
            
	    p5.pushMatrix();
            applyWorld();
            var mouseParent = null;
            var mouseChild = null;
            p5.noStroke();
            for (var i = 0; i < bubbles.length; i++) {
                var parent = bubbles[i];
                if (mouseParent == null && p5.dist(parent.x,parent.y,worldX(p5.mouseX),worldY(p5.mouseY)) < parent.r) {
                    mouseParent = parent;
                    mouseParent.lock = true;
                
		}
                else {
                    parent.lock = false;
                }
                if (active) {
                    p5.noStroke();
                    if (parent.lock) {
                        p5.fill(parent.c);
                        p5.ellipse(parent.x,parent.y,parent.r*2,parent.r*2);
                    }
                    p5.fill(parent.c,150);
                }
                else {
                    //here is the colour of the outer bubbles
		    p5.colorMode(p5.RGB);
		    p5.fill(171,199,49);
		    p5.smooth();
		    p5.strokeWeight(4);
                    //p5.noStroke();
		    p5.stroke(255);
                }
		// Here is where outer parent bubbles gets drawn                
                p5.ellipse(parent.x,parent.y,parent.r*2,parent.r*2);
		//p5.image(online, parent.x-50, parent.y-50,100,100);
		// here is where we write the text in an arc
		
		p5.textSize(16);
        	var textLength = p5.textWidth(parent.text);
		p5.fill(51,51,51);
		var answer = parent.text;
                //p5.text(parent.text, parent.x-textLength/2,parent.y-parent.r+20);
		p5.pushMatrix();
		p5.textAlign(p5.CENTER);
		//textAlign="center";
		p5.smooth();
		p5.translate(parent.x, parent.y);
		var arclength = 0;
		// For every box
  		for (var t = 0; t<answer.length; t++){
    
    			// The character and its width
    			var currentChar = answer.charAt(t);
    			// Instead of a constant width, we check the width of each character.
    			var w = p5.textWidth(currentChar) ;
    			// Each box is centered so we move half the width
    			arclength += w/2;
    			//2PI*r = perimeter of circle... we want to start at the 12 oclock, minus half the text length in radians			
			var startPoint =  4.116;
			
    			// Angle in radians is the arclength divided by the radius
  			// Starting on the left side of the circle by adding PI
    			var theta = startPoint + arclength / (parent.r-18);
    
   			p5.pushMatrix();
    
    			p5.translate((parent.r-18)*p5.cos(theta), (parent.r-18)*p5.sin(theta)); 
    			// Rotate the box (rotation is offset by 90 degrees)
    			p5.rotate(theta + 3.1416/2); 
    
    			// Display the character
    			p5.fill(51,51,51);
    			p5.text(currentChar,-0.2,-0.2);
   			p5.fill(51,51,51);
                        p5.text(currentChar,0,0);
 
  			p5.popMatrix();
    
   			// Move halfway again
    			arclength += w/2;
  		}
		p5.popMatrix();
		
		var maxR = 0;
                for (var j = 0; j < parent.children.length; j++) {
                    var bubble = parent.children[j];
                    if (mouseParent == parent && mouseChild == null && p5.dist(parent.x+bubble.x,parent.y+bubble.y,worldX(p5.mouseX),worldY(p5.mouseY)) < bubble.r) {
                        mouseChild = bubble;
                        mouseChild.lock = true;
			if(p5.mousePressed){
				p5.textSize(11);
				p5.fill(51,51,51);
				var twitterName = bubble.twitter;
				var theName = bubble.name;
				var twitterLength = p5.textWidth(twitterName);
				var nameLength = p5.textWidth(theName);
				p5.text(theName, parent.x+bubble.x, parent.y+bubble.y+36);	
				p5.text(twitterName, parent.x+bubble.x, parent.y+bubble.y+47);		
		
			}	
                    }
                    else {
                        bubble.lock = false;
                    }            
                    if (active) {
                        if (selectedChild == bubble) {
                            p5.fill(255);
                        }                    
                        else if(bubble.lock) {
                            p5.fill(p5.hue(bubble.c),p5.max(0,p5.saturation(bubble.c)-40),p5.min(255,p5.brightness(bubble.c)+40));
                        }
                        else {
                            var ageMult = (bubble.creationTime - firstCreationTime) / (lastCreationTime - firstCreationTime);
                            ageMult = 0.45 + (0.55 * ageMult);
                            var bright = p5.brightness(bubble.c) * ageMult;
                            p5.fill(p5.hue(bubble.c), p5.saturation(bubble.c), bright);
                        }
                        p5.noStroke();
                    }
                    else {
                        //here is the color of the inner bubbles
			p5.fill(255);
                        p5.strokeWeight(4);
			p5.stroke(255);
                    }
		    p5.rectMode(p5.CENTER);
		    p5.rect(parent.x+bubble.x,parent.y+bubble.y,45,45);
                    maxR = p5.max(maxR, p5.mag(bubble.x,bubble.y)+bubble.r);
                    var imageObj = getImage(bubble.email);
		    p5.rectMode(p5.CENTER);
		    p5.image(imageObj.image,parent.x+bubble.x-45/2,parent.y+bubble.y-45/2, 45, 45);
		
		}
                if (maxR) {
                    if (parent.children.length == 1) {
                        maxR += 2;
                    }
                    var tightness = 1.1; // 0.95 gives tight packing, 1.1 gives a border
                    parent.r += ((maxR*tightness)-parent.r) / 5.0;
                }
            }
            circlePack(bubbles, RectBounds(0,0,p5.width,p5.height), p5.width/2, p5.height/2);
        
            if (active && jQuery) {
                if (mouseParent != prevMouseParent && prevMouseParent) {
                    $('#fizz').trigger('bubbleHoverOut', [ prevMouseParent.id ]);
                }
                if ((mouseParent != prevMouseParent || mouseChild != prevMouseChild) && mouseParent) {
                    var args = [ mouseParent.id, mouseChild ? mouseChild.id : null, p5.mouseX, p5.mouseY ];
                    $('#fizz').trigger('bubbleHoverOver', args);
                }
            }
            
            prevMouseParent = mouseParent;
            prevMouseChild = mouseChild;

            if (prevMouseParent) {
                p5.cursor(p5.HAND);
            }
            else {
                p5.cursor(p5.ARROW);
            }

            p5.popMatrix();
        }
        p5.mouseDragged = function() {
            //else
            if (selectedParent && selectedParentOffset) {
                selectedParent.x = p5.mouseX + selectedParentOffset.x;
                selectedParent.y = p5.mouseY + selectedParentOffset.y; 
                if (!$(document.body).hasClass('dragging')) {
                    $(document.body).bind('selectstart', function() { return false; });
                    $(document.body).addClass("dragging");
                }
            }
        }
        p5.mouseReleased = function() {
            $(document.body).removeClass("dragging");        
            $(document.body).unbind('selectstart');
        }
        var selectedParentOffset = null;
        p5.mousePressed = function() {
            if (prevMouseParent) selectedParent = prevMouseParent;
            else selectedParent = null;
            if (prevMouseChild) selectedChild = prevMouseChild;
            else selectedChild = null;
            if (selectedParent) {
                selectedParentOffset = {
                    x: selectedParent.x - p5.mouseX,
                    y: selectedParent.y - p5.mouseY
                };
            }
            if (active && jQuery) {
                if (prevMouseParent) {
                    var args = [ prevMouseParent.id, prevMouseChild ? prevMouseChild.id : null, p5.mouseX, p5.mouseY ];
                    $('#fizz').trigger('bubbleClick', args);
                }
                else {
                    var args = [ null, null, p5.mouseX, p5.mouseY ];
                    $('#fizz').trigger('bubbleClick', args);
                }
            }
        }
    }

    
    function init(canvasId, bundleData) {
        id = canvasId;
	theBundleData = bundleData;
	var canvas = document.getElementById(id);
        var p5 = new Processing(canvas, sketchProc);
        var prevSize = { w: canvas.width, h: canvas.height };
        if (window.addEventListener) {
            window.addEventListener('resize', function() {
                var widget = canvas.parentNode;
                if (widget && prevSize.w != widget.offsetWidth || prevSize.h != widget.offsetHeight) {
                    p5.size(widget.offsetWidth, widget.offsetHeight);
                    p5.draw();
                    prevSize = { w: widget.offsetWidth, h: widget.offsetHeight };
                }
            }, false);
        }
    }
    
    return {
        init: init,
        addBubble: addBubble,
        getBubble: getBubble,
        active: function(b) { if (arguments.length > 0) { active = b; p5.setup(); } else { return active; } },
        highlight: function(s) { if (arguments.length > 0) { highlight = s; } else { return highlight; } },
        timeRange: function() { return [ firstCreationTime, lastCreationTime ] },
        p5: function() { return p5; }
    }

}();

