const noise = require('./fizzy-noise');

function fizzyText(message) {
    const that = this;

    // These are the variables that we manipulate with gui-dat.
    // Notice they're all defined with "this". That makes them public.
    // Otherwise, gui-dat can't see them.

    this.growthSpeed = 0.8;       // how fast do particles change size?
    this.minSize = 1;
    this.maxSize = 4;          // how big can they get?
    this.noiseStrength = 10;      // how turbulent is the flow?
    this.speed = 0.4;             // how fast do particles move?
    this.displayOutline = false;  // should we draw the message as a stroke?
    this.framesRendered = 0;

    // //////////////////////////////////////////////////////////////

    const _this = this;

    const width = 550;
    const height = 200;
    const textAscent = 101;
    const textOffsetLeft = 80;
    const noiseScale = 300;
    const frameTime = 30;

    const colors = ['#000000', '#1A1A1A', '#163C50', '#205A79', '#2A78A2'];

    // This is the context we use to get a bitmap of text using
    // the getImageData function.
    const r = document.createElement('canvas');
    const s = r.getContext('2d');

    // This is the context we actually use to draw.
    const c = document.createElement('canvas');
    const g = c.getContext('2d');

    r.setAttribute('width', width);
    c.setAttribute('width', width);
    r.setAttribute('height', height);
    c.setAttribute('height', height);

    // Add our demo to the HTML
    document.getElementById('fizzytext').appendChild(c);

    // Stores bitmap image
    let pixels = [];

    // Stores a list of particles
    const particles = [];

    // Set g.font to the same font as the bitmap canvas, incase we
    // want to draw some outlines.
    s.font = g.font = '800 82px monospace, monospace';

    // Instantiate some particles
    for (let i = 0; i < 1000; i++) {
        particles.push(new Particle(Math.random() * width, Math.random() * height));
    }

    // This function creates a bitmap of pixels based on your message
    // It's called every time we change the message property.
    const createBitmap = function (msg) {
        s.fillStyle = '#fff';
        s.fillRect(0, 0, width, height);

        s.fillStyle = '#222';
        s.fillText(msg, textOffsetLeft, textAscent);

        // Pull reference
        const imageData = s.getImageData(0, 0, width, height);
        pixels = imageData.data;
    };

    // Called once per frame, updates the animation.
    const render = function () {
        that.framesRendered++;

        g.clearRect(0, 0, width, height);

        if (_this.displayOutline) {
            g.globalCompositeOperation = 'source-over';
            g.strokeStyle = '#000';
            g.lineWidth = 0.5;
            g.strokeText(message, textOffsetLeft, textAscent);
        }

        g.globalCompositeOperation = 'darker';

        for (let i = 0; i < particles.length; i++) {
            g.fillStyle = colors[i % colors.length];
            particles[i].render();
        }
    };

    // Returns x, y coordinates for a given index in the pixel array.
    const getPosition = function (i) {
        return {
            x: (i - (width * 4) * Math.floor(i / (width * 4))) / 4,
            y: Math.floor(i / (width * 4)),
        };
    };

    // Returns a color for a given pixel in the pixel array.
    const getColor = function (x, y) {
        const base = (Math.floor(y) * width + Math.floor(x)) * 4;
        const c = {
            r: pixels[base + 0],
            g: pixels[base + 1],
            b: pixels[base + 2],
            a: pixels[base + 3],
        };

        return `rgb(${c.r},${c.g},${c.b})`;
    };

    this.message = message;
    createBitmap(message);

    var loop = function () {
        requestAnimationFrame(loop);
        render();
    };

    // This calls the render function every 30 milliseconds.
    loop();

    // This class is responsible for drawing and moving those little
    // colored dots.
    function Particle(x, y, c) {
    // Position
        this.x = x;
        this.y = y;

        // Size of particle
        this.r = 1;

        // This velocity is used by the explode function.
        this.vx = 0;
        this.vy = 0;

        this.constrain = function constrainFn(v, o1, o2) {
            if (v < o1) v = o1;
            else if (v > o2) v = o2;
            return v;
        };

        // Called every frame
        this.render = function renderFrame() {
            // What color is the pixel we're sitting on top of?
            const c = getColor(this.x, this.y);

            // Where should we move?
            const angle = noise(this.x / noiseScale, this.y / noiseScale) * _this.noiseStrength;
            // var angle = -Math.PI/2;

            // Are we within the boundaries of the image?
            const onScreen = this.x > 0 && this.x < width && this.y > 0 && this.y < height;

            const isBlack = c !== 'rgb(255,255,255)' && onScreen;

            // If we're on top of a black pixel, grow.
            // If not, shrink.
            if (isBlack) {
                this.r += _this.growthSpeed;
            } else {
                this.r -= _this.growthSpeed;
            }

            // This velocity is used by the explode function.
            this.vx *= 0.5;
            this.vy *= 0.5;

            // Change our position based on the flow field and our
            // explode velocity.
            this.x += Math.cos(angle) * _this.speed + this.vx;
            this.y += -Math.sin(angle) * _this.speed + this.vy;

            if (this.r > _this.maxSize) {
                this.r = _this.maxSize;
            } else if (this.r < 0) {
                this.r = 0;
                this.x = Math.random() * width;
                this.y = Math.random() * height;
                return false;
            }

            // this.r = 3;
            // debugger
            // console.log(DAT.GUI.constrain(this.r, 0, _this.maxSize));
            // this.r = this.constrain(this.r, _this.minSize, _this.maxSize);

            // If we're tiny, keep moving around until we find a black
            // pixel.
            if (this.r <= 0) {
                this.x = Math.random() * width;
                this.y = Math.random() * height;
                return false; // Don't draw!
            }

            // If we're off the screen, go over to other side
            if (this.x < 0) this.x = width;
            if (this.x > width) this.x = 0;
            if (this.y < 0) this.y = height;
            if (this.y > height) this.y = 0;

            // Draw the circle.
            g.beginPath();
            // g.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
            g.rect(this.x, this.y, this.r, this.r);
            g.fill();

            return true;
        };
    }
}

module.exports = fizzyText;
