/**
* Multicolor Series v2.2.7(2020-06-23)
*
* (c) 2012-2016 Black Label
*
* License: Creative Commons Attribution (CC)
*/
/* global Highcharts window document module:true */
/**
 * @fileoverview
 * @suppress {checkTypes}
 */
(function (factory) {
	if (typeof module === 'object' && module.exports) {
		module.exports = factory;
	} else {
		factory(Highcharts);
	}
}(function (H) {
	var each = H.each,
		seriesTypes = H.seriesTypes,
		pick = H.pick,
		UNDEFINED,
		NORMAL_STATE = '',
		VISIBLE = 'visible',
		HIDDEN = 'hidden',
		PREFIX = 'highcharts-',
		NONE = 'none',
		hasTouch = document.documentElement.ontouchstart !== UNDEFINED,
		TRACKER_FILL = 'rgba(192,192,192,' + (H.hasSVG ? 0.0001 : 0.002) + ')', // invisible but clickable
		M = 'M',
		L = 'L';
	
	// handle unsorted data, throw error anyway
	function error(code, stop) {
		var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code;
		if (stop) {
			throw msg;
		} else if (window.console) {
			console.log(msg); // eslint-disable-line
		}
	}
	
	/**
	If replacing L and M in tracker will be necessary use that getPath():
	
	function getPath(arr){
	var ret = [];
	each(arr, function(el, ind) {
	var len = el[0].length;
	for(var i = 0; i < len; i++){
	var p = el[0][i];
	if(p == M && ind != 0 && i == 0) {
	p = L;
	}
	ret.push(p);
	}
	});
	return ret;
	}
	**/
	
	
	function getPath(arr) {
		var ret = [];
		each(arr, function (el) {
			ret = ret.concat(el[0]);
		});
		return ret;
	}

	/**
	 * Return the graph path of a segment - compatibility with 4.2.3+
	 * @param {Object} segment of the path
	 * @returns {Array} Path (SVG)
	 */
	H.Series.prototype.getSegmentPath = function (segment) {
		var series = this,
			segmentPath = [],
			step = series.options.step;

		// build the segment line
		each(segment, function (point, i) {
			var plotX = point.plotX,
				plotY = point.plotY,
				lastPoint;

			if (series.getPointSpline) {
				// generate the spline as defined in the SplineSeries object
				segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
			} else {
				// moveTo or lineTo
				segmentPath.push(i ? L : M);

				// step line?
				if (step && i) {
					lastPoint = segment[i - 1];
					if (step === 'right') {
						segmentPath.push(
							lastPoint.plotX,
							plotY,
							L
						);
					} else if (step === 'center') {
						segmentPath.push(
							(lastPoint.plotX + plotX) / 2,
							lastPoint.plotY,
							L,
							(lastPoint.plotX + plotX) / 2,
							plotY,
							L
						);
					} else {
						segmentPath.push(
							plotX,
							lastPoint.plotY,
							L
						);
					}
				}

				// normal line to next point
				segmentPath.push(
					point.plotX,
					point.plotY
				);
			}
		});

		return segmentPath;
	};

	/**
	*
	*   ColoredLine series type
	*
	**/
	
	seriesTypes.coloredline = H.extendClass(seriesTypes.line);
	
	H.seriesTypes.coloredline.prototype.processData = function (force) {
		var series = this,
			processedXData = series.xData, // copied during slice operation below
			processedYData = series.yData,
			cropStart = 0,
			cropped,
			distance,
			closestPointRange,
			xAxis = series.xAxis,
			i, // loop variable
			options = series.options,
			isCartesian = series.isCartesian;
		
		// If the series data or axes haven't changed, don't go through this. Return false to pass
		// the message on to override methods like in data grouping.
		if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {
			return false;
		}
		
		// Find the closest distance between processed points
		for (i = processedXData.length - 1; i >= 0; i--) {
			distance = processedXData[i] - processedXData[i - 1];
			if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {
				closestPointRange = distance;
				
				// Unsorted data is not supported by the line tooltip, as well as data grouping and
				// navigation in Stock charts (#725) and width calculation of columns (#1900)
			} else if (distance < 0 && series.requireSorting) {
				error(15);
			}
		}
		
		// Record the properties
		series.cropped = cropped; // undefined or true
		series.cropStart = cropStart;
		series.processedXData = processedXData;
		series.processedYData = processedYData;
		
		if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
			series.pointRange = closestPointRange || 1;
		}
		series.closestPointRange = closestPointRange;
		return true;
	};
	
	H.seriesTypes.coloredline.prototype.drawTracker = function () {
		var series = this,
			options = series.options,
			trackByArea = options.trackByArea,
			trackerPath = [].concat(trackByArea ? series.areaPath : getPath(series.graphPath)),
			trackerPathLength = trackerPath.length,
			chart = series.chart,
			pointer = chart.pointer,
			renderer = chart.renderer,
			snap = chart.options.tooltip.snap,
			tracker = series.tracker,
			cursor = options.cursor,
			css = cursor && { cursor: cursor },
			singlePoints = series.singlePoints,
			singlePoint,
			i,
			onMouseOver;

		onMouseOver = function () {
			if (chart.hoverSeries !== series) {
				series.onMouseOver();
			}
		};
		// Extend end points. A better way would be to use round linecaps,
		// but those are not clickable in VML.
		if (trackerPathLength && !trackByArea) {
			i = trackerPathLength + 1;
			while (i--) {
				if (trackerPath[i] === M) { // extend left side
					trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
				}
				if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side
					trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
				}
			}
		}
		
		// handle single points
		for (i = 0; i < singlePoints.length; i++) {
			singlePoint = singlePoints[i];
			if (singlePoint.plotX && singlePoint.plotY) {
				trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
					L, singlePoint.plotX + snap, singlePoint.plotY);
			}
		}
		
		// draw the tracker
		if (tracker) {
			tracker.attr({ d: trackerPath });
		} else { // create
			series.tracker = renderer.path(trackerPath)
			.attr({
				'stroke-linejoin': 'round', // #1225
				visibility: series.visible ? VISIBLE : HIDDEN,
				stroke: TRACKER_FILL,
				fill: trackByArea ? TRACKER_FILL : NONE,
				'stroke-width': options.lineWidth + (trackByArea ? 0 : 2 * snap),
				zIndex: 2
			})
			.add(series.group);
			
			// The tracker is added to the series group, which is clipped, but is covered
			// by the marker group. So the marker group also needs to capture events.
			each([series.tracker, series.markerGroup], function (track) {
				track.addClass(PREFIX + 'tracker')
				.on('mouseover', onMouseOver)
				.on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
				.css(css);
				
				if (hasTouch) {
					track.on('touchstart', onMouseOver);
				}
			});
		}
		
	};
	
	H.seriesTypes.coloredline.prototype.setState = function (state) {
		var series = this,
			options = series.options,
			graph = series.graph,
			stateOptions = options.states,
			lineWidth = options.lineWidth,
			attribs;
		
		state = state || NORMAL_STATE;
		
		if (series.state !== state) {
			series.state = state;
			
			if (stateOptions[state] && stateOptions[state].enabled === false) {
				return;
			}
			
			if (state) {
				lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
			}
			
			if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
				attribs = {
					'stroke-width': lineWidth
				};
				// use attr because animate will cause any other animation on the graph to stop
				each(graph, function (seg) {
					seg.attr(attribs);
				});
			}
		}
	};
	
	/**
	* The main change to get multi color isFinite changes segments array.
	* From array of points to object with color and array of points.
	* @returns {undefined}
	**/
	H.seriesTypes.coloredline.prototype.getSegments = function () {
		var series = this,
			lastColor = 0,
			segments = [],
			i,
			points = series.points,
			pointsLength = points.length;
		
		if (pointsLength) { // no action required for []
			
			// if connect nulls, just remove null points
			if (series.options.connectNulls) {
				// iterate backwars for secure point removal
				for (i = pointsLength - 1; i >= 0; --i) {
					if (points[i].y === null) {
						points.splice(i, 1);
					}
				}
				pointsLength = points.length;
				
				each(points, function (point, j) {
					if (j > 0 && points[j].segmentColor !== points[j - 1].segmentColor) {
						segments.push({
							points: points.slice(lastColor, j + 1),
							color: points[j - 1].segmentColor
						});
						lastColor = j;
					}
				});

				if (pointsLength) {
					// add the last segment (only single-point last segement is added)
					if (lastColor !== pointsLength - 1) {
						segments.push({
							points: points.slice(lastColor, pointsLength),
							color: points[pointsLength - 1].segmentColor
						});
					}
				}
				
				if (points.length && segments.length === 0) {
					segments = [points];
				}
				
				// else, split on null points or different colors
			} else {
				var previousColor = null;
				each(points, function (point, j) {
					var colorChanged = j > 0 && (point.y === null || points[j - 1].y === null || (point.segmentColor !== points[j - 1].segmentColor && points[j].segmentColor !== previousColor)),
						colorExists = points[j - 1] && points[j - 1].segmentColor && points[j - 1].y !== null ? true : false;
					
					if (colorChanged) {
						var p = points.slice(lastColor, j + 1);
						if (p.length > 0) {
							// do not create segments with null ponits
							each(p, function (pointObject, k) {
								if (pointObject.y === null) {
									// remove null points (might be on edges)
									p.splice(k, 1);
								}
							});
							
							segments.push({
								points: p,
								color: colorExists ? points[j - 1].segmentColor : previousColor
							});
							lastColor = j;
						}
					} else if (j === pointsLength - 1) {
						var next = j + 1;
						if (point.y === null) {
							next--;
						}
						p = points.slice(lastColor, next);
						if (p.length > 0) {
							// do not create segments with null ponits
							each(p, function (pointObject, k) {
								if (pointObject.y === null) {
									// remove null points (might be on edges)
									p.splice(k, 1);
								}
							});
							segments.push({
								points: p,
								color: colorExists ? points[j - 1].segmentColor : previousColor
							});
							lastColor = j;
						}
						
					}
					
					// store previous color
					if (point) {
						previousColor = point.segmentColor;
					}
				});
			}
		}
		// register it
		series.segments = segments;
	};
	
	H.seriesTypes.coloredline.prototype.getGraphPath = function () {
		// var ret = f.apply(this, Array.prototype.slice.call(arguments, 1));
		var series = this,
			graphPath = [],
			segmentPath,
			singlePoints = []; // used in drawTracker
		// Divide into segments and build graph and area paths
		each(series.segments, function (segment) {
			segmentPath = series.getSegmentPath(segment.points);
			// add the segment to the graph, or a single point for tracking
			if (segment.points.length > 1) {
				graphPath.push([segmentPath, segment.color]);
			} else {
				singlePoints.push(segment.points);
			}
		});
		
		// Record it for use in drawGraph and drawTracker, and return graphPath
		series.singlePoints = singlePoints;
		series.graphPath = graphPath;
		
		return graphPath;
	};
	
	H.seriesTypes.coloredline.prototype.drawGraph = function () {
		var series = this,
			options = series.options,
			props = [['graph', options.lineColor || series.color]],
			lineWidth = options.lineWidth,
			dashStyle = options.dashStyle,
			roundCap = options.linecap !== 'square',
			graphPath = series.getGraphPath(),
			graphPathLength = graphPath.length,
			graphSegmentsLength = 0;
		
		function getSegment(segment, prop, i) {
			var attribs = {
					stroke: prop[1],
					'stroke-width': lineWidth,
					fill: 'none',
					zIndex: 1 // #1069
				},
				item;
			if (dashStyle) {
				attribs.dashstyle = dashStyle;
			} else if (roundCap) {
				attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round';
			}
			if (segment[1]) {
				attribs.stroke = segment[1];
			}
			
			item = series.chart.renderer.path(segment[0])
			.attr(attribs)
			.add(series.group);


			if (item.shadow) {
				item.shadow(!i && options.shadow);
			}
			
			return item;
		}

		// draw the graph
		each(props, function (prop, i) {
			var graphKey = prop[0],
				graph = series[graphKey],
				g;
			
			if (graph) { // cancel running animations, #459
				// do we have animation
				each(graphPath, function (segment, j) {
					// update color and path
					
					if (series[graphKey][j]) {
						series[graphKey][j].attr({ d: segment[0], stroke: segment[1] });
					} else {
						series[graphKey][j] = getSegment(segment, prop, i);
					}
				});
				
			} else if (graphPath.length) { // #1487
				graph = [];
				each(graphPath, function (segment, j) {
					graph[j] = getSegment(segment, prop, i);
				});
				series[graphKey] = graph;
				// add destroying elements
				series[graphKey].destroy = function () {
					for (g in series[graphKey]) {  // eslint-disable-line
						var el = series[graphKey][g];
						if (el && el.destroy) {
							el.destroy();
						}
					}
				};
			}
			// Checks if series.graph exists. #3
			graphSegmentsLength = (series.graph && series.graph.length) || -1;
			
			for (var j = graphSegmentsLength; j >= graphPathLength; j--) {
				if (series[graphKey][j]) {
					series[graphKey][j].destroy();
					series[graphKey].splice(j, 1);
				}
			}
		});
	};

	H.wrap(seriesTypes.coloredline.prototype, 'translate', function (proceed) {
		proceed.apply(this, [].slice.call(arguments, 1));
		if (this.getSegments) {
			this.getSegments();
		}
	});
	
	H.wrap(H.Series.prototype, 'applyZones', function (proceed) {
		var series = this,
			parts = ['area', 'graph'];

		parts.forEach(function (part) {
			var shape = series[part];

			if (shape && H.isArray(shape)) {
				shape.show = function () {
					shape.forEach(function (subGraph) {
						subGraph.show(true);
					});
				};
				shape.hide = function () {
					shape.forEach(function (subGraph) {
						subGraph.hide();
					});
				};
			}
		});
	
		return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
	});

	H.wrap(seriesTypes.coloredline.prototype, 'destroy', function (proceed) {
		// destroy all parts
		this.graph.destroy();
		proceed.apply(this, Array.prototype.slice.call(arguments, 1));
	});
	
	/**
	*
	*   ColoredArea series type
	*
	**/
	seriesTypes.coloredarea = H.extendClass(seriesTypes.coloredline);
	
	H.seriesTypes.coloredarea.prototype.init = function (chart, options) {
		options.threshold = options.threshold || null;
		H.Series.prototype.init.call(this, chart, options);
	};
	
	H.seriesTypes.coloredarea.prototype.closeSegment = function (path, segment, translatedThreshold) {
		path.push(
			L,
			segment[segment.length - 1].plotX,
			translatedThreshold,
			L,
			segment[0].plotX,
			translatedThreshold
		);
	};
	
	H.seriesTypes.coloredarea.prototype.drawGraph = function (f) {
		H.seriesTypes.coloredline.prototype.drawGraph.call(this, f);
		var series = this,
			options = this.options,
			props = [['graph', options.lineColor || series.color]];

		each(props, function (prop) {
			var graphKey = prop[0],
				graph = series[graphKey];
			
			if (graph) { // cancel running animations, #459
				// do we have animation
				each(series.graphPath, function (segment, j) {
					// update color and path
					
					if (series[graphKey][j]) {
						series[graphKey][j].attr({ fill: segment[1] });
					}
				});
				
			}
		});
	};

	/**
	* Extend the base Series getSegmentPath method by adding the path for the area.
	* This path is pushed to the series.areaPath property.
	* @param {Object} segment of the path
	* @returns {Array} Path (SVG)
	**/
	H.seriesTypes.coloredarea.prototype.getSegmentPath = function (segment) {
		var segmentPath = H.Series.prototype.getSegmentPath.call(this, segment), // call base method
			areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path
			i,
			options = this.options,
			segLength = segmentPath.length,
			translatedThreshold = this.yAxis.getThreshold(options.threshold), // #2181
			yBottom;

		if (segLength === 3) { // for animation from 1 to two points
			areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
		}
		if (options.stacking && !this.closedStacks) {
			for (i = segment.length - 1; i >= 0; i--) {

				yBottom = pick(segment[i].yBottom, translatedThreshold);

				// step line?
				if (i < segment.length - 1 && options.step) {
					areaSegmentPath.push(segment[i + 1].plotX, yBottom);
				}
				areaSegmentPath.push(segment[i].plotX, yBottom);
			}
		} else { // follow zero line back
			this.closeSegment(areaSegmentPath, segment, translatedThreshold);
		}
		return areaSegmentPath;
	};
	
	H.seriesTypes.coloredarea.prototype.getGraphPath = function () {
		var series = this,
			graphPath = [],
			segmentPath,
			singlePoints = []; // used in drawTracker
		// Divide into segments and build graph and area paths
		
		this.areaPath = [];
		each(series.segments, function (segment) {
			segmentPath = series.getSegmentPath(segment.points);
			// add the segment to the graph, or a single point for tracking
			if (segment.points.length > 1) {
				graphPath.push([segmentPath, segment.color]);
			} else {
				singlePoints.push(segment.points);
			}
		});
		
		// Record it for use in drawGraph and drawTracker, and return graphPath
		series.singlePoints = singlePoints;
		series.graphPath = graphPath;
		return graphPath;
	
	};
	
	H.seriesTypes.coloredarea.prototype.drawLegendSymbol = H.LegendSymbolMixin.drawRectangle;
}));