// Canvas Kaleidoscope -- Kent Brewster, 2009
// http://kentbrewster.com/collide-o-scope
(function (w, d) {
   var trueName = 'COLLIDE_O_SCOPE';
   w[trueName] = {};
   var $ = w[trueName];
   $.d = d;
   $.w = w;
   $.f = function() { 
      return {   
         behavior : function() {
           // an array to hold all circles
            $.v = [];
            // initial make and draw
            for (var i = 0; i < $.a.count; i++) {
               $.f.makeCircle(i);
            }
            // we'll try for 30 frames per second
            setInterval(trueName + ".f.draw()", 33);   
         },
         makeCircle : function(i, x, y) {
           // each circle has its own object member of the master $.v array
         $.v[i] = {};
               // initial X location 
               if (!x) { 
                  // random
                  $.v[i].x = $.f.getRnd($.a.width);
               } else {
                  // this circle was added by viewer click
                  $.v[i].x = x;
               }
               // initial X vector, 1 to 6 pixels per turn
               $.v[i].dx = $.f.getRnd(6, 1);
               // initial Y location
               if (!y) {
                  // random
                  $.v[i].y = $.f.getRnd($.a.height);
               } else { 
                  // this circle was added by viewer click
                  $.v[i].y = y;
               }
               // initial Y vector, 1 to 6 pixels per turn
               $.v[i].dy = $.f.getRnd(6, 1);     
               // number of rings in this circle         
               var n = $.f.getRnd($.a.addSize, $.a.baseSize);
               // array to hold rings
               $.v[i].c = [];
               for (var j = 0; j < n; j++) {
                  // object to hold colors for each ring
                  $.v[i].c[j] = {}; 
                  // red
                  $.v[i].c[j].r = $.f.getRnd($.a.maxColor); 
                  // green
                  $.v[i].c[j].g = $.f.getRnd($.a.maxColor); 
                  // blue
                  $.v[i].c[j].b = $.f.getRnd($.a.maxColor); 
               }    
         },
         getRnd: function(r, m) {
           // no range? use 1
           if (!r) { r = 1; }
           // no minimum? use 0
           if (!m) { m = 0; }
           // return a random number between 0 and the specified range, plus the minimum, rounded down
           return Math.floor(Math.random() * r) + m;
         },
         draw: function() {
           // set our background color
            $.c.fillStyle = 'rgba(' + $.a.backgroundRed + ',' + $.a.backgroundGreen + ',' + $.a.backgroundBlue + ',' + $.a.backgroundAlpha + ')';
            // draw our background
            $.c.fillRect(0,0,$.a.width,$.a.height);  
            // get length of circle array
            var n = $.v.length;
            // draw visible circles
            for (var i = $.a.firstCirc; i < n; i++) {
               var g = $.a.grid * 5;
               var ix = parseInt($.v[i].x / g, 10);
               var iy = parseInt($.v[i].y / g, 10);
               // skanky punk-rock collision detection
               for (var j = $.a.firstCirc; j < n; j++) { 
                  if (i !== j) {
                     var jx = parseInt($.v[j].x / g, 10);
                     var jy = parseInt($.v[j].y / g, 10);
                     // are both numbers within g pixels of each other?
                     if (ix === jx && iy === jy) {
                        // change directions
                        $.v[i].dx = $.f.getRnd(6, 1);   
                        $.v[i].dy = $.f.getRnd(6, 1);   
                        $.v[j].dx = $.f.getRnd(6, 1);   
                        $.v[j].dy = $.f.getRnd(6, 1);   
                     }
                  }
               }
               // offscreen? wrap around
               $.v[i].x = ($.v[i].x + $.v[i].dx) % $.a.width;
               $.v[i].y = ($.v[i].y + $.v[i].dy) % $.a.height;
               // draw all rings in this circle                     
               for (var k = 1; k < $.v[i].c.length; k++) {
                  $.f.drawRings({
                     "x":$.v[i].x, 
                     "y":$.v[i].y, 
                     "z":(k * ($.a.grid/2) - 5), 
                     "r":$.v[i].c[k].r, 
                     "g":$.v[i].c[k].g, 
                     "b":$.v[i].c[k].b,
                     "a":$.a.ringAlpha
                  });
               }
            }
         },
         drawRings : function(c) {      
            $.c.fillStyle = 'rgba(' + c.r + ',' + c.g + ',' + c.b + ',' + c.a + ')';   
            // draw all eight possible reflections of this circle            
            $.f.circ(c.x, c.y, c.z);
            $.f.circ($.a.width - c.x, c.y, c.z);
            $.f.circ(c.x, $.a.height - c.y, c.z);
            $.f.circ($.a.width - c.x, $.a.height - c.y, c.z);
            $.f.circ(c.y, c.x, c.z);
            $.f.circ($.a.width - c.y, c.x, c.z);
            $.f.circ(c.y, $.a.height - c.x, c.z);
            $.f.circ($.a.width - c.y, $.a.height - c.x, c.z);
         },
         circ : function(x, y, r) {
            $.c.beginPath();
            // 0 = start, $.a.p = 2 x pi, for full circle
            $.c.arc(x, y, r, 0, $.a.p, true);
            $.c.closePath();
            $.c.fill();    
         },
         parseJson : function(json) {
            this.parseJson.data = json; 
            if ( typeof json !== 'string') {
             return {"err":"trying to parse a non-string JSON object"}; 
           } 
           try { 
             var f = Function(['var alert,document,top,self,window,parent,Number,Date,Object,Function,', 'Array,String,Math,RegExp,Image,ActiveXObject;', 'return (' , json.replace(/<\!--.+-->/gim,'').replace(/\bfunction\b/g,'function&shy;') , ');'].join('')); 
             return f(); 
          } catch (e) { 
            return {"err":"trouble parsing JSON object; running with defaults"}; 
         }
         },
         up : function(v) {
            var e = v || window.event;
            var t =  $.v.length;
            // starting X and Y are where you clicked
            $.f.makeCircle(t, e.clientX - $.x, e.clientY - $.y);
            // keep the number of circles on the screen constant
            $.a.firstCirc++;
         },
         houseKeep: function(s){
            // arg store
            $.a = {};
            // were any args sent from the script?
            if (s.innerHTML) { 
               $.a = $.f.parseJson(s.innerHTML); 
            }
            // load some defaults
            var defaults = {
               "p":Math.PI * 2,
               "height":400,
               "width":400,
               "count":10,
               "addSize":5,
               "baseSize":4,
               "grid":15,
               "maxColor":256,
               "backgroundRed":0,
               "backgroundGreen":0,
               "backgroundBlue":0,
               "backgroundAlpha":1,
               "ringAlpha":".1"
            };           
            for (var d in defaults) { 
               if ($.a[d] === undefined) { 
                  $.a[d] = defaults[d]; 
               } 
            }
            // initially we draw all the circles
            $.a.firstCirc = 0;
            // make our canvas
            var c = $.d.createElement("CANVAS");
            c.id = trueName;
            c.height = $.a.height;
            c.width = $.a.width;
            s.parentNode.insertBefore(c, s);
            $.f.getCanvasPosition(c);
            $.c = c.getContext('2d');
            // if the viewer clicks, do something
            c.onmouseup = $.f.up;

            s.parentNode.removeChild(s);
            // if the viewer resizes the window, re-check to get canvas position
            $.w.onresize = function(){
               $.f.getCanvasPosition($.d.getElementById(trueName));
            };
            $.f.behavior();
         },
         getCanvasPosition : function(p) {
            $.x = 0;
            $.y = 0;
            do {
               $.x += p.offsetLeft;
               $.y += p.offsetTop;
               p = p.offsetParent;
            } while (p.offsetParent);    
         },
         init : function(thisScript) {
            var s = d.getElementsByTagName('SCRIPT');
            for (var i = 0; i < s.length; i++) { 
               if (s[i].src.match(thisScript)) { 
                  $.f.houseKeep(s[i]); 
                  break; 
               } 
            }
         }
      };
   }();
   var thisScript = /collide.js$/;
   if(typeof w.addEventListener !== 'undefined') {
      w.addEventListener('load', function() { $.f.init(thisScript); }, false);
   } else if(typeof w.attachEvent !== 'undefined') {
      w.attachEvent('onload', function() { $.f.init(thisScript); });
   }
})(window, document);

