<canvas id="drawing_canvas"></canvas>
body {
    background-color: #000;
    margin: 0;
    overflow: hidden;
}

#drawing_canvas {
    position: absolute;
    margin: auto;
    width: 768px;
    height: 768px;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}
// canvas settings
var viewWidth = 768,
    viewHeight = 768,
    drawingCanvas = document.getElementById("drawing_canvas"),
    ctx,
    timeStep = (1/60),
    time = 0;

var nodes = [],
    signals = [];

var signalCount = 0;

window.onload = function() {
    initDrawingCanvas();
    createNodes();
    connectNodes();

    transmit();
    setInterval(transmit, 1500);

    requestAnimationFrame(loop);
};

function initDrawingCanvas() {
    drawingCanvas.width = viewWidth;
    drawingCanvas.height = viewHeight;
    ctx = drawingCanvas.getContext('2d');
}

function createNodes() {
    var rad = viewWidth * 0.5 - 10;

    for (var i = 0; i < 300; i++) {
        var q = Math.random() * (Math.PI * 2);
        var r = Math.sqrt(Math.random());
        var x = (rad * r) * Math.cos(q) + viewWidth * 0.5;
        var y = (rad * r) * Math.sin(q) + viewWidth * 0.5;

        nodes[i] = new Node(x, y);
    }
}

function connectNodes() {
    var connection,
        j,
        connectCount;

    for (var i = 0; i < nodes.length; i++) {
        j = 0;

        connectCount = Math.floor(randomRange(3, 6));

        while (j < connectCount) {
            connection = getRandom(nodes);

            if (nodes[i] !== connection) {
                nodes[i].connections.push(connection);
                j++;
            }
        }
    }
}

function transmit() {
    signals.push(new Signal(getRandom(nodes)));
    signalCount++;
}

function update() {
    nodes.forEach(function(n) {
        n.update();
    });

    signals.forEach(function(s) {
        if (s.update() === true) {
            signals.splice(signals.indexOf(s), 1);
        }
    });
}

function draw() {
    ctx.clearRect(0, 0, viewWidth, viewHeight);

    nodes.forEach(function(n) {
        n.draw();
    });

    signals.forEach(function(s) {
        s.draw();
    });
}

function loop() {
    update();
    draw();
    time += timeStep;
    requestAnimationFrame(loop);
}

function Node(x, y) {
    this.x = this._x = x;
    this.y = this._y = y;

    this.connections = [];

    this.r = randomRange(-10, 10);
}
Node.prototype = {
    update:function() {
        this.x = this._x + Math.sin(time) * this.r;
        this.y = this._y + Math.cos(time) * this.r;
    },
    draw:function() {
        ctx.strokeStyle = '#fff';
        ctx.fillStyle = '#fff';
        ctx.lineWidth = 0.05;

        ctx.fillRect(this.x, this.y, 1, 1);

        for (var i = 0; i < this.connections.length; i++) {
            ctx.beginPath();
            ctx.moveTo(this.x, this.y);
            ctx.lineTo(this.connections[i].x, this.connections[i].y);
            ctx.stroke();
        }
    }
};

function Signal(start) {
    this.start = start;
    this.parts = [];
    this.completeParts = [];
    this.strength = 4.0;
    this.jumps = 0;
    
    var tint = (signalCount % 12) * 30;
    // var tint = Math.floor(Math.random() * 360);
    console.log(tint);
    this.style = 'hsl(' + tint + ',100%,50%)';

    for (var i = 0; i < start.connections.length; i++) {
        this.parts.push(new SignalPart(this.start, this.start.connections[i], this.strength, this.style));
    }
}
Signal.prototype = {
    update:function() {
        var complete = false;
        this.completeParts.length = 0;

        for (var i = this.parts.length - 1; i >= 0; i--) {
            this.parts[i].time += timeStep;

            if (this.parts[i].complete) {
                this.completeParts.push(this.parts.splice(i, 1)[0]);
            }
        }
        
        if (this.completeParts.length > 0) {
            this.jumps++;
            this.strength--;
            complete = this.jumps === 3;
        }
      
        if (complete === false) {
            var part,
              end,
              connection;

            for (var j = 0; j < this.completeParts.length; j++) {
                part = this.completeParts[j];
                end = part.end;

                for (var k = 0; k < end.connections.length; k++) {
                    connection = end.connections[k];

                    this.parts.push(new SignalPart(end, connection, this.strength, this.style));
                }
            }
        }
      
        return complete;
    },
    draw:function() {
        for (var i = 0; i < this.parts.length; i++) {
            this.parts[i].draw();
        }
    }
};

function SignalPart(start, end, strength, style) {
    this.start = start;
    this.end = end;
    this.strength = strength;
    this.style = style;
    this._time = 0;
    this.prevTime = 0;
    this.duration = 2;
    this.complete = false;

    this.p0 = {x:0, y:0};
    this.p1 = {x:0, y:0};
}
SignalPart.prototype = {
    set time(v) {
        this.prevTime = this._time;
        this._time = v >= this.duration ? this.duration : v;
        this.complete = this._time === this.duration;
    },
    get time() {
        return this._time;
    },
    draw:function() {
        var t0 = Ease.outCubic(this.prevTime, 0, 1, this.duration);
        var t1 = Ease.outQuad(this.time, 0, 1, this.duration);
        lerp(this.start, this.end, t0, this.p0);
        lerp(this.start, this.end, t1, this.p1);

        ctx.strokeStyle = this.style;
        ctx.lineWidth = this.strength * 0.25;
        ctx.lineCap = 'round';
        ctx.beginPath();
        ctx.moveTo(this.p0.x, this.p0.y);
        ctx.lineTo(this.p1.x, this.p1.y);
        ctx.stroke();
    }
};



function randomRange(min, max) {
    return min + Math.random() * (max - min);
}

function getRandom(a) {
    return a[Math.floor(Math.random() * a.length)];
}

function lerp(n1, n2, t, p) {
    p = p || {x:0, y:0};

    p.x = n1.x + t * (n2.x - n1.x);
    p.y = n1.y + t * (n2.y - n1.y);

    return p;
}

/**
 * easing equations from http://gizma.com/easing/
 * t = current time
 * b = start value
 * c = delta value
 * d = duration
 */
var Ease = {
    inCubic:function (t, b, c, d) {
        t /= d;
        return c*t*t*t + b;
    },
    outCubic:function(t, b, c, d) {
        t /= d;
        t--;
        return c*(t*t*t + 1) + b;
    },
    inQuad: function (t, b, c, d) {
        return c*(t/=d)*t + b;
    },
    outQuad: function (t, b, c, d) {
        return -c *(t/=d)*(t-2) + b;
    },
    inOutCubic:function(t, b, c, d) {
        t /= d/2;
        if (t < 1) return c/2*t*t*t + b;
        t -= 2;
        return c/2*(t*t*t + 2) + b;
    }
};