(function(){

"use strict";

var root = this,
        Chart = root.Chart,
        helpers = Chart.helpers;

var defaultConfig = {

        ///Boolean - Whether grid lines are shown across the chart
        scaleShowGridLines : true,

        //String - Colour of the grid lines
        scaleGridLineColor : "rgba(0,0,0,.05)",

        //Number - Width of the grid lines
        scaleGridLineWidth : 1,

        //Boolean - Whether to show horizontal lines (except X axis)
        scaleShowHorizontalLines: true,

        //Boolean - Whether to show vertical lines (except Y axis)
        scaleShowVerticalLines: true,

        //Boolean - Whether the line is curved between points
        bezierCurve : true,

        //Number - Tension of the bezier curve between points
        bezierCurveTension : 0.4,

        //Boolean - Whether to show a dot for each point
        pointDot : true,

        //Number - Radius of each point dot in pixels
        pointDotRadius : 4,

        //Number - Pixel width of point dot stroke
        pointDotStrokeWidth : 1,

        //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
        pointHitDetectionRadius : 20,

        //Boolean - Whether to show a stroke for datasets
        datasetStroke : true,

        //Number - Pixel width of dataset stroke
        datasetStrokeWidth : 2,

        //Boolean - Whether to fill the dataset with a colour
        datasetFill : true,

        //String - A legend template
        legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"

};

Chart.Type.extend({
        name: "Line",
        defaults : defaultConfig,
        initialize:  function(data){
                //Declare the extension of the default point, to cater for the options passed in to the constructor
                this.PointClass = Chart.Point.extend({
                        strokeWidth : this.options.pointDotStrokeWidth,
                        radius : this.options.pointDotRadius,
                        display: this.options.pointDot,
                        hitDetectionRadius : this.options.pointHitDetectionRadius,
                        ctx : this.chart.ctx,
                        inRange : function(mouseX){
                                return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
                        }
                });

                this.datasets = [];

                //Set up tooltip events on the chart
                if (this.options.showTooltips){
                        helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
                                var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
                                this.eachPoints(function(point){
                                        point.restore(['fillColor', 'strokeColor']);
                                });
                                helpers.each(activePoints, function(activePoint){
                                        activePoint.fillColor = activePoint.highlightFill;
                                        activePoint.strokeColor = activePoint.highlightStroke;
                                });
                                this.showTooltip(activePoints);
                        });
                }

                //Iterate through each of the datasets, and build this into a property of the chart
                helpers.each(data.datasets,function(dataset){

                        var datasetObject = {
                                label : dataset.label || null,
                                fillColor : dataset.fillColor,
                                strokeColor : dataset.strokeColor,
                                pointColor : dataset.pointColor,
                                pointStrokeColor : dataset.pointStrokeColor,
                                points : []
                        };

                        this.datasets.push(datasetObject);

                        helpers.each(dataset.data,function(dataPoint,index){
                                //Add a new point for each piece of data, passing any required data to draw.
                                datasetObject.points.push(new this.PointClass({
                                        value : dataPoint,
                                        label : data.labels[index],
                                        datasetLabel: dataset.label,
                                        strokeColor : dataset.pointStrokeColor,
                                        fillColor : dataset.pointColor,
                                        highlightFill : dataset.pointHighlightFill || dataset.pointColor,
                                        highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
                                }));
                        },this);

                        this.buildScale(data.labels);

                        this.eachPoints(function(point, index){
                                helpers.extend(point, {
                                        x: this.scale.calculateX(index),
                                        y: this.scale.endPoint
                                });
                                point.save();
                        }, this);

                },this);

                this.render();
        },
        update : function(){
                this.scale.update();
                // Reset any highlight colours before updating.
                helpers.each(this.activeElements, function(activeElement){
                        activeElement.restore(['fillColor', 'strokeColor']);
                });
                this.eachPoints(function(point){
                        point.save();
                });
                this.render();
        },
        eachPoints : function(callback){
                helpers.each(this.datasets,function(dataset){
                        helpers.each(dataset.points,callback,this);
                },this);
        },
        getPointsAtEvent : function(e){
                var pointsArray = [],
                        eventPosition = helpers.getRelativePosition(e);
                helpers.each(this.datasets,function(dataset){
                        helpers.each(dataset.points,function(point){
                                if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
                        });
                },this);
                return pointsArray;
        },
        buildScale : function(labels){
                var self = this;

                var dataTotal = function(){
                        var values = [];
                        self.eachPoints(function(point){
                                values.push(point.value);
                        });

                        return values;
                };

                var scaleOptions = {
                        templateString : this.options.scaleLabel,
                        height : this.chart.height,
                        width : this.chart.width,
                        ctx : this.chart.ctx,
                        textColor : this.options.scaleFontColor,
                        fontSize : this.options.scaleFontSize,
                        fontStyle : this.options.scaleFontStyle,
                        fontFamily : this.options.scaleFontFamily,
                        valuesCount : labels.length,
                        beginAtZero : this.options.scaleBeginAtZero,
                        integersOnly : this.options.scaleIntegersOnly,
                        calculateYRange : function(currentHeight){
                                var updatedRanges = helpers.calculateScaleRange(
                                        dataTotal(),
                                        currentHeight,
                                        this.fontSize,
                                        this.beginAtZero,
                                        this.integersOnly
                                );
                                helpers.extend(this, updatedRanges);
                        },
                        xLabels : labels,
                        font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
                        lineWidth : this.options.scaleLineWidth,
                        lineColor : this.options.scaleLineColor,
                        showHorizontalLines : this.options.scaleShowHorizontalLines,
                        showVerticalLines : this.options.scaleShowVerticalLines,
                        gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
                        gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
                        padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
                        showLabels : this.options.scaleShowLabels,
                        display : this.options.showScale
                };

                if (this.options.scaleOverride){
                        helpers.extend(scaleOptions, {
                                calculateYRange: helpers.noop,
                                steps: this.options.scaleSteps,
                                stepValue: this.options.scaleStepWidth,
                                min: this.options.scaleStartValue,
                                max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
                        });
                }

                this.scale = new Chart.Scale(scaleOptions);
        },
        addData : function(valuesArray,label){
                //Map the values array for each of the datasets

                helpers.each(valuesArray,function(value,datasetIndex){
                        //Add a new point for each piece of data, passing any required data to draw.
                        this.datasets[datasetIndex].points.push(new this.PointClass({
                                value : value,
                                label : label,
                                x: this.scale.calculateX(this.scale.valuesCount+1),
                                y: this.scale.endPoint,
                                strokeColor : this.datasets[datasetIndex].pointStrokeColor,
                                fillColor : this.datasets[datasetIndex].pointColor
                        }));
                },this);

                this.scale.addXLabel(label);
                //Then re-render the chart.
                this.update();
        },
        removeData : function(){
                this.scale.removeXLabel();
                //Then re-render the chart.
                helpers.each(this.datasets,function(dataset){
                        dataset.points.shift();
                },this);
                this.update();
        },
        reflow : function(){
                var newScaleProps = helpers.extend({
                        height : this.chart.height,
                        width : this.chart.width
                });
                this.scale.update(newScaleProps);
        },
        draw : function(ease){
                var easingDecimal = ease || 1;
                this.clear();

                var ctx = this.chart.ctx;

                // Some helper methods for getting the next/prev points
                var hasValue = function(item){
                        return item.value !== null;
                },
                nextPoint = function(point, collection, index){
                        return helpers.findNextWhere(collection, hasValue, index) || point;
                },
                previousPoint = function(point, collection, index){
                        return helpers.findPreviousWhere(collection, hasValue, index) || point;
                };

                this.scale.draw(easingDecimal);

                helpers.each(this.datasets,function(dataset){
                        var pointsWithValues = helpers.where(dataset.points, hasValue);

                        //Transition each point first so that the line and point drawing isn't out of sync
                        //We can use this extra loop to calculate the control points of this dataset also in this loop

                        helpers.each(dataset.points, function(point, index){
                                if (point.hasValue()){
                                        point.transition({
                                                y : this.scale.calculateY(point.value),
                                                x : this.scale.calculateX(index)
                                        }, easingDecimal);
                                }
                        },this);

                        // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
                        // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
                        if (this.options.bezierCurve){
                                helpers.each(pointsWithValues, function(point, index){
                                        var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
                                        point.controlPoints = helpers.splineCurve(
                                                previousPoint(point, pointsWithValues, index),
                                                point,
                                                nextPoint(point, pointsWithValues, index),
                                                tension
                                        );

                                        // Prevent the bezier going outside of the bounds of the graph

                                        // Cap puter bezier handles to the upper/lower scale bounds
                                        if (point.controlPoints.outer.y > this.scale.endPoint){
                                                point.controlPoints.outer.y = this.scale.endPoint;
                                        }
                                        else if (point.controlPoints.outer.y < this.scale.startPoint){
                                                point.controlPoints.outer.y = this.scale.startPoint;
                                        }

                                        // Cap inner bezier handles to the upper/lower scale bounds
                                        if (point.controlPoints.inner.y > this.scale.endPoint){
                                                point.controlPoints.inner.y = this.scale.endPoint;
                                        }
                                        else if (point.controlPoints.inner.y < this.scale.startPoint){
                                                point.controlPoints.inner.y = this.scale.startPoint;
                                        }
                                },this);
                        }

                        //Draw the line between all the points
                        ctx.lineWidth = this.options.datasetStrokeWidth;
                        ctx.strokeStyle = dataset.strokeColor;
                        ctx.beginPath();

                        helpers.each(pointsWithValues, function(point, index){
                                if (index === 0){
                                        ctx.moveTo(point.x, point.y);
                                }
                                else{
                                        if(this.options.bezierCurve){
                                                var previous = previousPoint(point, pointsWithValues, index);

                                                ctx.bezierCurveTo(
                                                        previous.controlPoints.outer.x,
                                                        previous.controlPoints.outer.y,
                                                        point.controlPoints.inner.x,
                                                        point.controlPoints.inner.y,
                                                        point.x,
                                                        point.y
                                                );
                                        }
                                        else{
                                                ctx.lineTo(point.x,point.y);
                                        }
                                }
                        }, this);

                        ctx.stroke();

                        if (this.options.datasetFill && pointsWithValues.length > 0){
                                //Round off the line by going to the base of the chart, back to the start, then fill.
                                ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
                                ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
                                ctx.fillStyle = dataset.fillColor;
                                ctx.closePath();
                                ctx.fill();
                        }

                        //Now draw the points over the line
                        //A little inefficient double looping, but better than the line
                        //lagging behind the point positions
                        helpers.each(pointsWithValues,function(point){
                                point.draw();
                        });
                },this);
        }
});

}).call(this);