/* eslint-disable */
// vim: ts=2 sw=2
(function () {
    d3.timeline = function() {
        var DISPLAY_TYPES = ['circle', 'rect'];
  
        var hover = function () {},
            mouseover = function () {},
            mouseout = function () {},
            click = function () {},
            scroll = function () {},
            labelFunction = function(label) { return label; },
            navigateLeft = function () {},
            navigateRight = function () {},
            orient = 'bottom',
            width = null,
            height = null,
            rowSeparatorsColor = null,
            backgroundColor = null,
            tickFormat = { format: d3.time.format('%I %p'),
                tickTime: d3.time.hours,
                tickInterval: 1,
                tickSize: 6,
                tickValues: null
            },
            colorCycle = d3.scale.category20(),
            colorPropertyName = null,
            display = 'rect',
            beginning = 0,
            labelMargin = 0,
            ending = 0,
            margin = {left: 30, right:30, top: 30, bottom:30},
            stacked = false,
            rotateTicks = false,
            timeIsRelative = false,
            fullLengthBackgrounds = false,
            itemHeight = 20,
            itemMargin = 5,
            navMargin = 60,
            showTimeAxis = true,
            showAxisTop = false,
            showTodayLine = false,
            timeAxisTick = false,
            timeAxisTickFormat = {stroke: 'stroke-dasharray', spacing: '4 10'},
            showTodayFormat = {marginTop: 25, marginBottom: 0, width: 1, color: colorCycle},
            showBorderLine = false,
            showBorderFormat = {marginTop: 25, marginBottom: 0, width: 1, color: colorCycle},
            showAxisHeaderBackground = false,
            showAxisNav = false,
            showAxisCalendarYear = false,
            axisBgColor = 'white',
            chartData = {}
        ;
  
        var appendTimeAxis = function(g, xAxis, yPosition) {
  
            if(showAxisHeaderBackground){ appendAxisHeaderBackground(g, 0, 0); }
  
            if(showAxisNav){ appendTimeAxisNav(g); }
  
            var axis = g.append('g')
                .attr('class', 'axis')
                .attr('transform', 'translate(' + 0 + ',' + yPosition + ')')
                .call(xAxis);
        };
  
        var appendTimeAxisCalendarYear = function (nav) {
            var calendarLabel = beginning.getFullYear();
  
            if (beginning.getFullYear() != ending.getFullYear()) {
                calendarLabel = beginning.getFullYear() + '-' + ending.getFullYear();
            }
  
            nav.append('text')
                .attr('transform', 'translate(' + 20 + ', 0)')
                .attr('x', 0)
                .attr('y', 14)
                .attr('class', 'calendarYear')
                .text(calendarLabel)
            ;
        };
        var appendTimeAxisNav = function (g) {
            var timelineBlocks = 6;
            var leftNavMargin = (margin.left - navMargin);
            var incrementValue = (width - margin.left)/timelineBlocks;
            var rightNavMargin = (width - margin.right - incrementValue + navMargin);
  
            var nav = g.append('g')
                .attr('class', 'axis')
                .attr('transform', 'translate(0, 20)')
          ;
  
            if(showAxisCalendarYear) { appendTimeAxisCalendarYear(nav); }
  
            nav.append('text')
                .attr('transform', 'translate(' + leftNavMargin + ', 0)')
                .attr('x', 0)
                .attr('y', 14)
                .attr('class', 'chevron')
                .text('<')
                .on('click', function () {
                    return navigateLeft(beginning, chartData);
                })
            ;
  
            nav.append('text')
                .attr('transform', 'translate(' + rightNavMargin + ', 0)')
                .attr('x', 0)
                .attr('y', 14)
                .attr('class', 'chevron')
                .text('>')
                .on('click', function () {
                    return navigateRight(ending, chartData);
                })
            ;
        };
  
        var appendAxisHeaderBackground = function (g, xAxis, yAxis) {
            g.insert('rect')
                .attr('class', 'row-green-bar')
                .attr('x', xAxis)
                .attr('width', width)
                .attr('y', yAxis)
                .attr('height', itemHeight)
                .attr('fill', axisBgColor);
        };
  
        var appendTimeAxisTick = function(g, xAxis, maxStack) {
            g.append('g')
                .attr('class', 'axis')
                .attr('transform', 'translate(' + 0 + ',' + (margin.top + (itemHeight + itemMargin) * maxStack) + ')')
                .attr(timeAxisTickFormat.stroke, timeAxisTickFormat.spacing)
                .call(xAxis.tickFormat('').tickSize(-(margin.top + (itemHeight + itemMargin) * (maxStack - 1) + 3), 0, 0));
        };
  
        var appendBackgroundBar = function (yAxisMapping, index, g, data, datum) {
            var greenbarYAxis = ((itemHeight + itemMargin) * yAxisMapping[index]) + margin.top;
            g.selectAll('svg').data(data).enter()
                .insert('rect')
                .attr('class', 'row-green-bar')
                .attr('x', fullLengthBackgrounds ? 0 : margin.left)
                .attr('width', fullLengthBackgrounds ? width : (width - margin.right - margin.left))
                .attr('y', greenbarYAxis)
                .attr('height', itemHeight)
                .attr('fill', backgroundColor instanceof Function ? backgroundColor(datum, index) : backgroundColor)
            ;
        };
  
        var appendLabel = function (gParent, yAxisMapping, index, hasLabel, datum) {
            var fullItemHeight    = itemHeight + itemMargin;
            var rowsDown          = margin.top + (fullItemHeight/2) + fullItemHeight * (yAxisMapping[index] || 1);
  
            gParent.append('text')
                .attr('class', 'timeline-label')
                .attr('transform', 'translate(' + labelMargin + ',' + rowsDown + ')')
                .text(hasLabel ? labelFunction(datum.label) : datum.id)
                .on('click', function (d, i) { click(d, index, datum); });
        };
  
        function timeline (gParent) {
            var g = gParent.append('g');
            var gParentSize = gParent[0][0].getBoundingClientRect();
  
            var gParentItem = d3.select(gParent[0][0]);
  
            var yAxisMapping = {},
                maxStack = 1,
                minTime = 0,
                maxTime = 0;
  
            setWidth();
  
            // check if the user wants relative time
            // if so, substract the first timestamp from each subsequent timestamps
            if(timeIsRelative){
                g.each(function (d, i) {
                    d.forEach(function (datum, index) {
                        datum.times.forEach(function (time, j) {
                            if(index === 0 && j === 0){
                                originTime = time.starting_time;               //Store the timestamp that will serve as origin
                                time.starting_time = 0;                        //Set the origin
                                time.ending_time = time.ending_time - originTime;     //Store the relative time (millis)
                            }else{
                                time.starting_time = time.starting_time - originTime;
                                time.ending_time = time.ending_time - originTime;
                            }
                        });
                    });
                });
            }
  
            // check how many stacks we're gonna need
            // do this here so that we can draw the axis before the graph
            if (stacked || ending === 0 || beginning === 0) {
                g.each(function (d, i) {
                    d.forEach(function (datum, index) {
  
                        // create y mapping for stacked graph
                        if (stacked && Object.keys(yAxisMapping).indexOf(index) == -1) {
                            yAxisMapping[index] = maxStack;
                            maxStack++;
                        }
  
                        // figure out beginning and ending times if they are unspecified
                        datum.times.forEach(function (time, i) {
                            if(beginning === 0)
                            {if (time.starting_time < minTime || (minTime === 0 && timeIsRelative === false))
                            {minTime = time.starting_time;}}
                            if(ending === 0)
                            {if (time.ending_time > maxTime)
                            {maxTime = time.ending_time;}}
                        });
                    });
                });
  
                if (ending === 0) {
                    ending = maxTime;
                }
                if (beginning === 0) {
                    beginning = minTime;
                }
            }
  
            var scaleFactor = (1/(ending - beginning)) * (width - margin.left - margin.right);
  
            // draw the axis
            var xScale = d3.time.scale()
                .domain([beginning, ending])
                .range([margin.left, width - margin.right]);
  
            var xAxis = d3.svg.axis()
                .scale(xScale)
                .orient(orient)
                .tickFormat(tickFormat.format)
                .tickSize(tickFormat.tickSize);
  
            if (tickFormat.tickValues != null) {
                xAxis.tickValues(tickFormat.tickValues);
            } else {
                xAxis.ticks(tickFormat.numTicks || tickFormat.tickTime, tickFormat.tickInterval);
            }
  
            // draw the chart
            g.each(function(d, i) {
                chartData = d;
                d.forEach( function(datum, index){
                    var data = datum.times;
                    var hasLabel = (typeof(datum.label) != 'undefined');
  
                    // issue warning about using id per data set. Ids should be individual to data elements
                    if (typeof(datum.id) != 'undefined') {
                        console.warn('d3Timeline Warning: Ids per dataset is deprecated in favor of a \'class\' key. Ids are now per data element.');
                    }
  
                    if (backgroundColor) { appendBackgroundBar(yAxisMapping, index, g, data, datum); }
  
                    g.selectAll('svg').data(data).enter()
                        .append(function(d, i) {
                            return document.createElementNS(d3.ns.prefix.svg, 'display' in d? d.display:display);
                        })
                        .attr('x', getXPos)
                        .attr('y', getStackPosition)
                        .attr('width', function (d, i) {
                            return (d.ending_time - d.starting_time) * scaleFactor;
                        })
                        .attr('cy', function(d, i) {
                            return getStackPosition(d, i) + itemHeight/2;
                        })
                        .attr('cx', getXPos)
                        .attr('r', itemHeight / 2)
                        .attr('height', itemHeight)
                        .style('fill', function(d, i){
                            var dColorPropName;
                            if (d.color) {return d.color;}
                            if( colorPropertyName ){
                                dColorPropName = d[colorPropertyName];
                                if ( dColorPropName ) {
                                    return colorCycle( dColorPropName );
                                } else {
                                    return colorCycle( datum[colorPropertyName] );
                                }
                            }
                            return colorCycle(index);
                        })
                        .on('mousemove', function (d, i) {
                            hover(d, index, datum);
                        })
                        .on('mouseover', function (d, i) {
                            mouseover(d, i, datum);
                        })
                        .on('mouseout', function (d, i) {
                            mouseout(d, i, datum);
                        })
                        .on('click', function (d, i) {
                            click(d, index, datum);
                        })
                        .attr('class', function (d, i) {
                            return datum.class ? 'timelineSeries_'+datum.class : 'timelineSeries_'+index;
                        })
                        .attr('id', function(d, i) {
                            // use deprecated id field
                            if (datum.id && !d.id) {
                                return 'timelineItem_'+datum.id;
                            }
  
                            return d.id ? d.id : 'timelineItem_'+index+'_'+i;
                        })
                    ;
  
                    g.selectAll('svg').data(data).enter()
                        .append('text')
                        .attr('x', getXTextPos)
                        .attr('y', getStackTextPosition)
                        .text(function(d) {
                            return d.label;
                        })
                    ;
  
                    if (rowSeparatorsColor) {
                        var lineYAxis = ( itemHeight + itemMargin / 2 + margin.top + (itemHeight + itemMargin) * yAxisMapping[index]);
                        gParent.append('svg:line')
                            .attr('class', 'row-separator')
                            .attr('x1', 0 + margin.left)
                            .attr('x2', width - margin.right)
                            .attr('y1', lineYAxis)
                            .attr('y2', lineYAxis)
                            .attr('stroke-width', 1)
                            .attr('stroke', rowSeparatorsColor);
                    }
  
                    // add the label
                    if (hasLabel) { appendLabel(gParent, yAxisMapping, index, hasLabel, datum); }
  
                    if (typeof(datum.icon) !== 'undefined') {
                        gParent.append('image')
                            .attr('class', 'timeline-label')
                            .attr('transform', 'translate('+ 0 +','+ (margin.top + (itemHeight + itemMargin) * yAxisMapping[index])+')')
                            .attr('xlink:href', datum.icon)
                            .attr('width', margin.left)
                            .attr('height', itemHeight);
                    }
  
                    function getStackPosition(d, i) {
                        if (stacked) {
                            return margin.top + (itemHeight + itemMargin) * yAxisMapping[index];
                        }
                        return margin.top;
                    }
                    function getStackTextPosition(d, i) {
                        if (stacked) {
                            return margin.top + (itemHeight + itemMargin) * yAxisMapping[index] + itemHeight * 0.75;
                        }
                        return margin.top + itemHeight * 0.75;
                    }
                });
            });
  
            var belowLastItem = (margin.top + (itemHeight + itemMargin) * maxStack);
            var aboveFirstItem = margin.top;
            var timeAxisYPosition = showAxisTop ? aboveFirstItem : belowLastItem;
            if (showTimeAxis) { appendTimeAxis(g, xAxis, timeAxisYPosition); }
            if (timeAxisTick) { appendTimeAxisTick(g, xAxis, maxStack); }
  
            if (width > gParentSize.width) {
                var move = function() {
                    var x = Math.min(0, Math.max(gParentSize.width - width, d3.event.translate[0]));
                    zoom.translate([x, 0]);
                    g.attr('transform', 'translate(' + x + ',0)');
                    scroll(x*scaleFactor, xScale);
                };
  
                var zoom = d3.behavior.zoom().x(xScale).on('zoom', move);
  
                gParent
                    .attr('class', 'scrollable')
                    .call(zoom);
            }
  
            if (rotateTicks) {
                g.selectAll('.tick text')
                    .attr('transform', function(d) {
                        return 'rotate(' + rotateTicks + ')translate('
                + (this.getBBox().width / 2 + 10) + ',' // TODO: change this 10
                + this.getBBox().height / 2 + ')';
                    });
            }
  
            var gSize = g[0][0].getBoundingClientRect();
            setHeight();
  
            if (showBorderLine) {
                g.each(function (d, i) {
                    d.forEach(function (datum) {
                        var times = datum.times;
                        times.forEach(function (time) {
                            appendLine(xScale(time.starting_time), showBorderFormat);
                            appendLine(xScale(time.ending_time), showBorderFormat);
                        });
                    });
                });
            }
  
            if (showTodayLine) {
                var todayLine = xScale(new Date());
                appendLine(todayLine, showTodayFormat);
            }
  
            function getXPos(d, i) {
                return margin.left + (d.starting_time - beginning) * scaleFactor;
            }
  
            function getXTextPos(d, i) {
                return margin.left + (d.starting_time - beginning) * scaleFactor + 5;
            }
  
            function setHeight() {
                if (!height && !gParentItem.attr('height')) {
                    if (itemHeight) {
                        // set height based off of item height
                        height = gSize.height + gSize.top - gParentSize.top;
                        // set bounding rectangle height
                        d3.select(gParent[0][0]).attr('height', height);
                    } else {
                        throw 'height of the timeline is not set';
                    }
                } else {
                    if (!height) {
                        height = gParentItem.attr('height');
                    } else {
                        gParentItem.attr('height', height);
                    }
                }
            }
  
            function setWidth() {
                if (!width && !gParentSize.width) {
                    try {
                        width = gParentItem.attr('width');
                        if (!width) {
                            throw 'width of the timeline is not set. As of Firefox 27, timeline().with(x) needs to be explicitly set in order to render';
                        }
                    } catch (err) {
                        console.log( err );
                    }
                } else if (!(width && gParentSize.width)) {
                    try {
                        width = gParentItem.attr('width');
                    } catch (err) {
                        console.log( err );
                    }
                }
                // if both are set, do nothing
            }
  
            function appendLine(lineScale, lineFormat) {
                gParent.append('svg:line')
                    .attr('x1', lineScale)
                    .attr('y1', lineFormat.marginTop)
                    .attr('x2', lineScale)
                    .attr('y2', height - lineFormat.marginBottom)
                    .style('stroke', lineFormat.color)//"rgb(6,120,155)")
                    .style('stroke-width', lineFormat.width);
            }
  
        }
  
        // SETTINGS
  
        timeline.margin = function (p) {
            if (!arguments.length) {return margin;}
            margin = p;
            return timeline;
        };
  
        timeline.orient = function (orientation) {
            if (!arguments.length) {return orient;}
            orient = orientation;
            return timeline;
        };
  
        timeline.itemHeight = function (h) {
            if (!arguments.length) {return itemHeight;}
            itemHeight = h;
            return timeline;
        };
  
        timeline.itemMargin = function (h) {
            if (!arguments.length) {return itemMargin;}
            itemMargin = h;
            return timeline;
        };
  
        timeline.navMargin = function (h) {
            if (!arguments.length) {return navMargin;}
            navMargin = h;
            return timeline;
        };
  
        timeline.height = function (h) {
            if (!arguments.length) {return height;}
            height = h;
            return timeline;
        };
  
        timeline.width = function (w) {
            if (!arguments.length) {return width;}
            width = w;
            return timeline;
        };
  
        timeline.display = function (displayType) {
            if (!arguments.length || (DISPLAY_TYPES.indexOf(displayType) == -1)) {return display;}
            display = displayType;
            return timeline;
        };
  
        timeline.labelFormat = function(f) {
            if (!arguments.length) {return labelFunction;}
            labelFunction = f;
            return timeline;
        };
  
        timeline.tickFormat = function (format) {
            if (!arguments.length) {return tickFormat;}
            tickFormat = format;
            return timeline;
        };
  
        timeline.hover = function (hoverFunc) {
            if (!arguments.length) {return hover;}
            hover = hoverFunc;
            return timeline;
        };
  
        timeline.mouseover = function (mouseoverFunc) {
            if (!arguments.length) {return mouseover;}
            mouseover = mouseoverFunc;
            return timeline;
        };
  
        timeline.mouseout = function (mouseoutFunc) {
            if (!arguments.length) {return mouseout;}
            mouseout = mouseoutFunc;
            return timeline;
        };
  
        timeline.click = function (clickFunc) {
            if (!arguments.length) {return click;}
            click = clickFunc;
            return timeline;
        };
  
        timeline.scroll = function (scrollFunc) {
            if (!arguments.length) {return scroll;}
            scroll = scrollFunc;
            return timeline;
        };
  
        timeline.colors = function (colorFormat) {
            if (!arguments.length) {return colorCycle;}
            colorCycle = colorFormat;
            return timeline;
        };
  
        timeline.beginning = function (b) {
            if (!arguments.length) {return beginning;}
            beginning = b;
            return timeline;
        };
  
        timeline.ending = function (e) {
            if (!arguments.length) {return ending;}
            ending = e;
            return timeline;
        };
  
        timeline.labelMargin = function (m) {
            if (!arguments.length) {return labelMargin;}
            labelMargin = m;
            return timeline;
        };
  
        timeline.rotateTicks = function (degrees) {
            if (!arguments.length) {return rotateTicks;}
            rotateTicks = degrees;
            return timeline;
        };
  
        timeline.stack = function () {
            stacked = !stacked;
            return timeline;
        };
  
        timeline.relativeTime = function() {
            timeIsRelative = !timeIsRelative;
            return timeline;
        };
  
        timeline.showBorderLine = function () {
            showBorderLine = !showBorderLine;
            return timeline;
        };
  
        timeline.showBorderFormat = function(borderFormat) {
            if (!arguments.length) {return showBorderFormat;}
            showBorderFormat = borderFormat;
            return timeline;
        };
  
        timeline.showToday = function () {
            showTodayLine = !showTodayLine;
            return timeline;
        };
  
        timeline.showTodayFormat = function(todayFormat) {
            if (!arguments.length) {return showTodayFormat;}
            showTodayFormat = todayFormat;
            return timeline;
        };
  
        timeline.colorProperty = function(colorProp) {
            if (!arguments.length) {return colorPropertyName;}
            colorPropertyName = colorProp;
            return timeline;
        };
  
        timeline.rowSeparators = function (color) {
            if (!arguments.length) {return rowSeparatorsColor;}
            rowSeparatorsColor = color;
            return timeline;
  
        };
  
        timeline.background = function (color) {
            if (!arguments.length) {return backgroundColor;}
            backgroundColor = color;
            return timeline;
        };
  
        timeline.showTimeAxis = function () {
            showTimeAxis = !showTimeAxis;
            return timeline;
        };
  
        timeline.showAxisTop = function () {
            showAxisTop = !showAxisTop;
            return timeline;
        };
  
        timeline.showAxisCalendarYear = function () {
            showAxisCalendarYear = !showAxisCalendarYear;
            return timeline;
        };
  
        timeline.showTimeAxisTick = function () {
            timeAxisTick = !timeAxisTick;
            return timeline;
        };
  
        timeline.fullLengthBackgrounds = function () {
            fullLengthBackgrounds = !fullLengthBackgrounds;
            return timeline;
        };
  
        timeline.showTimeAxisTickFormat = function(format) {
            if (!arguments.length) {return timeAxisTickFormat;}
            timeAxisTickFormat = format;
            return timeline;
        };
  
        timeline.showAxisHeaderBackground = function(bgColor) {
            showAxisHeaderBackground = !showAxisHeaderBackground;
            if(bgColor) { (axisBgColor = bgColor); }
            return timeline;
        };
  
        timeline.navigate = function (navigateBackwards, navigateForwards) {
            if (!arguments.length) {return [navigateLeft, navigateRight];}
            navigateLeft = navigateBackwards;
            navigateRight = navigateForwards;
            showAxisNav = !showAxisNav;
            return timeline;
        };
  
        return timeline;
    };
})();
  