window.Multiline = window.Multiline || {}

Multiline = function(parentEl, chartId, data, width, height) {
  var self = {},
      data,
      parentEl,
      parent,
      chart,
      grayLines,
      colorLines,
      avgLines,
      avgG,
      areasG,
      linesG,
      labelsG,
      diff,
      defs,
      tooltip,
      labelLeft,
      labelRight,
      tt,
      isAnimating=false,
      cancelFn = false,
      cross,
      persisted = [],
      selected = [],
      animating = [],
      margin = (width<375)? {top: 45, right:60, bottom: 30, left: 50}:{top: 45, right:80, bottom: 30, left: 70},
      width = width - margin.left-margin.right,
      height = height - margin.top - margin.bottom,
      ticks = 3,
      timeFormat = d3.time.format("%b %Y"),
      opacityF = .9,
      opacityD = .3,
      areaToShow = [],
      useVoronoi = true,
      useStacked = false,
      padding = 3

  var time = d3.time.format('%Y')

  var exclude = []

  var x = d3.time.scale()
      .range([0, width])

  var y = d3.scale.linear()
      .range([height, 0])
      .nice()

  var line = d3.svg.line()
      .x(function(d) {return x(d.key); })
      .y(function(d) {return (useStacked)?y(d.stackY):y(d.value)})
      // .defined(function (d) { return (useStacked)?y(d.stackY):y(d.value) })

  var area = d3.svg.area()
      .x(function(d) { return x(d.key); })
      .y0(height)
      .y1(function(d) { return (useStacked)?y(d.stackY):y(d.value)});

  var formatDollars = d3.format('$,.0f')

  x.domain(collectionDomainExtent(data))
  y.domain(collectionRangeExtent(data))
  y.domain([cToF(-1.5),cToF(1.5)])

  var yAxis = d3.svg.axis()
          .scale(y)
          .orient("left")
          .ticks(ticks)
          .tickFormat(function(d) {
            return d ? formatTemperature(d) : ''
          })
          .tickSubdivide(0)

  var xAxis = d3.svg.axis()
          .scale(x)
          // .orient("bottom")
          .ticks(0)
          .tickFormat(function(d) {
            return timeFormat(d).split(' ')[0]+'. '+timeFormat(d).split(' ')[1]
          })

  if(useStacked) {
    d3.range(data[1].values.length).forEach(function(i){
      // debugger
      var curValue = 0
      data.forEach(function(d){
       d.values[i].stackY = curValue + d.values[i].value
       curValue+=d.values[i].value
      })
    })
    y.domain([0, d3.extent(data[data.length-1].values.map(function(d){return d.stackY}))[1]])
  }

  data.forEach(function(d, i){
    d.values = d.values.map(function(value,j) {
      var v = value;
      v.id = toSlug(d.key+'-'+j)
      v.color = d.color
      v.y = y
      return v
    })

    if(useStacked) {
      areaToShow.push(toSlug(d.key))
    }
  })


  self.data = function(newData) {
    if (!arguments.length) return data
    data = newData
    self.draw(parentEl, chartId)
    return this
  }

  self.draw = function(parentEl, chartId) {
    yAxis.ticks(ticks)
    d3.select('.chart-container.'+toSlug(chartId)).remove()
    d3.select('#'+toSlug(chartId)).remove()

    parent = d3.select(parentEl).append('div').classed('chart-container noselect '+toSlug(chartId), true)
      .style({
        position: 'relative',
        width: width + margin.left + margin.right+'px',
        height: height + margin.top + margin.bottom+'px',
        margin: '0 auto'
      })

    labelLeft = parent.append('div')
      .attr('class', 'year-label left '+ toSlug(chartId))
      .style({
        opacity:1
      })

    chart = parent.append("svg")
        .attr('id', toSlug(chartId))
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
      .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")

    defs = chart.append('defs')

    self.addPatterns(data)

    chart.append("g")
        .attr("class", "axis axis--x")
        .attr("transform", "translate(0," + y(0) + ")")
        .append('line')
        .attr({
          x1:0,
          y1:0,
          x2:1.05*width,
          y2:0,
        })
        // .call(xAxis)

    chart.append("g")
        .attr("transform", "translate(0, 0)")
        .attr("class", "axis axis--y left ")
        .call(yAxis)
        .append('line')
          .attr({
            x1:0,
            y1:y(2),
            x2:0,
            y2:y(-2),
          })

      .append("text")
        .attr("x", 2)
        .attr("dy", ".22em")
        .style("font-weight", "bold")


    // chart.select('.line').append('circle')

    areasG = chart.append("g")
        .attr("class", "area")

    areasG.insert('rect')
      .classed('block', true)
      .attr('fill','#fff')
      .attr('fill-opacity',0)
      .attr('width', x.range()[1])
      .attr('height', y.range()[0])
      .on('mousemove',function(d){
        dispatch.tooltip(d)
      })
      .on('click',function(d){
        // dispatch.mouseoverArea(d)
      })

    diff = chart.append("g").attr("class", "diff")

    areasG = areasG.selectAll('g')
          .data(data).enter()
        .append('g')


    if(useStacked){
      area.y0(0)

      defs.selectAll('clipPath')
        .data(data).enter()
        .append('clipPath')
        .attr('id',function(d){return 'clip-path-'+toSlug(d.key)})
          .append('path')
          .attr('d',function(d) {
            return area(d.values)
          })

      area.y0(height)
    }


    var colorAreas = areasG.append("path")
        .attr("d", function(d,i) {
            d.area = this;
            return area(d.values)
        })
        .attr('class',function(d) {return 'area-'+toSlug(d.key)+' color-area'})
        .attr('fill', function(d,i) {return 'url(#pattern-'+toSlug(d.key)+')'})
        .attr('clip-path', function(d,i) {
          return (i==0)? '' :'url(#clip-path-'+toSlug(data[i-1].key)+')'
        })
        .attr('opacity',function(d) {
            return (areaToShow.indexOf(toSlug(d.key))<0)?0:opacityD
          })
        .on('mousemove',function(d){
          if(useStacked){
            d3.selectAll('.color-area')
              .style('opacity',function(d) {
                return (persisted.indexOf(d.key)<0)?opacityD:opacityF
              })
              .each(function(d) {
                d3.select(d.line).style('opacity',function(d) {
                  return (persisted.indexOf(d.key)<0)?opacityD:opacityF
                })
              })

            d3.select(this).style('opacity',function(d) {
              return (areaToShow.indexOf(toSlug(d.key))<0)?0:opacityF
            })
          }
          d3.select(d.line).style('opacity',opacityF)

          dispatch.mouseoverArea(d)
        })
        .on('mouseout',function(d){
          if(useStacked){
            d3.selectAll('.color-area').style('opacity',function(d) {
                if(persisted.length>0){
                  return (persisted.indexOf(d.key)<0)?opacityD:opacityF
                }else{
                  return opacityF
                }
              })
              .each(function(d) {
                d3.select(d.line).style('opacity',function(d) {
                  if(persisted.length>0){
                    return (persisted.indexOf(d.key)<0)?opacityD:opacityF
                  }else{
                    return opacityF
                  }
                })
              })
          }
          dispatch.mouseoutArea(d)
        })
        .on('click',function(d) {
          if(useStacked){
            if(exclude.indexOf(d.key)<0){
              dispatch.clickArea(d)
              if(persisted.length==2){
                self.persist([])
              }else{
                self.persist(d.key)
              }

              d3.select(this).style('opacity',function(d) {
                return (persisted.indexOf(d.key)<0)?opacityD:opacityF
              })
              d3.select(d.line).style('opacity',function(d) {
                return (persisted.indexOf(d.key)<0)?opacityD:opacityF
              })
            }else{
              self.persist([])
            }
          }
        })


    linesG = chart.append("g")
        .attr("class", "line")
      .selectAll('g')
        .data(data).enter()
        .append('g')
        .attr('class', function(d) {
          return 'lineG '+toSlug(d.key)+' '+toSlug(d.class)
        })

    grayLines = linesG.append("path")
        .attr("d", function(d,i) {
            d.line = this;
            return line(d.values)
        })
        .attr('class',function(d) {return 'line-'+toSlug(d.key)+' gray-line'})
        .attr('opacity', 0)

    colorLines = linesG.append("path")
        .attr("d", function(d,i) {
            d.line = this;
            return line(d.values)
        })
        .attr('class',function(d) {return 'line-'+toSlug(d.key)+' color-line'})
        .attr('stroke', function(d,i) {return d.color})
        .attr('opacity', opacityF)


    var swoop1 ='<path d="M9.762,2.169 c1.127,22.398,19.615,40.182,42.296,40.182"/>'
        swoop1+='<g> <polygon points="2.365,9.506 3.948,10.73 9.973,2.925 17.32,9.505 18.654,8.013 9.706,0"/></g>'

    var swoop2 ='<path stroke-miterlimit="10" d="M7.261,2.837 C22.926,21.3,21.457,48.958,3.557,65.643"/>'
        swoop2+='<g> <polygon points="4.792,13.184 6.787,13.356 7.625,3.531 17.365,5.086 17.679,3.109 5.817,1.217"/> </g>'

    var swoop2 ='<path stroke-miterlimit="10" d="M2.168,7.174 c14.643,1.107,26.179,13.339,26.179,28.265c0,15.655-12.691,28.347-28.347,28.347"/>'
        swoop2+='<g> <polygon points="7.634,16.365 9.181,15.095 2.91,7.483 10.961,1.785 9.803,0.152 0,7.094"/> </g>'

    // var swoop2 ='<path fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" d="M21.852,2.826 c9.944,22.077,0.876,48.249-20.942,59.327"/>'
    //     swoop2+='<g> <polygon points="16.617,12.084 18.485,12.802 22.011,3.593 30.94,7.784 31.789,5.97 20.914,0.87"/></g>'


    var annotations = chart.append('g.annotations')

    annotations.append('g.top-swoop.swoop').html(swoop1)
      .attr("transform", "translate(" + x(new Date('10/01/1954')) + "," + y(.15) + ") "+(window.innerWidth<1200?'scale(.8)':''))
      .append('text')
      .attr("transform", "translate(" + 60 + "," + 45 + ")")
      .tspans(['This line shows the measured, or ','"observed," land-ocean temperature'])

    annotations.append('g.bottom-swoop.swoop').html(swoop2)
      .attr("transform", "translate(" + x(new Date('09/01/2002')) + "," + y(-.3) + ") "+(window.innerWidth<1200?'scale(.8)':''))
      .append('text')
      // .attr("transform", "translate(" + 0 + "," + 60 + ")")
      .attr("transform", "translate(" + -5 + "," + 60 + ")")
      .tspans(['This band shows where temperatures', 'fall in 95% of climate simulations'])

    var key = chart.append('g.key')
    var un = key.append('g.uncertainty')

    self.addPatterns([{key:'key',color:'#c6c6c6'}])

    un.append('rect')
      .attr({
        x:x(new Date('01/01/2012')),
        y:'72.5%',
        width:95,
        height:35,
        fill:'url(#pattern-key)'
      })

    un.append('text').text('95% Confidence')
    .attr({
      x:x(new Date('02/01/2012')),
      y:'71.5%',
    })

    key.append('text').text('1880-1910')
    .attr({
      x:'-65',
      y:y(0.08),
    })

    key.append('text').text('Average')
    .attr({
      x:'-55',
      y:y(-0.08),
    })

    key.append('text').text('Hotter')
    .attr({
      x:'-30',
      y:y(2.2),
    })

    key.append('text').text('Colder')
    .attr({
      x:'-30',
      y:y(-2.3),
    })

    parent.append('div.labels')
      .selectAll('div')
        .data(data).enter()
        .append('div')
        .attr('class',function(d){return d.slug + ' text moving opM'})
        .style('background',ƒ('color'))
        .append('p').text(ƒ('clabel'))
          .classed('c w',true)


    diff.append('line.tt-axis')
      .attr({
        x1:0,
        y1:y(1.2),
        x2:0,
        y2:y(-1.2),
      })

    diff.append('line.bar')
      .attr({
        x1:0,
        y1:y(0),
        x2:0,
        y2:y(0),
      })


    chart.append("g").attr("class", "voronoi");

    if(useVoronoi) self.updateVoronoi(data)

    dispatch.on('voronoi.'+toSlug(chartId), function(d) {
      chart.select('.voronoi').classed("voronoi--show", d)
    })
    return self
  }

  self.resize = function(_w, _h){
    width = _w - margin.left - margin.right
    height = _h - margin.top - margin.bottom

    x.range([0, width])
    y.range([height, 0])

    xAxis.scale(x)
    yAxis.scale(y)


    var ratio = window.pageYOffset/document.body.scrollHeight
    self.draw(parentEl, chartId)

    return self
  }

  self.addPatterns = function(data){

    data.forEach(function(d,i) {
      defs
        .append('pattern')
        .datum(d)
        .attr({
          x:1,
          y:1,
          width:4,
          height:4,
          patternUnits:'userSpaceOnUse'
        })
        .attr('id',function(d){return 'pattern-'+toSlug(d.key)})
        .append('circle').attr({
          cx:1,
          cy:1,
          r:1
        })
        .style('fill',function(d,i){return d.color})
    })
  }

  self.updateVoronoi = function(data){
    var voronoi = d3.geom.voronoi()
        .x(function(d) {
          return x(d.key); })
        .y(function(d) { return y(d.value); })
        .clipExtent([[-margin.left, 0], [width + margin.right, height]]);

    var voronoiData = voronoi(d3.nest()
          .key(function(d) { return x(d.key) + "," + y(d.value); })
          .rollup(function(v) { return v[0]; })
          .entries(d3.merge(data.map(function(d) { return d.values; })))
          .map(function(d) { return d.values;
        }))

    chart.select('.voronoi').html('').selectAll("path")
        .data(voronoiData)
      .enter().append("path")
        .attr("d", function(d) {
          return (d && d.join("L") != "") ? "M" + d.join("L") + "Z" : "";
        })
        .datum(function(d) { return d?d.point:''; })
        .on("mousemove", mousemove)
        .on("mouseover", mouseover)
        .on("mouseout", mouseout)
  }

  function mousemove (d) {
    dispatch.lineover(d)
  }

  function mouseover(d) {
    if(useVoronoi){
      dispatch.tooltip(d)
    }
  }

  function mouseout(d) {
    dispatch.lineout(d)
    if(useVoronoi){
      d3.selectAll('.tooltip').style('opacity',0)
    }
  }
  self.animate = function(years,duration,sel,cb){
    isAnimating =true
    var blockLock = {}

    var selector = (sel&&sel!='')?sel:'.line g'

    var defs = chart.select('defs')

    var paths = chart.selectAll(selector)
      .each(function(d,i) {
        var pExtent = pathExtent(d3.select(d.line).attr('d').slice(1).split('L'))
        if(defs.selectAll('clipPath#line-clip-path-'+toSlug(d.key)).size()==0){
          defs
            .append('clipPath')
              .attr('id',function(){return 'line-clip-path-'+toSlug(d.key)})
            .append('rect')
              .attr('width', function() {
                return Math.round(x.range()[1])
              })
              .attr('height', function() {
                return Math.round(y.range()[0])
              })

        }
        var id = "#line-clip-path-"+toSlug(d.key)
        d3.select(this).attr('clip-path',"url("+id+")")

        defs.selectAll(id).call(pathblock,duration)
      })

    function pathblock (path, duration) {
      var el = path
      path = path.select('rect')
      var id = (sel)?'#'+el.attr('id'):''
      var values = data.filter(function(d){return d.slug == el.attr('id').slice(15)})[0].values
      var lastY = 0
      var lastX = 0
      if(years==0){
        path.attr("width", 0)
      }else{
        if(duration==0){
          var index = x.invert(years).getFullYear()-x.domain()[0].getFullYear()
          lastX = (values[index])?x.domain()[0].setFullYear(x.domain()[0].getFullYear()+years):lastX
          lastY = (values[index])?values[index].value:lastY
          // debugger
          d3.select('.text.moving.'+el.attr('id').slice(15))
            .style({
              'left':parseInt(lastX)+60+'px',
              'top':y(lastY)+35+'px',
              'opacity':1
            })
          
          var year = x.domain()[0].getFullYear()+years+''  
          path.attr("width",x(time.parse(year)));

        }else{
          d3.select(blockLock).transition()
            .duration(duration)
            .ease('linear')
            .tween("attr:width", function() {
              var newDate = x.domain()[0].setFullYear(x.domain()[0].getFullYear()+years)
              var i = d3.interpolateString(defs.selectAll(id+' rect').attr('width'), x(newDate));

              return function(t) {
                var index = x.invert(i(t)).getFullYear()-x.domain()[0].getFullYear()

                lastY = (values[index])?values[index].value:lastY
                lastX = (values[index])?i(t):lastX

                d3.select('.text.moving.'+el.attr('id').slice(15))
                  .style({
                    'left':parseInt(lastX)+60+'px',
                    'opacity':1
                  })
                  .transition()
                  .duration(200)
                  .ease('linear')
                  .style({
                    'top':y(lastY)+35+'px'
                  })
                  if(currentIndex==0) {
                    d3.select('.year-label.force-chart').text('1880-'+ x.invert(i(t)).getFullYear())
                  }else if(x.invert(i(t)).getFullYear()<2006){
                    d3.select('.year-label.force-chart').text('1880-'+ x.invert(i(t)).getFullYear())
                  }

                path.attr("width", i(t));
              };
            })
            .call(endall,function() {
              isAnimating = false
              d3.select('g.key .uncertainty')
                .transition()
                .duration(1500)
                .style('opacity',currentIndex==0?0:1)

              if(currentIndex==0){
                d3.select('.top-swoop').style('opacity',0)
                  .transition()
                  .duration(500)
                  .ease('linear')
                  .style('opacity',1)
              }else if(currentIndex==1){
                d3.select('.bottom-swoop').style('opacity',0)
                  .transition()
                  .duration(500)
                  .ease('linear')
                  .style('opacity',1)
              }
            })

            function endall(transition, callback) {
              if (transition.size() === 0) { callback() }
              var n = 0;
              transition
                  .each(function() { ++n; })
                  .each("end", function() { if (!--n) callback.apply(this, arguments); });
            }
        }
      }

    }
    return self
  }
  self.scheduleFn = function(fn,duration){
    function runFn(){
      var t = new Date()
      fn(d3.time.format("%b-%e-%H:%M")(t))
      var s = t.getSeconds()
      // setTimeout(runFn, (80 - s)*1000)
      setTimeout(runFn, duration)
    }
    if(self.cancelFn) runFn()
    return this
  }

  self.line = function(_x) {
    if (!arguments.length) return line
    line = _x
    return this
  }

  self.yDomain = function(_x) {
    if (!arguments.length) return y.domain()
    y.domain(_x)
    return this
  }

  self.width = function(_x) {
    if (!arguments.length) return width
    width = _x
    return this
  }

  self.height = function(_x) {
    if (!arguments.length) return height
    height = _x
    return this
  }

  // expecting {top: 40, right:60, bottom: 200, left: 60}
  self.margin = function(_x) {
    if (!arguments.length) return margin
    margin = _x
    return this
  }

  self.ticks = function(_x) {
    if (!arguments.length) return x
    ticks = _x
    // if(width>0){
    //   self.draw(parentEl, chartId)
    // }
    return this
  }

  self.x = function(_x) {
    if (!arguments.length) return x
    x = _x
    return this
  }

  self.y = function(_x) {
    if (!arguments.length) return y
    y = _x
    return this
  }

  self.area = function(_x) {
    if (!arguments.length) return areaToShow
    areaToShow.push(_x)
    if(width>0){
      self.draw(parentEl, chartId)
    }
    return this
  }

  self.yAxis = function(_x) {
    if (!arguments.length) return yAxis
    yAxis = _x
    return this
  }

  self.isAnimating = function(_x) {
    if (!arguments.length) return isAnimating
    isAnimating = _x
    return this
  }

  self.cancelFn = function(_x) {
    if (!arguments.length) return cancelFn
    cancelFn = _x
    return this
  }

  self.useVoronoi = function(_x) {
    if (!arguments.length) return useVoronoi
    useVoronoi = _x

    d3.select('#'+toSlug(chartId)).select('.voronoi').remove()


    if(width>0){
      self.draw(parentEl, chartId)
    }
    return this
  }

  self.persist= function(_x) {
    //expects [latin-name]
    if (!arguments.length) {
      return persisted
    }else{
      if(_x.length>0){
        var index = persisted.indexOf(_x)
        if(index<0){
          persisted = persisted.concat(_x)
        }else{
          persisted.splice(index,1)
        }
      }else{
        persisted=[]
      }
    }
    persisted = _.uniq(persisted)

    // cross = clock.append('g')
    //       .classed('logo',true)

    // cross.append("circle")
    //       .attr("r", innerBound)
    //       .attr('fill','#fff')

    // cross.append('path')
    //       .attr('d', d3.svg.symbol().type("cross").size(30))
    //       .attr('transform','rotate(45)')
    //       .style({
    //         'fill':'rgb(236,47,45)',
    //         'fill-opacity': 1
    //       })

    return this
  }

  self.persistpop = function(_x) {
    if (!arguments.length) return chartId
    chartId = _x
    return this
  }

  self.id = function(_x) {
    if (!arguments.length) return chartId
    chartId = _x
    return this
  }

  self.el= function() {
    if (!arguments.length) return d3.select(parentEl)
    return this
  }

  self.svg= function() {
    if (!arguments.length) return chart
    return this
  }

  self.color = function(_x) {
    if (!arguments.length) return color
    color = _x
    self.draw(parentEl, chartId)

    return this
  }

  if(width>0){
    self.draw(parentEl, chartId)
  }

  d3.rebind(self,dispatch,'on')
  return self;
}

/* Utils Specific to this project */

function pathExtent (data) {
  var extentX = []
  var extentY = []
  _.each(data, function(c) {
    extentX = extentX.concat(Math.round(parseFloat(c.split(',')[0])))
    extentY = extentY.concat(Math.round(parseFloat(c.split(',')[1])))
  })
  return [d3.extent(extentX),d3.extent(extentY)]
}

function collectionRangeExtent (data) {
  var extent = []
  _.each(data, function(c) {
    extent = extent.concat(c.values.map(function(y) {return parseFloat(y.value)}))
  })
  return d3.extent(extent)
}
function collectionDomainExtent (data) {
  var extent = []
  _.each(data, function(c) {
    extent = extent.concat(c.values.map(function(d){return d.key.getTime()}))
  })
  return d3.extent(extent)
}