/**
 * Highcharts JS v4.1.8 (2015-08-20)
 * Highcharts Broken Axis module
 * 
 * Author: Stephane Vanraes, Torstein Honsi
 * License: www.highcharts.com/license
 */

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

	"use strict";

	var pick = H.pick,
		wrap = H.wrap,
		extend = H.extend,
		fireEvent = HighchartsAdapter.fireEvent,
		Axis = H.Axis,
		Series = H.Series;

	function stripArguments() {
		return Array.prototype.slice.call(arguments, 1);
	}

	extend(Axis.prototype, {
		isInBreak: function (brk, val) {
			var ret,
				repeat = brk.repeat || Infinity,
				from = brk.from,
				length = brk.to - brk.from,
				test = (val >= from ? (val - from) % repeat :  repeat - ((from - val) % repeat));

			if (!brk.inclusive) {
				ret = test < length && test !== 0;
			} else {
				ret = test <= length;
			}
			return ret;
		},

		isInAnyBreak: function (val, testKeep) {

			var breaks = this.options.breaks,
				i = breaks && breaks.length,
				inbrk,
				keep,
				ret;

			
			if (i) { 

				while (i--) {
					if (this.isInBreak(breaks[i], val)) {
						inbrk = true;
						if (!keep) {
							keep = pick(breaks[i].showPoints, this.isXAxis ? false : true);
						}
					}
				}

				if (inbrk && testKeep) {
					ret = inbrk && !keep;
				} else {
					ret = inbrk;
				}
			}
			return ret;
		}
	});

	wrap(Axis.prototype, 'setTickPositions', function (proceed) {
		proceed.apply(this, Array.prototype.slice.call(arguments, 1));
		
		if (this.options.breaks) {
			var axis = this,
				tickPositions = this.tickPositions,
				info = this.tickPositions.info,
				newPositions = [],
				i;

			if (info && info.totalRange >= axis.closestPointRange) { 
				return;
			}

			for (i = 0; i < tickPositions.length; i++) {
				if (!axis.isInAnyBreak(tickPositions[i])) {
					newPositions.push(tickPositions[i]);
				}
			}

			this.tickPositions = newPositions;
			this.tickPositions.info = info;
		}
	});
	
	wrap(Axis.prototype, 'init', function (proceed, chart, userOptions) {
		// Force Axis to be not-ordinal when breaks are defined
		if (userOptions.breaks && userOptions.breaks.length) {
			userOptions.ordinal = false;
		}

		proceed.call(this, chart, userOptions);

		if (this.options.breaks) {

			var axis = this;
			
			axis.doPostTranslate = true;

			this.val2lin = function (val) {
				var nval = val,
					brk,
					i;

				for (i = 0; i < axis.breakArray.length; i++) {
					brk = axis.breakArray[i];
					if (brk.to <= val) {
						nval -= brk.len;
					} else if (brk.from >= val) {
						break;
					} else if (axis.isInBreak(brk, val)) {
						nval -= (val - brk.from);
						break;
					}
				}

				return nval;
			};
			
			this.lin2val = function (val) {
				var nval = val,
					brk,
					i;

				for (i = 0; i < axis.breakArray.length; i++) {
					brk = axis.breakArray[i];
					if (brk.from >= nval) {
						break;
					} else if (brk.to < nval) {
						nval += brk.len;
					} else if (axis.isInBreak(brk, nval)) {
						nval += brk.len;
					}
				}
				return nval;
			};

			this.setExtremes = function (newMin, newMax, redraw, animation, eventArguments) {
				// If trying to set extremes inside a break, extend it to before and after the break ( #3857 )
				while (this.isInAnyBreak(newMin)) {
					newMin -= this.closestPointRange;
				}				
				while (this.isInAnyBreak(newMax)) {
					newMax -= this.closestPointRange;
				}
				Axis.prototype.setExtremes.call(this, newMin, newMax, redraw, animation, eventArguments);
			};

			this.setAxisTranslation = function (saveOld) {
				Axis.prototype.setAxisTranslation.call(this, saveOld);

				var breaks = axis.options.breaks,
					breakArrayT = [],	// Temporary one
					breakArray = [],
					length = 0, 
					inBrk,
					repeat,
					brk,
					min = axis.userMin || axis.min,
					max = axis.userMax || axis.max,
					start,
					i,
					j;

				// Min & max check (#4247)
				for (i in breaks) {
					brk = breaks[i];
					repeat = brk.repeat || Infinity;
					if (axis.isInBreak(brk, min)) {
						min += (brk.to % repeat) - (min % repeat);
					}
					if (axis.isInBreak(brk, max)) {
						max -= (max % repeat) - (brk.from % repeat);
					}
				}

				// Construct an array holding all breaks in the axis
				for (i in breaks) {
					brk = breaks[i];
					start = brk.from;
					repeat = brk.repeat || Infinity;

					while (start - repeat > min) {
						start -= repeat;
					}
					while (start < min) {
						start += repeat;
					}

					for (j = start; j < max; j += repeat) {
						breakArrayT.push({
							value: j,
							move: 'in'
						});
						breakArrayT.push({
							value: j + (brk.to - brk.from),
							move: 'out',
							size: brk.breakSize
						});
					}
				}

				breakArrayT.sort(function (a, b) {
					if (a.value === b.value) {
						return (a.move === 'in' ? 0 : 1) - (b.move === 'in' ? 0 : 1);
					} else {
						return a.value - b.value;
					}
				});
				
				// Simplify the breaks
				inBrk = 0;
				start = min;

				for (i in breakArrayT) {
					brk = breakArrayT[i];
					inBrk += (brk.move === 'in' ? 1 : -1);

					if (inBrk === 1 && brk.move === 'in') {
						start = brk.value;
					}
					if (inBrk === 0) {
						breakArray.push({
							from: start,
							to: brk.value,
							len: brk.value - start - (brk.size || 0)
						});
						length += brk.value - start - (brk.size || 0);
					}
				}

				axis.breakArray = breakArray;

				fireEvent(axis, 'afterBreaks');
				
				axis.transA *= ((max - axis.min) / (max - min - length));

				axis.min = min;
				axis.max = max;
			};
		}
	});

	wrap(Series.prototype, 'generatePoints', function (proceed) {

		proceed.apply(this, stripArguments(arguments));

		var series = this,
			xAxis = series.xAxis,
			yAxis = series.yAxis,
			points = series.points,
			point,
			i = points.length,
			connectNulls = series.options.connectNulls,
			nullGap;


		if (xAxis && yAxis && (xAxis.options.breaks || yAxis.options.breaks)) {
			while (i--) {
				point = points[i];

				nullGap = point.y === null && connectNulls === false; // respect nulls inside the break (#4275)
				if (!nullGap && (xAxis.isInAnyBreak(point.x, true) || yAxis.isInAnyBreak(point.y, true))) {
					points.splice(i, 1);
					if (this.data[i]) {
						this.data[i].destroyElements(); // removes the graphics for this point if they exist
					}
				}
			}
		}

	});

	wrap(H.seriesTypes.column.prototype, 'drawPoints', function (proceed) {
		proceed.apply(this);

		var series = this,
			points = series.points,
			yAxis = series.yAxis,
			breaks = yAxis.breakArray || [],
			threshold = pick(this.options.threshold, yAxis.min),
			eventName,
			point,
			brk,
			i,
			j,
			y;

		for (i = 0; i < points.length; i++) {
			point = points[i];
			y = point.stackY || point.y;
			for (j = 0; j < breaks.length; j++) {
				brk = breaks[j];
				eventName = false;

				if ((threshold < brk.from && y > brk.to) || (threshold > brk.from && y < brk.from)) { 
						eventName = 'pointBreak';
				} else if ((threshold < brk.from && y > brk.from && y < brk.to) || (threshold > brk.from && y > brk.to && y < brk.from)) { // point falls inside the break
						eventName = 'pointInBreak'; // docs
				} 
				if (eventName) {
					fireEvent(yAxis, eventName, {point: point, brk: brk});
				}
			}
		}

	});
}(Highcharts));
