var corners = ['topLeft', 'topRight', 'bottomRight', 'bottomLeft'];
var symbolize = false;

function getCut(item, point, reference) {
	var vector = point - reference;
	var middle = reference + vector / 2;
	var line = new Line(middle, middle + vector.rotate(90), true);
	var cut = [];
	for (var i = 0; i < corners.length; i++) {
		var side = new Line(
				item.bounds[corners[i]],
				item.bounds[corners[(i + 1) % 4]]);
		var point = line.getIntersectionPoint(side);
		if (point)
			cut.push({ point: point, index: i });
	}
	return cut.length == 2 ? cut : null;
}

function createCutMask(item, cut, flip) {
	var part = new Path();
	// Add first cut point
	var index1 = flip ? 0 : 1, index2 = flip ? 1 : 0;
	part.add(cut[index1].point);
	// Add bounds bounds in between
	for (var i = cut[index1].index + 1; i != cut[index2].index + 1; i++)
		part.add(item.bounds[corners[i %= 4]]);
	// Add second cut point
	part.add(cut[index2].point);
	part.closed = true;
	return part;
}

function removeInivisbles(item, mask) {
	if (item instanceof Group) {
		var child = item.firstChild;
		if (child.clipMask && !child.intersects(mask)) {
			item.remove();
			return null;
		}
		item.children.each(function(child) {
			removeInivisbles(child, mask);
		});
	}
	return item;
}

function makeFoldGroup(item, mask) {
	var item = removeInivisbles(item, mask);
	if (item) {
		var group = new Group([ mask, item ]);
		group.clipped = true;
		return group;
	} else {
		mask.remove();
		return null;
	}
}

function reverseOriginals(group) {
	if (group.data.original) {
		group.reverseChildren();
	} else {
		group.children.each(reverseOriginals);
	}
}

function process(item, point, reference, isDrag) {
	var cut = getCut(item, point, reference);
	if (cut) {
		var cutLine = new Line(cut[0].point, cut[1].point);
		var side = -cutLine.getSide(point);
		// Create the cut masks
		var mask1 = createCutMask(item, cut, side == -1);
		var mask2 = createCutMask(item, cut, side == 1);

		var center = (cutLine.point1 + cutLine.point2) / 2;
		var vector = cutLine.getVector() * side;
		// Calculate the angle to rotate the flipped clone by so it aligns with
		// the fold: 180 + 2 * angle
		var angle = 180.0 + vector.angle * 2;
		// Create a matrix that flips the clone first and then rotates it
		var matrix = new Matrix().rotate(angle, center).scale(-1, 1, center);
		if (isDrag) {
			mask1.removeOn({ drag: true, up: true });
			mask2.removeOn({ drag: true, up: true });
			mask2.transform(matrix);
		} else {
			// Turn the item into a symbol if it was not done so already
			if (symbolize && !item.data.symbolized) {
				item = new PlacedSymbol(item);
				// Mark as symbolized so we dont do it again in the next fold
				item.data.symbolized = true;
			}
			// Create a clone of the item before making the group, as the group
			// might turn out to be invisible and the item removed
			var clone = item.clone();
			var group1 = makeFoldGroup(item, mask1);
			// Mark as original if it was not folded before, so
			// reverseOriginals() knows what to do
			if (group1 && !item.data.folded)
				item.data.original = clone.data.original = true;
			// Now process the clone and flip it
			var group2 = makeFoldGroup(clone, mask2);
			if (group2) {
				// Reverse the order of the children, as we're looking at the
				// other side of the 'paper'
				reverseOriginals(clone);
				// And finally transform it according to the fold
				group2.transform(matrix);
			}
			if (group2 && group1) {
				// Now group it all again, so it appears as one and can be
				// folded again
				var group = new Group([ group2, group1 ]);
				// Mark as symbolized as its contents were symbolized above
				if (symbolize)
					group.data.symbolized = true;
				// Mark as folded so we know it's not an original
				group.data.folded = true;
			}
		}
	}
}

var item, reference, bounds;

function onMouseDown(event) {
	item = event.item;
	if (item) {
		reference = null;
		var minDistance = Number.MAX_VALUE;
		// Find closest reference point and drag from there.
		['topLeft', 'topCenter', 'topRight', 'rightCenter', 'bottomRight',
				'bottomCenter', 'bottomLeft', 'leftCenter'].each(function(name) {
			bounds = item.bounds;
			var point = item.bounds[name];
			var distance = (event.point - point).length;
			if (!reference || distance < minDistance) {
				reference = point;
				minDistance = distance;
			}
		});
	}
}

function onMouseDrag(event) {
	if (item)
		process(item, event.point, reference, true);
}

function onMouseUp(event) {
	if (item)
		process(item, event.point, reference, false);
}

Line = Base.extend({
	initialize: function(point1, point2, extend) {
		this.point1 = point1;
		this.point2 = point2;
		// Extend controls wether the line extends beyond the defining points,
		// meaning point results outside the line segment are allowed.
		this.extend = extend;
	},

	getIntersectionPoint: function(line) {
		var v1 = this.point2 - this.point1;
		var v2 = line.point2 - line.point1;
		var cross = v1.cross(v2);
		// Epsilon tolerance
		if (Math.abs(cross) <= 10e-6)
			return null;
		var v = line.point1 - this.point1;
		var t1 = v.cross(v2) / cross;
		var t2 = v.cross(v1) / cross;
		// Check the ranges of t parameters if the line is not allowed to
		// extend beyond the definition points.
		if ((this.extend || 0 <= t1 && t1 <= 1)
				&& (line.extend || 0 <= t2 && t2 <= 1))
			return this.point1 + v1 * t1;
		return null;
	},

	getSide: function(p) {
		var v1 = this.point2 - this.point1;
		var v2 = p - this.point1;
		var ccw = v2.cross(v1);
		if (ccw == 0.0) {
			ccw = v2.dot(v1);
			if (ccw > 0.0) {
				ccw = (v2 - v1).dot(v1);
				if (ccw < 0.0)
				    ccw = 0.0;
			}
		}
		return ccw < 0.0 ? -1 : ccw > 0.0 ? 1 : 0;
	},

	getVector: function() {
		return this.point2 - this.point1;
	}
});