/*
 * jQuery.flot.dashes
 *
 * options = {
 *   series: {
 *     dashes: {
 *
 *       // show
 *       // default: false
 *       // Whether to show dashes for the series.
 *       show: <boolean>,
 *
 *       // lineWidth
 *       // default: 2
 *       // The width of the dashed line in pixels.
 *       lineWidth: <number>,
 *
 *       // dashLength
 *       // default: 10
 *       // Controls the length of the individual dashes and the amount of
 *       // space between them.
 *       // If this is a number, the dashes and spaces will have that length.
 *       // If this is an array, it is read as [ dashLength, spaceLength ]
 *       dashLength: <number> or <array[2]>
 *     }
 *   }
 * }
 */
(function($){

  function init(plot) {
    plot.hooks.drawSeries.push(function(plot, ctx, series) {
      if (!series.dashes.show) return;

      var plotOffset = plot.getPlotOffset(),
          axisx = series.xaxis,
          axisy = series.yaxis;

      function plotDashes(xoffset, yoffset) {

        var points = series.datapoints.points,
            ps = series.datapoints.pointsize,
            prevx = null,
            prevy = null,
            dashRemainder = 0,
            dashOn = true,
            dashOnLength,
            dashOffLength;

        if (series.dashes.dashLength[0]) {
          dashOnLength = series.dashes.dashLength[0];
          if (series.dashes.dashLength[1]) {
            dashOffLength = series.dashes.dashLength[1];
          } else {
            dashOffLength = dashOnLength;
          }
        } else {
          dashOffLength = dashOnLength = series.dashes.dashLength;
        }

        ctx.beginPath();

        for (var i = ps; i < points.length; i += ps) {

          var x1 = points[i - ps],
              y1 = points[i - ps + 1],
              x2 = points[i],
              y2 = points[i + 1];

          if (x1 == null || x2 == null) continue;

          // clip with ymin
          if (y1 <= y2 && y1 < axisy.min) {
            if (y2 < axisy.min) continue;   // line segment is outside
            // compute new intersection point
            x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
            y1 = axisy.min;
          } else if (y2 <= y1 && y2 < axisy.min) {
            if (y1 < axisy.min) continue;
            x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
            y2 = axisy.min;
          }

          // clip with ymax
          if (y1 >= y2 && y1 > axisy.max) {
            if (y2 > axisy.max) continue;
            x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
            y1 = axisy.max;
          } else if (y2 >= y1 && y2 > axisy.max) {
            if (y1 > axisy.max) continue;
            x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
            y2 = axisy.max;
          }

          // clip with xmin
          if (x1 <= x2 && x1 < axisx.min) {
            if (x2 < axisx.min) continue;
            y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
            x1 = axisx.min;
          } else if (x2 <= x1 && x2 < axisx.min) {
            if (x1 < axisx.min) continue;
            y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
            x2 = axisx.min;
          }

          // clip with xmax
          if (x1 >= x2 && x1 > axisx.max) {
            if (x2 > axisx.max) continue;
            y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
            x1 = axisx.max;
          } else if (x2 >= x1 && x2 > axisx.max) {
            if (x1 > axisx.max) continue;
            y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
            x2 = axisx.max;
          }

          if (x1 != prevx || y1 != prevy) {
            ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
          }

          var ax1 = axisx.p2c(x1) + xoffset,
              ay1 = axisy.p2c(y1) + yoffset,
              ax2 = axisx.p2c(x2) + xoffset,
              ay2 = axisy.p2c(y2) + yoffset,
              dashOffset;

          function lineSegmentOffset(segmentLength) {

            var c = Math.sqrt(Math.pow(ax2 - ax1, 2) + Math.pow(ay2 - ay1, 2));

            if (c <= segmentLength) {
              return {
                deltaX: ax2 - ax1,
                deltaY: ay2 - ay1,
                distance: c,
                remainder: segmentLength - c
              }
            } else {
              var xsign = ax2 > ax1 ? 1 : -1,
                  ysign = ay2 > ay1 ? 1 : -1;
              return {
                deltaX: xsign * Math.sqrt(Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
                deltaY: ysign * Math.sqrt(Math.pow(segmentLength, 2) - Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
                distance: segmentLength,
                remainder: 0
              };
            }
          }
          //-end lineSegmentOffset

          do {

            dashOffset = lineSegmentOffset(
                dashRemainder > 0 ? dashRemainder :
                  dashOn ? dashOnLength : dashOffLength);

            if (dashOffset.deltaX != 0 || dashOffset.deltaY != 0) {
              if (dashOn) {
                ctx.lineTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
              } else {
                ctx.moveTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
              }
            }

            dashOn = !dashOn;
            dashRemainder = dashOffset.remainder;
            ax1 += dashOffset.deltaX;
            ay1 += dashOffset.deltaY;

          } while (dashOffset.distance > 0);

          prevx = x2;
          prevy = y2;
        }

        ctx.stroke();
      }
      //-end plotDashes

      ctx.save();
      ctx.translate(plotOffset.left, plotOffset.top);
      ctx.lineJoin = 'round';

      var lw = series.dashes.lineWidth,
          sw = series.shadowSize;

      // FIXME: consider another form of shadow when filling is turned on
      if (lw > 0 && sw > 0) {
        // draw shadow as a thick and thin line with transparency
        ctx.lineWidth = sw;
        ctx.strokeStyle = "rgba(0,0,0,0.1)";
        // position shadow at angle from the mid of line
        var angle = Math.PI/18;
        plotDashes(Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2));
        ctx.lineWidth = sw/2;
        plotDashes(Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4));
      }

      ctx.lineWidth = lw;
      ctx.strokeStyle = series.color;

      if (lw > 0) {
        plotDashes(0, 0);
      }

      ctx.restore();

    });
    //-end draw hook
  }
  //-end init

  $.plot.plugins.push({
    init: init,
    options: {
      series: {
        dashes: {
          show: false,
          lineWidth: 2,
          dashLength: 10
        }
      }
    },
    name: 'dashes',
    version: '0.1'
  });

})(jQuery)
