let SVG_DX = 800,
    N_COLS = 56, 
    CELL_DIM = 10;

(function addArrayEqualityMethod() {
    // from http://stackoverflow.com/questions/7837456/how-to-compare-arrays-in-javascript
    Array.prototype.equals = function (array) {

        for (var i = 0, l=this.length; i < l; i++) {
            if (this[i] != array[i]) { 
                return false;   
            }           
        }       
        return true;
    }
})();

function assignClass(cell) {
    return "row_" + cell.row + (cell.state == 1 ? " state_1" : " state_0");
}

function computeNewState(states) {
    // state of first cell remains 0
    let new_states = [0];  

    // NB: loop begins at second element in array and end at second
    //     to last element in array; state for first and last elements 
    //     in array remain 0
    for (let i = 1; i < states.length - 1; i++) {

        // NB: slice end not included
        let context = states.slice(i - 1, i + 2);

        // rule 126
        if (context.equals([1, 1, 1])) {
            new_states.push(0); 
        } else if (context.equals([1, 1, 0])) {
            new_states.push(1);
        } else if (context.equals([1, 0, 1])) {
            new_states.push(1);
        } else if (context.equals([1, 0, 0])) {
            new_states.push(1);
        } else if (context.equals([0, 1, 1])) {
            new_states.push(1);
        } else if (context.equals([0, 1, 0])) {
            new_states.push(1);
        } else if (context.equals([0, 0, 1])) {
            new_states.push(1);
        } else if (context.equals([0, 0, 0])) {
            new_states.push(0);
        }
    }

    // state of last cell remains 0
    new_states.push(0);  

    return new_states;
}

function updateRow(selection, plot, row) {
    let old_states = selection.data().map(cell => cell.state),
        new_states = computeNewState(old_states);

    if (plot == "single") {
        updateThisRow(selection, new_states);
    } 
    if (plot == "multi") {
        updateNextRow(new_states, row);
    }
}

function updateThisRow(selection, new_states) {
    selection.each(function(cell, i) {  
                    cell.state = new_states[i]; 

                    d3.select(this)
                      .attr("class", assignClass); 
             });
}

function updateNextRow(new_states, row) {
    d3.select("#plot_multi")
      .selectAll(".row_" + (row + 1))
      .each(function(cell, i) {  
          cell.state = new_states[i]; 

          d3.select(this)
            .transition()
            .delay(row * 50) 
            .attr("class", assignClass); 
      });
}

function constructCells(n_rows) {
    let rows = d3.range(n_rows),
        cols = d3.range(N_COLS),
        cells = d3.cross(rows, cols, (row, col) => {
            return {"row": row, "col": col, "state": 0};
        });

    // set initial state of cell in first row, center col to 1 
    cells[Math.round(N_COLS/2)].state = 1;

    return cells;
}

function addSVG(plot, svg_dy) {
    d3.select("#plot_" + plot)
      .append("svg")
      .attr("id", "svg_" + plot)
      .attr("width", SVG_DX)
      .attr("height", svg_dy);
}

function plotRects(plot, cell) {
    d3.select("#svg_" + plot)
      .selectAll("rect")
      .data(cell)
      .enter()
      .append("rect")
      .attr("class", assignClass)
      .attr("height", CELL_DIM + "px")
      .attr("width", CELL_DIM + "px")
      .attr("x", cell => cell.col * 11)
      .attr("y", cell => cell.row * 11);
}

function clearStates(plot) {
    d3.select("#plot_" + plot)
      .selectAll("rect")
      .each(function(cell) {
          cell.row == 0 && cell.col == Math.round(N_COLS/2) ? cell.state = 1 : cell.state = 0;

          d3.select(this)
            .attr("class", assignClass);
      });
}