/**
 * Highcharts Drilldown plugin
 * 
 * Author: Torstein Honsi
 * License: MIT License
 *
 * Demo: http://jsfiddle.net/highcharts/Vf3yT/
 */

/*global HighchartsAdapter*/
(function (H) {

	"use strict";

	var noop = function () {},
		defaultOptions = H.getOptions(),
		each = H.each,
		extend = H.extend,
		wrap = H.wrap,
		Chart = H.Chart,
		seriesTypes = H.seriesTypes,
		PieSeries = seriesTypes.pie,
		ColumnSeries = seriesTypes.column,
		fireEvent = HighchartsAdapter.fireEvent,
		inArray = HighchartsAdapter.inArray;

	// Utilities
	function tweenColors(startColor, endColor, pos) {
		var rgba = [
				Math.round(startColor[0] + (endColor[0] - startColor[0]) * pos),
				Math.round(startColor[1] + (endColor[1] - startColor[1]) * pos),
				Math.round(startColor[2] + (endColor[2] - startColor[2]) * pos),
				startColor[3] + (endColor[3] - startColor[3]) * pos
			];
		return 'rgba(' + rgba.join(',') + ')';
	}

	// Add language
	extend(defaultOptions.lang, {
		drillUpText: '◁ Back to {series.name}'
	});
	defaultOptions.drilldown = {
		activeAxisLabelStyle: {
			cursor: 'pointer',
			color: '#0d233a',
			fontWeight: 'bold',
			textDecoration: 'underline'			
		},
		activeDataLabelStyle: {
			cursor: 'pointer',
			color: '#0d233a',
			fontWeight: 'bold',
			textDecoration: 'underline'			
		},
		animation: {
			duration: 500
		},
		drillUpButton: {
			position: { 
				align: 'right',
				x: -10,
				y: 10
			}
			// relativeTo: 'plotBox'
			// theme
		}
	};	

	/**
	 * A general fadeIn method
	 */
	H.SVGRenderer.prototype.Element.prototype.fadeIn = function (animation) {
		this
		.attr({
			opacity: 0.1,
			visibility: 'visible'
		})
		.animate({
			opacity: 1
		}, animation || {
			duration: 250
		});
	};

	// Extend the Chart prototype
	Chart.prototype.drilldownLevels = [];

	Chart.prototype.addSeriesAsDrilldown = function (point, ddOptions) {
		var oldSeries = point.series,
			xAxis = oldSeries.xAxis,
			yAxis = oldSeries.yAxis,
			newSeries,
			color = point.color || oldSeries.color,
			pointIndex,
			level;
			
		ddOptions = extend({
			color: color
		}, ddOptions);
		pointIndex = inArray(point, oldSeries.points);
		
		level = {
			seriesOptions: oldSeries.userOptions,
			shapeArgs: point.shapeArgs,
			bBox: point.graphic.getBBox(),
			color: color,
			newSeries: ddOptions,
			pointOptions: oldSeries.options.data[pointIndex],
			pointIndex: pointIndex,
			oldExtremes: {
				xMin: xAxis && xAxis.userMin,
				xMax: xAxis && xAxis.userMax,
				yMin: yAxis && yAxis.userMin,
				yMax: yAxis && yAxis.userMax
			}
		};

		this.drilldownLevels.push(level);

		newSeries = this.addSeries(ddOptions, false);
		if (xAxis) {
			xAxis.oldPos = xAxis.pos;
			xAxis.userMin = xAxis.userMax = null;
			yAxis.userMin = yAxis.userMax = null;
		}

		// Run fancy cross-animation on supported and equal types
		if (oldSeries.type === newSeries.type) {
			newSeries.animate = newSeries.animateDrilldown || noop;
			newSeries.options.animation = true;
		}
		
		oldSeries.remove(false);
		
		this.redraw();
		this.showDrillUpButton();
	};

	Chart.prototype.getDrilldownBackText = function () {
		var lastLevel = this.drilldownLevels[this.drilldownLevels.length - 1];

		return this.options.lang.drillUpText.replace('{series.name}', lastLevel.seriesOptions.name);

	};

	Chart.prototype.showDrillUpButton = function () {
		var chart = this,
			backText = this.getDrilldownBackText(),
			buttonOptions = chart.options.drilldown.drillUpButton,
			attr,
			states;
			

		if (!this.drillUpButton) {
			attr = buttonOptions.theme;
			states = attr && attr.states;
						
			this.drillUpButton = this.renderer.button(
				backText,
				null,
				null,
				function () {
					chart.drillUp(); 
				},
				attr, 
				states && states.hover,
				states && states.select
			)
			.attr({
				align: buttonOptions.position.align,
				zIndex: 9
			})
			.add()
			.align(buttonOptions.position, false, buttonOptions.relativeTo || 'plotBox');
		} else {
			this.drillUpButton.attr({
				text: backText
			})
			.align();
		}
	};

	Chart.prototype.drillUp = function () {
		var chart = this,
			level = chart.drilldownLevels.pop(),
			oldSeries = chart.series[0],
			oldExtremes = level.oldExtremes,
			newSeries = chart.addSeries(level.seriesOptions, false);
		
		fireEvent(chart, 'drillup', { seriesOptions: level.seriesOptions });

		if (newSeries.type === oldSeries.type) {
			newSeries.drilldownLevel = level;
			newSeries.animate = newSeries.animateDrillupTo || noop;
			newSeries.options.animation = true;

			if (oldSeries.animateDrillupFrom) {
				oldSeries.animateDrillupFrom(level);
			}
		}

		oldSeries.remove(false);

		// Reset the zoom level of the upper series
		if (newSeries.xAxis) {
			newSeries.xAxis.setExtremes(oldExtremes.xMin, oldExtremes.xMax, false);
			newSeries.yAxis.setExtremes(oldExtremes.yMin, oldExtremes.yMax, false);
		}


		this.redraw();

		if (this.drilldownLevels.length === 0) {
			this.drillUpButton = this.drillUpButton.destroy();
		} else {
			this.drillUpButton.attr({
				text: this.getDrilldownBackText()
			})
			.align();
		}
	};

	PieSeries.prototype.animateDrilldown = function (init) {
		var level = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1],
			animationOptions = this.chart.options.drilldown.animation,
			animateFrom = level.shapeArgs,
			start = animateFrom.start,
			angle = animateFrom.end - start,
			startAngle = angle / this.points.length,
			startColor = H.Color(level.color).rgba;

		if (!init) {
			each(this.points, function (point, i) {
				var endColor = H.Color(point.color).rgba;

				/*jslint unparam: true*/
				point.graphic
					.attr(H.merge(animateFrom, {
						start: start + i * startAngle,
						end: start + (i + 1) * startAngle
					}))
					.animate(point.shapeArgs, H.merge(animationOptions, {
						step: function (val, fx) {
							if (fx.prop === 'start') {
								this.attr({
									fill: tweenColors(startColor, endColor, fx.pos)
								});
							}
						}
					}));
				/*jslint unparam: false*/
			});
		}
	};


	/**
	 * When drilling up, keep the upper series invisible until the lower series has
	 * moved into place
	 */
	PieSeries.prototype.animateDrillupTo = 
			ColumnSeries.prototype.animateDrillupTo = function (init) {
		if (!init) {
			var newSeries = this,
				level = newSeries.drilldownLevel;

			each(this.points, function (point) {
				point.graphic.hide();
				if (point.dataLabel) {
					point.dataLabel.hide();
				}
				if (point.connector) {
					point.connector.hide();
				}
			});


			// Do dummy animation on first point to get to complete
			setTimeout(function () {
				each(newSeries.points, function (point, i) {  
					// Fade in other points			  
					var verb = i === level.pointIndex ? 'show' : 'fadeIn';
					point.graphic[verb]();
					if (point.dataLabel) {
						point.dataLabel[verb]();
					}
					if (point.connector) {
						point.connector[verb]();
					}
				});
			}, Math.max(this.chart.options.drilldown.animation.duration - 50, 0));

			// Reset
			this.animate = noop;
		}

	};
	
	ColumnSeries.prototype.animateDrilldown = function (init) {
		var animateFrom = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1].shapeArgs,
			animationOptions = this.chart.options.drilldown.animation;
			
		if (!init) {

			animateFrom.x += (this.xAxis.oldPos - this.xAxis.pos);
	
			each(this.points, function (point) {
				point.graphic
					.attr(animateFrom)
					.animate(point.shapeArgs, animationOptions);
				if (point.dataLabel) {
					point.dataLabel.fadeIn(animationOptions);
				}
			});
		}
		
	};

	/**
	 * When drilling up, pull out the individual point graphics from the lower series
	 * and animate them into the origin point in the upper series.
	 */
	ColumnSeries.prototype.animateDrillupFrom = 
		PieSeries.prototype.animateDrillupFrom =
	function (level) {
		var animationOptions = this.chart.options.drilldown.animation,
			group = this.group;

		delete this.group;
		each(this.points, function (point) {
			var graphic = point.graphic,
				startColor = H.Color(point.color).rgba;

			delete point.graphic;

			/*jslint unparam: true*/
			graphic.animate(level.shapeArgs, H.merge(animationOptions, {

				step: function (val, fx) {
					if (fx.prop === 'start') {
						this.attr({
							fill: tweenColors(startColor, H.Color(level.color).rgba, fx.pos)
						});
					}
				},
				complete: function () {
					graphic.destroy();
					if (group) {
						group = group.destroy();
					}
				}
			}));
			/*jslint unparam: false*/
		});
	};
	
	H.Point.prototype.doDrilldown = function () {
		var series = this.series,
			chart = series.chart,
			drilldown = chart.options.drilldown,
			i = (drilldown.series || []).length,
			seriesOptions;
		
		while (i-- && !seriesOptions) {
			if (drilldown.series[i].id === this.drilldown) {
				seriesOptions = drilldown.series[i];
			}
		}

		// Fire the event. If seriesOptions is undefined, the implementer can check for 
		// seriesOptions, and call addSeriesAsDrilldown async if necessary.
		fireEvent(chart, 'drilldown', { 
			point: this,
			seriesOptions: seriesOptions
		});
		
		if (seriesOptions) {
			chart.addSeriesAsDrilldown(this, seriesOptions);
		}

	};
	
	wrap(H.Point.prototype, 'init', function (proceed, series, options, x) {
		var point = proceed.call(this, series, options, x),
			chart = series.chart,
			tick = series.xAxis && series.xAxis.ticks[x],
			tickLabel = tick && tick.label;
		
		if (point.drilldown) {
			
			// Add the click event to the point label
			H.addEvent(point, 'click', function () {
				point.doDrilldown();
			});
			
			// Make axis labels clickable
			if (tickLabel) {
				if (!tickLabel._basicStyle) {
					tickLabel._basicStyle = tickLabel.element.getAttribute('style');
				}
				tickLabel
					.addClass('highcharts-drilldown-axis-label')
					.css(chart.options.drilldown.activeAxisLabelStyle)
					.on('click', function () {
						if (point.doDrilldown) {
							point.doDrilldown();
						}
					});
					
			}
		} else if (tickLabel && tickLabel._basicStyle) {
			tickLabel.element.setAttribute('style', tickLabel._basicStyle);
		}
		
		return point;
	});

	wrap(H.Series.prototype, 'drawDataLabels', function (proceed) {
		var css = this.chart.options.drilldown.activeDataLabelStyle;

		proceed.call(this);

		each(this.points, function (point) {
			if (point.drilldown && point.dataLabel) {
				point.dataLabel
					.attr({
						'class': 'highcharts-drilldown-data-label'
					})
					.css(css)
					.on('click', function () {
						point.doDrilldown();
					});
			}
		});
	});

	// Mark the trackers with a pointer 
	ColumnSeries.prototype.supportsDrilldown = true;
	PieSeries.prototype.supportsDrilldown = true;
	var type, 
		drawTrackerWrapper = function (proceed) {
			proceed.call(this);
			each(this.points, function (point) {
				if (point.drilldown && point.graphic) {
					point.graphic
						.attr({
							'class': 'highcharts-drilldown-point'
						})
						.css({ cursor: 'pointer' });
				}
			});
		};
	for (type in seriesTypes) {
		if (seriesTypes[type].prototype.supportsDrilldown) {
			wrap(seriesTypes[type].prototype, 'drawTracker', drawTrackerWrapper);
		}
	}
		
}(Highcharts));
