(function () {
    d3.select('#svg').on('click', function () {
        draw('svg');
    });
    d3.select('#canvas').on('click', function () {
        draw('canvas');
    });
    if (d3.resolution() > 1) {
        d3.select('#paper').append('label').html(
            "<input id='canvas-low' name='type' type='radio'><span>canvas low resolution</span>"
        );
        d3.select('#canvas-low').on('click', function () {
            draw('canvas', 1);
        });
    }

    var color = d3.scaleSequential(d3.interpolateBlues),
        waves, particles, x, y, r, data, boat;

    draw('svg');
    d3.timer(animate);


    function draw(type, r) {

        var example = d3.select("#example"),
            width = d3.getSize(example.style('width')),
            height = Math.min(500, width);
        x = d3.scaleLinear().range([0, width]);
        y = d3.scaleLinear().range([height, 0]);

        if (!data)
            data = [0.7, 0.6, 0.4, 0.2].map(function (d, i) {
                var w = wave()
                    .radius(0.02*(i+1)*height)
                    .waveLength(0.2*(i+1))
                    .y(d);
                w.area.x(function (dd) {
                    return x(dd.x) + dd.dx;
                }).y1(function (dd) {
                    return y(dd.y) - dd.dy;
                }).y0(function () {
                    return y(0);
                });
                return w;
            });

        example.select('.paper').remove();

        var paper = example
                .append(type)
                .classed('paper', true)
                .attr('width', width).attr('height', height).canvasResolution(r).canvas(true)
                .style('stroke-width', 0.5);

        sun(paper)
            .append('rect')
            .attr('width', width)
            .attr('height', height)
            .style('fill', 'url(#sun)');

        waves = paper
            .append('g')
            .classed('waves', true)
            .selectAll('path')
            .data(data)
            .enter()
            .append('path')
            .style('stroke', 'none')
            .each(function (d) {
                d3.select(this).attr('d', d.context(null)).style('fill', color(d.y()));
            });

        var circles = paper.selectAll('g.circles')
            .data(data)
            .enter()
            .append('g')
            .classed('circles', true)
            .selectAll('circles')
            .data(function (d) {return d.points();})
            .enter()
            .append('circle')
            .attr('r', function (d) {return d.radius;})
            .style('fill', 'none')
            .style('stroke', '#666')
            .attr('cx', function (d) {return x(d.x);})
            .attr('cy', function (d) {return y(d.y);});

        particles = paper.selectAll('g.particles')
            .data(data)
            .enter()
            .append('g')
            .classed('particles', true)
            .selectAll('circles')
            .data(function (d) {return d.points();})
            .enter()
            .append('circle')
            .attr('r', 3)
            .style('fill', '#666')
            .style('stroke-width', 0)
            .attr('cx', function (d) {return x(d.x) + d.dx;})
            .attr('cy', function (d) {return y(d.y) - d.dy;});

        boat = paper.append('text')
            .text("⛵")
            .style('text-anchor', 'middle')
            .style('alignment-baseline', 'middle')
            .style("font-size", "60px");

        moveBoat();
    }

    function animate () {
        waves.each(function (d) {
            d3.select(this).attr('d', d.tick());
        });
        particles.data(function (d) {return d.points();})
            .attr('cx', function (d) {return x(d.x) + d.dx;})
            .attr('cy', function (d) {return y(d.y) - d.dy;});
        moveBoat();
    }

    function moveBoat() {
        var d = data[1].point(20);
        boat.attr("transform", "translate(" + (x(d.x) + d.dx) + ", " + (y(d.y) - d.dy) + ")");
    }

    function sun (paper) {
        paper
            .append('defs')
            .append('radialGradient')
            .attr('id', 'sun')
            .attr('cx', '70%')
            .attr('cy', '30%')
            .attr('fx', '60%')
            .attr('fy', '30%')
            .selectAll('stop')
            .data([
                {color: "#e31a1c", offset: '0%'},
                {color: '#fd8d3c', offset: '60%'}
            ])
            .enter()
            .append('stop')
            .attr('offset', function (d) {
                return d.offset;
            })
            .attr('stop-color', function (d) {
                return d.color;
            });
        return paper;
    }

    function wave() {
        var radius = 0.1,       // intensity of wave
            waveLength = 1,     // wave length
            y = 0,
            area = d3.area().curve(d3.curveNatural),
            extent = [0, 1],
            pi = Math.PI,
            cos = Math.cos,
            sin = Math.sin,
            N = 8,
            speed = 0.01,
            time = 0;

        function wave (d) {
            return area(wave.points(d));
        }

        wave.area = area;

        wave.tick = function () {
            time += 1;
            return wave;
        };

        wave.context = function (_) {
            if (!arguments.length) return area.context();
            area.context(_);
            return wave;
        };

        wave.extent = function (_) {
            if (!arguments.length) return extent;
            extent = _;
            return wave;
        };

        wave.N = function (_) {
            if (!arguments.length) return N;
            N = +_;
            return wave;
        };

        wave.waveLength = function (_) {
            if (!arguments.length) return waveLength;
            waveLength = _;
            return wave;
        };

        wave.y = function (_) {
            if (!arguments.length) return y;
            y = _;
            return wave;
        };

        wave.radius = function (_) {
            if (!arguments.length) return radius;
            radius = _;
            return wave;
        };

        wave.speed = function (_) {
            if (!arguments.length) return speed;
            speed = _;
            return wave;
        };

        wave.points = function () {
            var w = extent[1] - extent[0] + 2*waveLength,
                dx = waveLength/N,
                Nx = Math.round(w/dx) + 1;

            return d3.range(Nx).map(point);
        };

        wave.point = point;

        function point (i) {
            var da = 2*pi/N,
                dx = waveLength/N,
                x0 = extent[0] - waveLength,
                a = i*da - time*speed*pi;

            return {
                x: x0 + i * dx,
                y: y,
                angle: a,
                radius: radius,
                dx: radius * cos(a),
                dy: radius * sin(a)
            };
        }

        return wave;
    }

}());