text: /* eslint-disable no-underscore-dangle, no-param-reassign */
  import d3 from 'd3';
  import { category21 } from '../javascripts/modules/colors';
  import { wrapSvgText } from '../javascripts/modules/utils';
  import { okspectrumcolors } from '../javascripts/modules/colors';
  import { warningspectrumcolors } from '../javascripts/modules/colors';
  import { errorspectrumcolors } from '../javascripts/modules/colors';
  
  require('./sunburst_opvizor.css');
  
  // Modified from http://bl.ocks.org/kerryrodden/7090426
  function sunburstVis(slice, payload) {
    const container = d3.select(slice.selector);
  
    // vars with shared scope within this function
    const margin = { top: 10, right: 5, bottom: 10, left: 5 };
    const containerWidth = slice.width();
    const containerHeight = slice.height();
    const breadcrumbHeight = containerHeight * 0.085;
    const visWidth = containerWidth - margin.left - margin.right;
    const visHeight = containerHeight - margin.top - margin.bottom - breadcrumbHeight;
    const radius = Math.min(visWidth, visHeight) / 2;
    var primary_label = payload.form_data['label_primary'];
    if (primary_label.length < 1){
      primary_label = 'm1';}
    var secondary_label = payload.form_data['label_secondary'];
     if (secondary_label.length < 1){
      secondary_label = 'm2';}
    var main_label = payload.form_data['main_label'];
     if (main_label.length < 1){
      main_label = 'total';}
    var second_label = payload.form_data['second_label'];
      if (second_label.length < 1){
      second_label = 'parent';}
  
    let colorByCategory = true; // color by category if primary/secondary metrics match
    let maxBreadcrumbs;
    let breadcrumbDims; // set based on data
    let totalSize; // total size of all segments; set after loading the data.
    let colorScale;
    let breadcrumbs;
    let vis;
    let arcs;
    let gMiddleText; // dom handles
  
    // Helper + path gen functions
    const partition = d3.layout.partition()
      .size([2 * Math.PI, radius * radius])
      .value(function (d) { return d.m1; });
  
    const arc = d3.svg.arc()
      .startAngle((d) => d.x)
      .endAngle((d) => d.x + d.dx)
      .innerRadius(function (d) {
        return Math.sqrt(d.y);
      })
      .outerRadius(function (d) {
        return Math.sqrt(d.y + d.dy);
      });
  
    const formatNum = d3.format( slice.formData.number_format);
    const formatPerc = d3.format('.3p');
  
    container.select('svg').remove();
  
    const svg = container.append('svg:svg')
      .attr('width', containerWidth)
      .attr('height', containerHeight);
  
    function createBreadcrumbs(rawData) {
      const firstRowData = rawData.data[0];
      // -2 bc row contains 2x metrics, +extra for %label and buffer
      maxBreadcrumbs = (firstRowData.length - 2) + 1;
      breadcrumbDims = {
        width: visWidth / maxBreadcrumbs,
        height: breadcrumbHeight * 0.8, // more margin
        spacing: 3,
        tipTailWidth: 10,
      };
  
      breadcrumbs = svg.append('svg:g')
        .attr('class', 'breadcrumbs')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  
      breadcrumbs.append('svg:text')
        .attr('class', 'end-label');
    }
  
    // Given a node in a partition layout, return an array of all of its ancestor
    // nodes, highest first, but excluding the root.
    function getAncestors(node) {
      const path = [];
      let current = node;
      while (current.parent) {
        path.unshift(current);
        current = current.parent;
      }
      return path;
    }
  
    // Generate a string that describes the points of a breadcrumb polygon.
    function breadcrumbPoints(d, i) {
      const points = [];
      points.push('0,0');
      points.push(breadcrumbDims.width + ',0');
      points.push(
        breadcrumbDims.width + breadcrumbDims.tipTailWidth + ',' + (breadcrumbDims.height / 2));
      points.push(breadcrumbDims.width + ',' + breadcrumbDims.height);
      points.push('0,' + breadcrumbDims.height);
      if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
        points.push(breadcrumbDims.tipTailWidth + ',' + (breadcrumbDims.height / 2));
      }
      return points.join(' ');
    }
  
    function updateBreadcrumbs(sequenceArray, percentageString) {
      const g = breadcrumbs.selectAll('g')
        .data(sequenceArray, function (d) {
          return d.name + d.depth;
        });
  
      // Add breadcrumb and label for entering nodes.
      const entering = g.enter().append('svg:g');
  
      entering.append('svg:polygon')
          .attr('points', breadcrumbPoints)
          .style('fill', function (d) {
              //adding colors for when its ok warning or error
              if(d.name == 'ok'){return okspectrumcolors(d.name);}
              if(d.name == 'warning'){return warningspectrumcolors(d.name);}
              if(d.name == 'error'){return errorspectrumcolors(d.name);}
            return colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1);
          });
  
      entering.append('svg:text')
          .attr('x', (breadcrumbDims.width + breadcrumbDims.tipTailWidth) / 2)
          .attr('y', breadcrumbDims.height / 4)
          .attr('dy', '0.35em')
          .style('fill', function (d) {
            // Make text white or black based on the lightness of the background
  
            const col = d3.hsl(colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1));
            return col.l < 0.5 ? 'white' : 'black';
          })
          .attr('class', 'step-label')
          .text(function (d) { return d.name.replace(/_/g, ' '); })
          .call(wrapSvgText, breadcrumbDims.width, breadcrumbDims.height / 2);
  
      // Set position for entering and updating nodes.
      g.attr('transform', function (d, i) {
        return 'translate(' + i * (breadcrumbDims.width + breadcrumbDims.spacing) + ', 0)';
      });
  
      // Remove exiting nodes.
      g.exit().remove();
  
      // Now move and update the percentage at the end.
      breadcrumbs.select('.end-label')
          .attr('x', (sequenceArray.length + 0.5) * (breadcrumbDims.width + breadcrumbDims.spacing))
          .attr('y', breadcrumbDims.height / 2)
          .attr('dy', '0.35em')
          .text(percentageString);
  
      // Make the breadcrumb trail visible, if it's hidden.
      breadcrumbs.style('visibility', null);
    }
  
    // Fade all but the current sequence, and show it in the breadcrumb trail.
    function mouseenter(d) {
      const sequenceArray = getAncestors(d);
      const parentOfD = sequenceArray[sequenceArray.length - 2] || null;
  
      const absolutePercentage = (d.m1 / totalSize).toPrecision(3);
      const conditionalPercentage = parentOfD ? (d.m1 / parentOfD.m1).toPrecision(3) : null;
  
      const absolutePercString = formatPerc(absolutePercentage);
      const conditionalPercString = parentOfD ? formatPerc(conditionalPercentage) : '';
  
      // 3 levels of text if inner-most level, 4 otherwise
      const yOffsets = ['-25', '7', '35', '60'];
      let offsetIndex = 0;
  
      // If metrics match, assume we are coloring by category
      const metricsMatch = Math.abs(d.m1 - d.m2) < 0.00001;
  
      gMiddleText.selectAll('*').remove();
  
      gMiddleText.append('text')
        .attr('class', 'path-abs-percent')
        .attr('y', yOffsets[offsetIndex++])
        .text(absolutePercString + ' of '+main_label);
  
      if (conditionalPercString) {
        gMiddleText.append('text')
          .attr('class', 'path-cond-percent')
          .attr('y', yOffsets[offsetIndex++])
          .text(conditionalPercString + ' of '+second_label);
      }
  
      gMiddleText.append('text')
        .attr('class', 'path-metrics')
        .attr('y', yOffsets[offsetIndex++])
        .text(primary_label+': ' + formatNum(d.m1) + (metricsMatch ? '' : ', '+secondary_label+': ' + formatNum(d.m2)));
  
      gMiddleText.append('text')
        .attr('class', 'path-ratio')
        .attr('y', yOffsets[offsetIndex++])
        .text((metricsMatch ? '' : (secondary_label+'/'+primary_label+': ' + formatPerc(d.m2 / d.m1))));
  
      // Reset and fade all the segments.
      arcs.selectAll('path')
        .style('stroke-width', null)
        .style('stroke', null)
        .style('opacity', 0.7);
  
      // Then highlight only those that are an ancestor of the current segment.
      arcs.selectAll('path')
        .filter(function (node) {
          return (sequenceArray.indexOf(node) >= 0);
        })
        .style('opacity', 1)
        .style('stroke-width', '2px')
        .style('stroke', '#000');
  
      updateBreadcrumbs(sequenceArray, absolutePercString);
    }
  
    // Restore everything to full opacity when moving off the visualization.
    function mouseleave() {
      // Hide the breadcrumb trail
      breadcrumbs.style('visibility', 'hidden');
  
      gMiddleText.selectAll('*').remove();
  
      // Deactivate all segments during transition.
      arcs.selectAll('path').on('mouseenter', null);
  
      // Transition each segment to full opacity and then reactivate it.
      arcs.selectAll('path')
        .transition()
        .duration(200)
        .style('opacity', 1)
        .style('stroke', null)
        .style('stroke-width', null)
        .each('end', function () {
          d3.select(this).on('mouseenter', mouseenter);
        });
    }
  
  
    function buildHierarchy(rows) {
      const root = {
        name: 'root',
        children: [],
      };
  
      // each record [groupby1val, groupby2val, (<string> or 0)n, m1, m2]
      for (let i = 0; i < rows.length; i++) {
        const row = rows[i];
        const m1 = Number(row[row.length - 2]);
        const m2 = Number(row[row.length - 1]);
        const levels = row.slice(0, row.length - 2);
        if (isNaN(m1)) { // e.g. if this is a header row
          continue;
        }
        let currentNode = root;
        for (let level = 0; level < levels.length; level++) {
          const children = currentNode.children || [];
          const nodeName = levels[level];
          // If the next node has the name '0', it will
          const isLeafNode = (level >= levels.length - 1) || levels[level + 1] === 0;
          let childNode;
          let currChild;
  
          if (!isLeafNode) {
            // Not yet at the end of the sequence; move down the tree.
            let foundChild = false;
            for (let k = 0; k < children.length; k++) {
              currChild = children[k];
              if (currChild.name === nodeName &&
                  currChild.level === level) {
  // must match name AND level
  
                childNode = currChild;
                foundChild = true;
                break;
              }
            }
            // If we don't already have a child node for this branch, create it.
            if (!foundChild) {
              childNode = {
                name: nodeName,
                children: [],
                level,
              };
              children.push(childNode);
            }
            currentNode = childNode;
          } else if (nodeName !== 0) {
            // Reached the end of the sequence; create a leaf node.
            childNode = {
              name: nodeName,
              m1,
              m2,
            };
            children.push(childNode);
          }
        }
      }
  
      function recurse(node) {
        if (node.children) {
          let sums;
          let m1 = 0;
          let m2 = 0;
          for (let i = 0; i < node.children.length; i++) {
            sums = recurse(node.children[i]);
            m1 += sums[0];
            m2 += sums[1];
          }
          node.m1 = m1;
          node.m2 = m2;
        }
        return [node.m1, node.m2];
      }
  
      recurse(root);
      return root;
    }
  
    // Main function to draw and set up the visualization, once we have the data.
    function createVisualization(rawData) {
      const tree = buildHierarchy(rawData.data);
  
      vis = svg.append('svg:g')
        .attr('class', 'sunburst-vis')
        .attr('transform', (
          'translate(' +
            `${(margin.left + (visWidth / 2))},` +
            `${(margin.top + breadcrumbHeight + (visHeight / 2))}` +
          ')'
        ))
        .on('mouseleave', mouseleave);
  
      arcs = vis.append('svg:g')
        .attr('id', 'arcs');
  
      gMiddleText = vis.append('svg:g')
        .attr('class', 'center-label');
  
      // Bounding circle underneath the sunburst, to make it easier to detect
      // when the mouse leaves the parent g.
      arcs.append('svg:circle')
        .attr('r', radius)
        .style('opacity', 0);
  
      // For efficiency, filter nodes to keep only those large enough to see.
      const nodes = partition.nodes(tree)
        .filter(function (d) {
          return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
        });
  
      let ext;
      const fd = slice.formData;
  
      if (fd.metric !== fd.secondary_metric) {
        colorByCategory = false;
        ext = d3.extent(nodes, (d) => d.m2 / d.m1);
        colorScale = d3.scale.linear()
          .domain([ext[0], ext[0] + ((ext[1] - ext[0]) / 2), ext[1]])
          .range(['#00D1C1', 'white', '#FFB400']);
      }
  
      const path = arcs.data([tree]).selectAll('path')
        .data(nodes)
        .enter()
        .append('svg:path')
        .attr('display', function (d) {
          return d.depth ? null : 'none';
        })
        .attr('d', arc)
        .attr('fill-rule', 'evenodd')
       // .style('fill', (d) => colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1))
          .style('fill', function (d) {
              //adding colors for when its ok warning or error
              if(d.name == 'ok'){return okspectrumcolors(d.name);}
              if(d.name == 'warning'){return warningspectrumcolors(d.name);}
              if(d.name == 'error'){return errorspectrumcolors(d.name);}
              return colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1);
          })
        .style('opacity', 1)
        .on('mouseenter', mouseenter)
        .on('click', function (d) {
          //checking if its the outside ring
             if(d.depth == 1 && payload.form_data['variable_export_1'].replace(/ /g,"").length >0 && payload.form_data['location_1'] .replace(/ /g,"").length >0) {
                  var par = {};
                 // par.name = 'issuetitle';
                  par.name = payload.form_data['variable_export_1'];
                  par.value = d.name;
                  redirect([par],payload.form_data['location_1']);
              }
              if(d.depth == 2 && payload.form_data['variable_export_2'].replace(/ /g,"").length >0 && payload.form_data['location_2'] .replace(/ /g,"").length >0) {
                  var par = {};
                 par.name = payload.form_data['variable_export_2'];
                  par.value = d.name;
                  redirect([par],payload.form_data['location_2']);
              }
              if(d.depth == 3 && payload.form_data['variable_export_3'].replace(/ /g,"").length >0 && payload.form_data['location_3'] .replace(/ /g,"").length >0) {
                  var par = {};
                  par.name = payload.form_data['variable_export_3'];
                  par.value = d.name;
                  redirect([par],payload.form_data['location_3']);
              }
          });
  
      // Get total size of the tree = value of root node from partition.
      totalSize = path.node().__data__.value;
    }
    createBreadcrumbs(payload);
    createVisualization(payload);
  
    const redirect = function (d,l) {
  
          // console.log(d);
          var toEncode = JSON.stringify(makeJsonPreselectFilter(d));
          var encoded = encodeURIComponent(toEncode);
          var link = document.location.origin + l+"?preselect_filters="+encoded;
          window.open(link);
      }
  
      const makeJsonPreselectFilter = function(d){
          var obj = {};
          for(var i = 0; i< d.length; i++){
              var pair = d[i];
              var inner = {};
              var name  = pair.name;
              var value = pair.value;
              inner[name] = [value];
              obj[i]= inner;
          }
          return obj;
      }
  }
  
  module.exports = sunburstVis;
  