/*
There are three parameters:
(1) Parameter is of type object. Inside can include (* marks required):
  data* - Array of objects with key, value pairs that represent a single stack of bars:
    xValue - Corresponding value for the x-axis
    stackData - Array of objects with key, value pairs that represent a bar:
      color - Defines what "color" the bar will map to
      value - Maps to the height of the bar, along the y-axis
      tooltip - (Optional) Text to display on mouse hover
  
  height - Height of the SVG the graph will be displayed in (default: 500)

  width - Width of the SVG the graph will be displayed in (default: 500)

  margin - Object with key, value pairs for the graph's margins within the SVG (default for all: 10)
    top - Top margin
    bottom - Bottom margin
    right - Right margin
    left - Left margin

  yRange - Array of two values, representing the min and max respectively. (default: [0, <calculated max>])

  xRange - Array of either the min and max or ordered ordinals (default: calculated min and max or ordered ordinals given in data)

  colorRange - Array of either the min and max or ordered ordinals (default: calculated min and max or ordered ordinals given in data)

  bVerticalXAxisLabel - Boolean whether to make the labels in the x-axis veritcal (default: false)

  bLegend - Boolean if false does not create the graph with a legend (default: true)

(2) Parameter is a d3 pointer to the SVG the graph will draw itself in.

(3) Parameter is a d3 pointer to a div that will be used for the graph's tooltip.

****Does not actually draw graph.**** Returns an object that includes a function
  drawGraph, for when ready to draw graph. Reason for this is, because of all
  the defaults, some changes may be needed before drawing the graph

returns an object with the following:
  state - All information that can be put in parameters and adding:
    margin.axisX - margin to accomodate the x-axis
    margin.axisY - margin to acommodate the y-axis

  drawGraph - function to call when ready to draw graph

  scale - Object containing three d3 scales
    x - d3 scale for the x-axis
    y - d3 scale for the y-axis
    stackColor - d3 scale for the stack color

  axis - Object containg the graph's two d3 axis
    x - d3 axis for the x-axis
    y - d3 axis for the y-axis

  svg - d3 pointer to the svg holding the graph

  svgGroup - object holding the svg groups
    main - svg group holding all other groups
    xAxis - svg group holding the x-axis
    yAxis - svg group holding the x-axis
    bars - svg groups holding the bars

  yAxisLabel - d3 pointer to the text component that holds the y axis label

  divTooltip - d3 pointer to the div that is used as the tooltip for the graph

  rects - d3 collection of the rects used in the bars

  legend - object containing information for the legend
    height - height of the legend
    width - width of the legend (if change, need to update state.margin.axisY also)
    range - array of values that appears in the legend
    barHeight - height of a bar in the legend, based on height and length of range
*/

edx_d3CreateStackedBarGraph = function(parameters, svg, divTooltip) {
  var graph = {
    svg : svg,
    state : {
      data : undefined,
      height : 500,
      width : 500,
      margin: {top: 10, bottom: 10, right: 10, left: 10},
      yRange: [0],
      xRange : undefined,
      colorRange : undefined,
      tag : "",
      bVerticalXAxisLabel : false,
      bLegend : true,
    },
    divTooltip : divTooltip,
  };

  var state = graph.state;

  // Handle parameters
  state.data = parameters.data;

  if (parameters.margin != undefined) {
    for (var key in state.margin) {
      if ((state.margin.hasOwnProperty(key) &&
           (parameters.margin[key] != undefined))) {
        state.margin[key] = parameters.margin[key];
      }
    }
  }

  for (var key in state) {
    if ((key != "data") && (key != "margin")) {
      if (state.hasOwnProperty(key) && (parameters[key] != undefined)) {
        state[key] = parameters[key];
      }
    }
  }

  if (state.tag != "")
    state.tag = state.tag+"-";

  if ((state.xRange == undefined) || (state.yRange.length < 2 || 
                                      state.colorRange == undefined)) {
    var aryXRange = [];
    var bXIsOrdinal = false;
    var maxYRange = 0;
    var aryColorRange = [];
    var bColorIsOrdinal = false;

    for (var stackKey in state.data) {
      var stack = state.data[stackKey];
      aryXRange.push(stack.xValue);
      if (isNaN(stack.xValue))
        bXIsOrdinal = true;
      
      var valueTotal = 0;
      for (var barKey in stack.stackData) {
        var bar = stack.stackData[barKey];
        valueTotal += bar.value;

        if (isNaN(bar.color))
          bColorIsOrdinal = true;

        if (aryColorRange.indexOf(bar.color) < 0)
          aryColorRange.push(bar.color);
      }
      if (maxYRange < valueTotal)
        maxYRange = valueTotal;
    }

    if (state.xRange == undefined){
      if (bXIsOrdinal)
        state.xRange = aryXRange;
      else
        state.xRange = [
          Math.min.apply(null,aryXRange),
          Math.max.apply(null,aryXRange)
        ];
    }

    if (state.yRange.length < 2)
      state.yRange[1] = maxYRange;

    if (state.colorRange == undefined){
      if (bColorIsOrdinal)
        state.colorRange = aryColorRange;
      else
        state.colorRange = [
          Math.min.apply(null,aryColorRange),
          Math.max.apply(null,aryColorRange)
        ];
    }
  }

  // Find needed spacing for axes
  var tmpEl = graph.svg.append("text").text(state.yRange[1]+"1234")
    .attr("id",state.tag+"stacked-bar-graph-long-str");
  state.margin.axisY = document.getElementById(state.tag+"stacked-bar-graph-long-str")
    .getComputedTextLength()+state.margin.left;

  var longestXAxisStr = "";
  if (isNaN(state.xRange[0])) {
    for (var i in state.xRange) {
      if (longestXAxisStr.length < state.xRange[i].length)
        longestXAxisStr = state.xRange[i]+"1234";
    }
  } else {
    longestXAxisStr = state.xRange[1]+"1234";
  }

  tmpEl.text(longestXAxisStr);
  if (state.bVerticalXAxisLabel) {
    state.margin.axisX = document.getElementById(state.tag+"stacked-bar-graph-long-str")
      .getComputedTextLength()+state.margin.bottom;
  } else {
    state.margin.axisX = document.getElementById(state.tag+"stacked-bar-graph-long-str")
      .clientHeight+state.margin.bottom;
  }

  tmpEl.remove();

  // Add y0 and y1 of the y-axis based on the count and order of the colorRange.
  // First, case if color is a number range
  if ((state.colorRange.length == 2) && !(isNaN(state.colorRange[0])) &&
    !(isNaN(state.colorRange[1]))) {
    for (var stackKey in state.data) {
      var stack = state.data[stackKey];
      stack.stackData.sort(function(a,b) { return a.color - b.color; });

      var currTotal = 0;
      for (var barKey in stack.stackData) {
        var bar = stack.stackData[barKey];
        bar.y0 = currTotal;
        currTotal += bar.value;
        bar.y1 = currTotal;
      }
    }
  } else {
    for (var stackKey in state.data) {
      var stack = state.data[stackKey];
      
      var tmpStackData = [];
      for (var barKey in stack.stackData) {
        var bar = stack.stackData[barKey];
        tmpStackData[state.colorRange.indexOf(bar.color)] = bar;
      }
      stack.stackData = tmpStackData;

      var currTotal = 0;
      for (var barKey in stack.stackData) {
        var bar = stack.stackData[barKey];
        bar.y0 = currTotal;
        currTotal += bar.value;
        bar.y1 = currTotal;
      }
    }
  }

  // Add information to create legend
  if (state.bLegend) {
    graph.legend = {
      height : (state.height-state.margin.top-state.margin.axisX),
      width : 30,
      range : state.colorRange,
    };
    if ((state.colorRange.length == 2) && !(isNaN(state.colorRange[0])) &&
        !(isNaN(state.colorRange[1]))) {
      graph.legend.range = [];
      
      var i = 0;
      var min = state.colorRange[0];
      var max = state.colorRange[1];
      while (i <= 10) {
        graph.legend.range[i] = min+((max-min)/10)*i;
        i += 1;
      }
    }
    graph.legend.barHeight = graph.legend.height/graph.legend.range.length;
    
    // Shifting the axis over to make room
    graph.state.margin.axisY += graph.legend.width;
  }

  // Make the scales
  graph.scale = {
    x: d3.scale.ordinal()
      .domain(graph.state.xRange)
      .rangeRoundBands([
        (graph.state.margin.axisY),
        (graph.state.width-graph.state.margin.right)],
                       .3),

    y: d3.scale.linear()
      .domain(graph.state.yRange) // yRange is the range of the y-axis values
      .range([
        (graph.state.height-graph.state.margin.axisX),
        graph.state.margin.top
      ]),
    
    stackColor: d3.scale.ordinal()
      .domain(graph.state.colorRange)
      .range(["#ffeeee","#ffebeb","#ffd8d8","#ffc4c4","#ffb1b1","#ff9d9d","#ff8989","#ff7676","#ff6262","#ff4e4e","#ff3b3b"])
  };

  if ((state.colorRange.length == 2) && !(isNaN(state.colorRange[0])) &&
    !(isNaN(state.colorRange[1]))) {
    graph.scale.stackColor = d3.scale.linear()
      .domain(state.colorRange)
      .range(["#e13f29","#17a74d"]);
  }

  // Setup axes
  graph.axis = {
    x: d3.svg.axis()
      .scale(graph.scale.x),
    y: d3.svg.axis()
      .scale(graph.scale.y),
  }

  graph.axis.x.orient("bottom");
  graph.axis.y.orient("left");

  // Draw graph function, to call when ready.
  graph.drawGraph = function() {
    var graph = this;
    
    // Steup SVG
    graph.svg.attr("id", graph.state.tag+"stacked-bar-graph")
      .attr("class", "stacked-bar-graph")
      .attr("width", graph.state.width)
      .attr("height", graph.state.height);
    graph.svgGroup = {};

    graph.svgGroup.main = graph.svg.append("g");

    // Draw Bars
    graph.svgGroup.bars = graph.svgGroup.main.selectAll(".stacked-bar")
      .data(graph.state.data)
      .enter().append("g")
      .attr("class", "stacked-bar")
      .attr("transform", function(d) {
          return "translate("+graph.scale.x(d.xValue)+",0)";
      });

    graph.rects = graph.svgGroup.bars.selectAll("rect")
      .data(function(d) { return d.stackData; })
      .enter().append("rect")
      .attr("width", function(d) {
          return graph.scale.x.rangeBand()
      })
      .attr("y", function(d) { return graph.scale.y(d.y1); })
      .attr("height", function(d) {
        return graph.scale.y(d.y0) - graph.scale.y(d.y1);
      })
      .attr("id", function(d) { return d.module_url })
      .style("fill", function(d) { return graph.scale.stackColor(d.color); })
      .style("stroke", "white")
      .style("stroke-width", "0.5px");

    // Setup tooltip
    if (graph.divTooltip != undefined) {
      graph.divTooltip
        .style("position", "absolute")
        .style("z-index", "10")
        .style("visibility", "hidden");
    }

    graph.rects
      .on("mouseover", function(d) {
        var pos = d3.mouse(graph.divTooltip.node().parentNode);
        var left = pos[0]+10;
        var top = pos[1]-10;
        var width = $('#'+graph.divTooltip.attr("id")).width();

        graph.divTooltip.style("visibility", "visible")
          .text(d.tooltip);

        if ((left+width+30) > $("#"+graph.divTooltip.node().parentNode.id).width())
          left -= (width+30);
        
        graph.divTooltip.style("top", top+"px")
          .style("left", left+"px");
      })
      .on("mouseout", function(d){
        graph.divTooltip.style("visibility", "hidden")
      });

    // Add legend
    if (graph.state.bLegend) {
      graph.svgGroup.legendG = graph.svgGroup.main.append("g")
        .attr("class","stacked-bar-graph-legend")
        .attr("transform","translate("+graph.state.margin.left+","+
              graph.state.margin.top+")");
      graph.svgGroup.legendGs = graph.svgGroup.legendG.selectAll(".stacked-bar-graph-legend-g")
        .data(graph.legend.range)
        .enter().append("g")
        .attr("class","stacked-bar-graph-legend-g")
        .attr("id",function(d,i) { return graph.state.tag+"legend-"+i; })
        .attr("transform", function(d,i) {
          return "translate(0,"+
            (graph.state.height-graph.state.margin.axisX-((i+1)*(graph.legend.barHeight))) + ")";
        });
      
      graph.svgGroup.legendGs.append("rect")
        .attr("class","stacked-bar-graph-legend-rect")
        .attr("height", graph.legend.barHeight)
        .attr("width", graph.legend.width)
        .style("fill", graph.scale.stackColor)
        .style("stroke", "white");

      graph.svgGroup.legendGs.append("text")
        .attr("class","axis-label")
        .attr("transform", function(d) {
          var str = "translate("+(graph.legend.width/2)+","+
            (graph.legend.barHeight/2)+")";
          return str;
        })
        .attr("dy", ".35em")
        .attr("dx", "-1px")
        .style("text-anchor", "middle")
        .text(function(d,i) { return d; });
    }


    // Draw Axes
    graph.svgGroup.xAxis = graph.svgGroup.main.append("g")
      .attr("class","stacked-bar-graph-axis")
      .attr("id",graph.state.tag+"x-axis");

    var tmpS = "translate(0,"+(graph.state.height-graph.state.margin.axisX)+")";
    if (graph.state.bVerticalXAxisLabel) {
      graph.axis.x.orient("left");
      tmpS = "rotate(270), translate(-"+(graph.state.height-graph.state.margin.axisX)+",0)";
    }
    graph.svgGroup.xAxis.attr("transform", tmpS)
      .call(graph.axis.x);

    graph.svgGroup.yAxis = graph.svgGroup.main.append("g")
      .attr("class","stacked-bar-graph-axis")
      .attr("id",graph.state.tag+"y-axis")
      .attr("transform","translate("+
            (graph.state.margin.axisY)+",0)")
      .call(graph.axis.y);
    graph.yAxisLabel = graph.svgGroup.yAxis.append("text")
      .attr("dy","1em")
      .attr("transform","rotate(-90)")
      .style("text-anchor","end")
      .text(gettext("Number of Students"));
  };

  return graph;
};