/******************************************************
*
* Path
*
* Abstraction layer for annoying clipper js
* ! inherrits from Array !
*
******************************************************/

D3D.Paths = function (paths, closed) {
	"use strict";

	Array.call(this);

	this.setPaths(paths || []);

	this.closed = (closed !== undefined) ? closed : true;
};
D3D.Paths.prototype = Object.create(Array.prototype);
D3D.Paths.prototype.setPaths = function (paths) {
	"use strict";

	for (var i = 0; i < paths.length; i ++) {
		var path = paths[i];
		if (path.length > 0) {
			this.push(path);
		}
	}

	return this;
};
D3D.Paths.prototype.clip = function (path, type) {
	"use strict";

	var solution = new ClipperLib.Paths();

	var clipper = new ClipperLib.Clipper();
	clipper.AddPaths(this, ClipperLib.PolyType.ptSubject, this.closed);
	clipper.AddPaths(path, ClipperLib.PolyType.ptClip, path.closed);
	clipper.Execute(type, solution);

	return new D3D.Paths(solution, this.closed);
};
D3D.Paths.prototype.union = function (path) {
	"use strict";

	return this.clip(path, ClipperLib.ClipType.ctUnion);
};
D3D.Paths.prototype.difference = function (path) {
	"use strict";

	return this.clip(path, ClipperLib.ClipType.ctDifference);
};
D3D.Paths.prototype.intersect = function (path) {
	"use strict";

	return this.clip(path, ClipperLib.ClipType.ctIntersection);
};
D3D.Paths.prototype.xor = function () {
	"use strict";

	return this.clip(path, ClipperLib.ClipType.ctXor);
};
D3D.Paths.prototype.offset = function (offset) {
	"use strict";

	var solution = new ClipperLib.Paths();
	var co = new ClipperLib.ClipperOffset(1, 1);
	co.AddPaths(this, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon);
	co.Execute(solution, offset);

	return new D3D.Paths(solution);
};
D3D.Paths.prototype.scaleUp = function (factor) {
	"use strict";

	var path = ClipperLib.JS.ScaleUpPaths(this, factor);

	return this;
};
D3D.Paths.prototype.scaleDown = function (factor) {
	"use strict";

	var path = ClipperLib.JS.ScaleDownPaths(this, factor);

	return this;
};
D3D.Paths.prototype.lastPoint = function () {
	"use strict";

	var lastPath = this[this.length - 1];
	var lastPoint = this.closed ? lastPath[0] : lastPath[lastPath.length - 1];
	return new THREE.Vector2(lastPoint.X, lastPoint.Y);
};
D3D.Paths.prototype.optimizePath = function (start) {
	"use strict";

	var optimizedPaths = new D3D.Paths([], this.closed);
	var donePaths = [];

	while (optimizedPaths.length !== this.length) {
		var minLength = false;
		var reverse;
		var minPath;
		var offset;
		var pathIndex;

		for (var i = 0; i < this.length; i ++) {
			var path = this[i];

			if (donePaths.indexOf(i) === -1) {

				if (this.closed) {
					for (var j = 0; j < path.length; j ++) {
						var point = new THREE.Vector2(path[j].X, path[j].Y);
						var length = point.sub(start).length();
						if (minLength === false || length < minLength) {
							minPath = path;
							minLength = length;
							offset = j;
							pathIndex = i;
						}
					}
				}
				else {
					var startPoint = new THREE.Vector2(path[0].X, path[0].Y);
					var length = startPoint.sub(start).length();
					if (minLength === false || length < minLength) {
						minPath = path;
						minLength = length;
						reverse = false;
						pathIndex = i;
					}
					var endPoint = new THREE.Vector2(path[path.length - 1].X, path[path.length - 1].Y);
					var length = endPoint.sub(start).length();
					if (length < minLength) {
						minPath = path;
						minLength = length;
						reverse = true;
						pathIndex = i;
					}
				}
			}
		}

		if (this.closed) {
			minPath = minPath.concat(minPath.splice(0, offset));
			var point = minPath[0];
		}
		else {
			if (reverse) {
				minPath.reverse();	
			}
			var point = minPath[minPath.length - 1];
		}
		donePaths.push(pathIndex);
		start = new THREE.Vector2(point.X, point.Y);
		optimizedPaths.push(minPath);
	}

	return optimizedPaths;
};
D3D.Paths.prototype.tresholdArea = function (minArea) {
	//code not tested yet
	"use strict";

	for (var i = 0; i < this.length; i ++) {
		var shape = this[i];

		var area = ClipperLib.Clipper.Area(shape);

		if (area < minArea) {
			this.splice(i, 1);
			i --;
		}
	}
	
	return areas;
};
D3D.Paths.prototype.join = function (path) {
	"use strict";

	for (var i = 0; i < path.length; i ++) {
		this.push(path[i]);
	}

	return this;
};
D3D.Paths.prototype.clone = function () {
	"use strict";

	return new D3D.Paths(ClipperLib.JS.Clone(this), this.closed);
};
D3D.Paths.prototype.bounds = function () {
	"use strict";

	return ClipperLib.Clipper.GetBounds(this);
};
D3D.Paths.prototype.draw = function (context, color) {
	"use strict";

	context.strokeStyle = color;
	for (var i = 0; i < this.length; i ++) {
		var shape = this[i];

		//var point = shape[0];
		//context.fillText(i, point.X*2, point.Y*2);

		context.beginPath();
		var length = this.closed ? (shape.length + 1) : shape.length;
		for (var j = 0; j < length; j ++) {
			var point = shape[j % shape.length];

			context.lineTo(point.X*2, point.Y*2);
		}
		context.stroke();
	}
};