import { select } from 'd3-selection'
import { format, formatPrefix } from 'd3-format'

const d3_identity =  (d) => d

const d3_reverse = (arr) => {
  const mirror = [];
  for (let i = 0, l = arr.length; i < l; i++) {
    mirror[i] = arr[l-i-1];
  }
  return mirror;
}

//Text wrapping code adapted from Mike Bostock
const d3_textWrapping = (text, width) => {
  text.each(function() {
    var text = select(this),
        words = text.text().split(/\s+/).reverse(),
        word,
        line = [],
        lineNumber = 0,
        lineHeight = 1.2, //ems
        y = text.attr("y"),
        dy = parseFloat(text.attr("dy")) || 0,
        tspan = text.text(null)
          .append("tspan")
          .attr("x", 0)
          .attr("dy", dy + "em");

    while (word = words.pop()) {
      line.push(word);
      tspan.text(line.join(" "));
      if (tspan.node().getComputedTextLength() > width && line.length > 1) {
        line.pop();
        tspan.text(line.join(" "));
        line = [word];
        tspan = text.append("tspan")
          .attr("x", 0)
          .attr("dy", lineHeight + dy + "em").text(word);
      }
    }
  });
}


const d3_mergeLabels = (gen=[], labels, domain, range) => {

    if (typeof labels === "object"){
      if(labels.length === 0) return gen;

      let i = labels.length;
      for (; i < gen.length; i++) {
        labels.push(gen[i]);
      }
      return labels;
    } else if (typeof labels === "function") {
      const customLabels = []
      const genLength = gen.length
      for (let i=0; i < genLength; i++){
        customLabels.push(labels({
          i,
          genLength,
          generatedLabels : gen,
          domain,
          range }))
      }
      return customLabels
    }

    return gen;
  }

const d3_linearLegend = (scale, cells, labelFormat) => {
  let data = [];

  if (cells.length > 1){
    data = cells;

  } else {
    const domain = scale.domain(),
    increment = (domain[domain.length - 1] - domain[0])/(cells - 1)
    let i = 0;

    for (; i < cells; i++){
      data.push(domain[0] + i*increment);
    }
  }

  const labels = data.map(labelFormat);
  return {data: data,
          labels: labels,
          feature: d => scale(d)};
}

const d3_quantLegend = (scale, labelFormat, labelDelimiter) => {
  const labels = scale.range().map( d => {
    const invert = scale.invertExtent(d);
    return labelFormat(invert[0]) + " " + labelDelimiter + " " + labelFormat(invert[1]);
  });

  return {data: scale.range(),
          labels: labels,
          feature: d3_identity
        };
}

const d3_ordinalLegend= scale => ({data: scale.domain(),
          labels: scale.domain(),
          feature: d => scale(d) }
)

const d3_cellOver = (cellDispatcher, d, obj) => {
  cellDispatcher.call("cellover", obj, d);
}

const d3_cellOut = (cellDispatcher, d, obj) => {
  cellDispatcher.call("cellout", obj, d);
}

const d3_cellClick = (cellDispatcher, d, obj) => {
  cellDispatcher.call("cellclick", obj, d);
}


export default {

  d3_drawShapes: (shape, shapes, shapeHeight, shapeWidth, shapeRadius, path) => {
    if (shape === "rect"){
        shapes.attr("height", shapeHeight)
        .attr("width", shapeWidth);

    } else if (shape === "circle") {
        shapes.attr("r", shapeRadius)

    } else if (shape === "line") {
        shapes.attr("x1", 0).attr("x2", shapeWidth).attr("y1", 0).attr("y2", 0);

    } else if (shape === "path") {
      shapes.attr("d", path);
    }
  },

  d3_addText: function (svg, enter, labels, classPrefix, labelWidth){
    enter.append("text").attr("class", classPrefix + "label");
    const text = svg.selectAll(`g.${classPrefix}cell text.${classPrefix}label`)
      .data(labels)
      .text(d3_identity);

    if (labelWidth){
      svg.selectAll(`g.${classPrefix}cell text.${classPrefix}label`)
          .call(d3_textWrapping, labelWidth)
    }

    return text
  },

  d3_calcType: function (scale, ascending, cells, labels, labelFormat, labelDelimiter){
    const type = scale.invertExtent ?
            d3_quantLegend(scale, labelFormat, labelDelimiter) : scale.ticks ?
            d3_linearLegend(scale, cells, labelFormat) : d3_ordinalLegend(scale);

    //for d3.scaleSequential that doesn't have a range function
    const range = scale.range && scale.range() || scale.domain()
    type.labels = d3_mergeLabels(type.labels, labels, scale.domain(), range);

    if (ascending) {
      type.labels = d3_reverse(type.labels);
      type.data = d3_reverse(type.data);
    }

    return type;
  },

  d3_filterCells: (type, cellFilter) => {
    let filterCells = type.data.map((d, i) => ({ data: d, label: type.labels[i] }))
      .filter(cellFilter)
    const dataValues = filterCells.map(d => d.data)
    const labelValues = filterCells.map(d => d.label)
    type.data = type.data.filter(d => dataValues.indexOf(d) !== -1)
    type.labels = type.labels.filter(d => labelValues.indexOf(d) !== -1)
    return type
  },

  d3_placement: (orient, cell, cellTrans, text, textTrans, labelAlign) => {
    cell.attr("transform", cellTrans);
    text.attr("transform", textTrans);
    if (orient === "horizontal"){
      text.style("text-anchor", labelAlign);
    }
  },

  d3_addEvents: function(cells, dispatcher){
      cells.on("mouseover.legend", function (d) { d3_cellOver(dispatcher, d, this); })
          .on("mouseout.legend", function (d) { d3_cellOut(dispatcher, d, this); })
          .on("click.legend", function (d) { d3_cellClick(dispatcher, d, this); });
  },

  d3_title: (svg, title, classPrefix, titleWidth) => {
    if (title !== ""){

      const titleText = svg.selectAll('text.' + classPrefix + 'legendTitle');

      titleText.data([title])
        .enter()
        .append('text')
        .attr('class', classPrefix + 'legendTitle');

      svg.selectAll('text.' + classPrefix + 'legendTitle')
        .text(title)

      if (titleWidth){
        svg.selectAll('text.' + classPrefix + 'legendTitle')
          .call(d3_textWrapping, titleWidth)
      }

      const cellsSvg = svg.select('.' + classPrefix + 'legendCells')
      const yOffset = svg.select('.' + classPrefix + 'legendTitle').nodes()
          .map(d => d.getBBox().height)[0],

      xOffset = -cellsSvg.nodes().map(function(d) { return d.getBBox().x})[0];
      cellsSvg.attr('transform', 'translate(' + xOffset + ',' + (yOffset) + ')');

    }
  },

  d3_defaultLocale: {
    format,
    formatPrefix
  },

  d3_defaultFormatSpecifier: '.01f',

  d3_defaultDelimiter: 'to'
}
