////////////////////////////////////////////////////////////////////////////////
// Note from the Scriptographer.org Team
//
// In Scriptographer 2.9, we switched to a top-down coordinate system and
// degrees for angle units as an easier alternative to radians.
// 
// For backward compatibility we offer the possibility to still use the old
// bottom-up coordinate system and radians for angle units, by setting the two
// values bellow. Read more about this transition on our website:
// http://scriptographer.org/news/version-2.9.064-arrived/

if(scriptographer.version < 2.9) {
	Dialog.alert('Please update your version of Scriptographer\nThis script only runs on Scriptographer 2.9 or higher.');
} else {

	var values = {
		width: script.preferences.width != undefined ? script.preferences.width : 10,
		opacity: script.preferences.opacity != undefined ? script.preferences.opacity : 60,
		alternate: script.preferences.alternate != undefined ? script.preferences.alternate : false,
		oddColor: document ? document.currentStyle.fillColor : new CMYKColor(0, 0, 0, 0.3),
		evenColor: new CMYKColor(0, 0, 0, 1),
		teeth:  script.preferences.teeth != undefined ? script.preferences.teeth : 1
	}

	if(script.preferences.oddColor)
		values.oddColor = eval(script.preferences.oddColor);
	
	if(script.preferences.evenColor)
		values.evenColor = eval(script.preferences.evenColor);

	var folder;

	function onMouseDown(event) {
		var shiftKeyDown = Tracker.currentModifiers == 36;
		folder = null;
		var hitFolder = Folder.getHitFolder(event.point);
		if(hitFolder) {
			folder = hitFolder;
		} else {
			var selectedFolder = Folder.getSelectedFolder();
			if(!selectedFolder || shiftKeyDown) {
				folder = new Folder({
					point: event.point
				});
			} else {
				folder = selectedFolder;
				folder.addSegment();
				folder.setHitSegment(event.point);
				folder.render();
			}
		}
	}

	function onMouseDrag(event) {
		folder.setHitSegment(event.point);
		folder.render();
	}

	function onMouseUp(event) {
		folder.finalize();
	}

	Line = Base.extend({
		initialize: function(pt1, pt2, width) {
			if(pt1 instanceof Curve) {
				var curve = pt1;
				this.pt1 = new Point(curve.point1);
				this.pt2 = new Point(curve.point2);
				this.curve = curve;
				var path = this.curve.path;
				if(path.data.width) {
					this.width = path.data.width;
				} else if(path.strokeWidth) {
					this.width = path.strokeWidth;
				}
			} else {
				if(pt1) this.pt1 = pt1;
				if(pt2) this.pt2 = pt2;
				if(width) this.width = width;
			}

			if(this.width !== undefined) {
				this.bounds = this.getBounds(this.width);
				this.boundingLines = this.getBoundinglines();
			}
		},

		setPt1: function(point) {
			this.initialize(point)
		},

		setPt2: function(point) {
			this.initialize(null, point);
		},

		reverse: function() {
			return new Line(this.pt2, this.pt1, this.width);
		},

		toString: function() {
			return '[' + this.pt1 + ', ' + this.pt2 + ']'
		},

		// code adapted from code supplied by 'sabre150'
		// http://forums.sun.com/thread.jspa?messageID=3942889#3942889
		getIntersectionPoint: function(line) {
			var p1 = this.pt1; var p2 = this.pt2;
			var p3 = line.pt1; var p4 = line.pt2;

			var dx1 = p2.x - p1.x;
			var dx2 = p3.x - p4.x;
			var m1 = (p2.y - p1.y) / dx1;
			var m2 = (p3.y - p4.y) / dx2;

			if (!(dx1 || dx2)) return NaN;

			if (!dx1) {
				// infinity
				return new Point(p1.x, m2 * (p1.x - p4.x) + p4.y );

			} else if (!dx2) {
				// infinity
				return new Point(p4.x, m1 * (p4.x - p1.x) + p1.y );
			}
			var xInt = (-m2 * p4.x + p4.y + m1 * p1.x - p1.y) / (m1 - m2);
			var yInt = m1 * (xInt - p1.x) + p1.y;
			return new Point(xInt, yInt);
		},

		/**
			* Finds the point closest to a point 'p' on the line
			* between two points 'pt1' and 'pt2'.
			* @param pt1 one end of the line
			* @param pt2 the other end of the line
			* @param p the point to find the closest point to.
			* @param allowOnExtension <code>true</code> if the calculated
			* point may be located outside the bounds defind by 'pt1' and 'pt2'.
			* @return the point closest to a point 'p' on the line
			* between two points 'pt1' and 'pt2'.
			*/
		getClosestPoint: function(p, allowOnExtension) {

			// If the two points represent the same point then
			// they are the closest point.
			if (this.pt1 == this.pt2)
				return this.pt1.clone();

			// Compute the relative position of the closest point to the point 'p'
			// u = ((p - pt1) . (pt2 - pt1)) / ((pt2 - pt1) . (pt2 - pt1))
			// where '.' is the vector dot product
			// the original code was:
			// ((p.x-this.pt1.x)*(this.pt2.x-this.pt1.x)+(p.y-this.pt1.y)*(this.pt2.y-this.pt1.y))/(Math.pow(this.pt2.x-this.pt1.x, 2)+Math.pow(this.pt2.y-this.pt1.y, 2));
			// Thank god for Scriptographer and clean code!
			var delta = this.pt2 - this.pt1;
			var u = (p - this.pt1).dot(delta) / delta.dot(delta);

			// Remove this conditional statement if you allow the closest point to be
			// exterior to the direct line between pt1 and pt2.
			if (!allowOnExtension) {
				if (u >= 1) {
					return this.pt2.clone();
				} else if (u <= 0) {
					return this.pt1.clone();
				}
			}
			// Return the closest point
			return this.pt2 * u + this.pt1 * (1 - u);
		},

		distanceToClosestPoint: function(p, allowOnExtension) {
			var pt0 = this.getClosestPoint(p, allowOnExtension);
			return (p - pt0).length;
		},

		isWithinRadius: function(p, radius, allowOnExtension) {
			var pt0 = this.getClosestPoint(p, allowOnExtension);
			return p.isClose(pt0, radius);
		},

		getNormalizedVector: function() {
			return (this.pt1 - this.pt2).normalize();
		},

		getAngle: function() {
			return this.getNormalizedVector().angle
		},

		getBounds: function(width) {
			var norm = this.getNormalizedVector();
			var offset = norm.rotate(90) * width / 2;
			return {
				bottomLeft: this.pt1 - offset,
				topLeft: this.pt2 - offset,
				topRight: this.pt2 + offset,
				bottomRight: this.pt1 + offset
			};
		},

		getBoundinglines: function() {
			return {
				top: new Line(this.bounds.topLeft, this.bounds.topRight),
				bottom: new Line(this.bounds.bottomLeft, this.bounds.bottomRight),
				left: new Line(this.bounds.bottomLeft, this.bounds.topLeft),
				right: new Line(this.bounds.bottomRight, this.bounds.topRight)
			}
		},

		makePath: function() {
			var path = new Path.Line(this.pt1, this.pt2) {
				name: 'line',
				strokeColor: null,
				fillColor: null
			};
			if(this.width != undefined)
				path.data.width = this.width;
			return path;
		},

		makeExpandedPath: function() {
			var path = new Path() {
				closed: true,
				name: 'path'
			};
			this.bounds.each(function(point) {
				path.lineTo(point);
			});
			return path;
		}
	});

	FoldSegment = Base.extend({
		initialize: function(param) {
			this.topFold = param.topFold;
			this.bottomFold = param.bottomFold;

			this.group = new Group() {
				name: 'segment'
			};

			this.setLine(param.line);

			this.path = new Path() {
				closed: true,
				name: 'path',
				opacity: values.opacity / 100,
				strokeJoin: 'round',
				fillColor: document.currentStyle.fillColor
			};
			
			this.group.appendTop(this.path)
			
			this.setSegments();
			this.group.appendTop(this.path);
		},

		setLine: function(line) {
			this.group.data.pt1 = line.pt1;
			this.group.data.pt2 = line.pt2;
			this.group.data.width = line.width;
			this.line = line;
		},

		getLine: function() {
			return new Line(this.group.data.pt1, this.group.data.pt2, this.group.data.width)
		},

		setTopFold: function(fold) {
			this.topFold = fold;
			this.setSegments();
		},

		setBottomFold: function(fold) {
			this.bottomFold = fold;
			this.setSegments();
		},

		setSegments: function() {

			// this.path.segments.removeAll();
			this.path.data.pt1 = this.topFold.pt1 - (this.topFold.pt1 - this.topFold.pt2) / 2;
			this.path.data.pt2 = this.bottomFold.pt1 - (this.bottomFold.pt1 - this.bottomFold.pt2) / 2;
			this.path.segments = [
				this.topFold.pt1,
				this.topFold.pt2,
				this.bottomFold.pt1,
				this.bottomFold.pt2
			];

			// this.path.add(this.topFold.pt1);
			// var ver = (this.topFold.pt2 - this.topFold.pt1).rotate((90).toRadians()) / 10;
			// this.path.curveThrough( this.topFold.pt1 + (this.topFold.pt2 - this.topFold.pt1) / 2 + ver, this.topFold.pt2)
			// 
			// 
			// this.path.add(this.bottomFold.pt1);
			// var ver = (this.bottomFold.pt2 - this.bottomFold.pt1).rotate((90).toRadians()) / 10;
			// this.path.curveThrough( this.bottomFold.pt1 + (this.bottomFold.pt2 - this.bottomFold.pt1) / 2 + ver, this.bottomFold.pt2)
			
		},

		isValid: function() {
			return this.group && this.group.isValid();
		},
		
		remove: function() {
			if(this.group) {
				this.group.remove();
				this.group = null;
			}
		}
	});
	
	FoldSegment.inject({
		_beans: true,
		getIndex: function() {
			return this.group.parent.children.length
		}
	})

	Folder = Base.extend({
		initialize: function(param) {
			if(param.path || param.group) {
				if(param.path && param.path.name == 'path') {
					this.group = param.path.parent.parent.parent;
					this.path = this.group.children['data'];
				} else if(param.group){
					this.group = param.group
					this.path = this.group.children['data'];
				}

				this.foldSegments = this.group.children.foldSegments;
				if(param.hitPoint) {
					this.hitSegment = this.getHitSegment(param.hitPoint);
				}
			} else {
				this.group = new Group() {
					name: 'folder'
				};

				this.foldSegments = new Group() {
					name: 'foldSegments'
				};
				this.group.appendTop(this.foldSegments);
				
				if(param.tracePath) {
					this.path = param.tracePath.clone();
				} else {
					this.path = this.createLinePath(param.point);
					this.path.lineTo(param.point);
				}

				this.hitSegment = this.path.segments.last;
			}
			this.dirty = false;
		},

		isValid: function() {
			return this.group.isValid();
		},

		createLinePath: function(point) {
			var path = new Path() {
				fillColor: null,
				strokeColor: new GrayColor(1),
				strokeWidth: this.width,
				strokeJoin: 'bevel',
				segments: [point],
				name: 'data',
				visible: false
			}
			this.group.appendBottom(path);
			return path;
		},

		addSegment: function() {
			this.path.lineTo(this.path.segments.last.point);
			this.hitSegment = this.path.segments.last;
		},

		getHitSegment: function(point) {
			var hitSegment;
			this.path.visible = true;
			hitResult = this.path.hitTest(point, 'all', document.activeView.zoom * values.width/1.5);
			this.path.visible = false;
			if(hitResult) {
				if(hitResult.type != 'anchor' || (hitResult.parameter && hitResult.parameter < 0.99)) {
					var curve = hitResult.curve.divide(hitResult.parameter);
					hitSegment = curve.segment1;
					hitResult.curve.handle1 = null;
					hitResult.curve.handle2 = null;
					curve.handle1 = null;
					curve.handle2 = null;
				} else {
					hitSegment = hitResult.segment;
				}
			}
			return hitSegment
		},

		setHitSegment: function(point) {
			this.hitSegment.point = point;
		},

		getLength: function() {
			return this.path.curves.length
		},

		render: function() {
			this.dirty = false;
			this.foldSegments.removeChildren();
			this.previousFoldSegment = null;
			var lastSegment = this.path.segments.last;
			for(var i = this.path.segments.length - 2; i > -1; i--) {
				var segment = this.path.segments[i];
				this.addFoldSegment(lastSegment.point, segment.point);
				lastSegment = segment;
			}
			
			// draw ends of the ribbon
			
			var amount = this.teeth * 2;
			
			var firstFold = this.foldSegments.children.first.children.first;
			var vertical = firstFold.data.pt1 - firstFold.data.pt2;
			vertical.length = this.width / amount;
			
			var first = firstFold.segments[2].point;
			var second = firstFold.segments[3].point;
			var vector = second - first;
			vector /= amount;
			for(var i = 0; i < amount; i++) {
				var position = first + vector * i;
				if(i.isOdd())
					position += vertical;
				firstFold.insert(3 + i, position);
			}
			
			
			var lastFold = this.foldSegments.children.last.children.first;
			var vertical = lastFold.data.pt2 - lastFold.data.pt1;
			vertical.length = this.width / amount;
			
			var first = lastFold.segments[0].point;
			var second = lastFold.segments[1].point;
			var vector = second - first;
			vector /= amount;
			for(var i = 0; i < amount; i++) {
				var position = first + vector * i;
				if(i.isOdd())
					position += vertical;
				lastFold.insert(1 + i, position);
			}
		},

		addFoldSegment: function(pt1, pt2, secondTime) {
			var line = new Line(pt1, pt2, this.width);
			var prev = this.previousFoldSegment;

			if(prev && prev.isValid()) {
				var firstPoint = line.boundingLines.left.getIntersectionPoint(prev.line.boundingLines.right);
				var secondPoint = line.boundingLines.right.getIntersectionPoint(prev.line.boundingLines.left);

				var bottomFold = new Line(secondPoint, firstPoint);

				var intersection = bottomFold.getIntersectionPoint(prev.bottomFold);
				var distance = prev.bottomFold.distanceToClosestPoint(intersection);

				var intersection = bottomFold.getIntersectionPoint(line.boundingLines.top);
				var secondDistance = line.boundingLines.top.distanceToClosestPoint(intersection);
				if(distance > 0.1 && secondDistance > 0.1) {
					this.previousFoldSegment.setTopFold(bottomFold);
					this.currentFoldSegment = new FoldSegment({
						topFold: line.boundingLines.top,
						bottomFold: bottomFold,
						line: line
					});
				} else {
					this.dirty = true;
				}
			} else {
				this.currentFoldSegment = new FoldSegment({
					topFold: line.boundingLines.top,
					bottomFold: line.boundingLines.bottom.reverse(),
					line: line
				});
			}
			if(this.currentFoldSegment && this.previousFoldSegment && this.currentFoldSegment.path.intersects(this.previousFoldSegment.path) && !secondTime) {
				this.currentFoldSegment.remove();
				this.currentFoldSegment = null;
				this.addFoldSegment(this.previousFoldSegment.line.pt2, pt2, true);
			} else {
				if(this.currentFoldSegment && this.currentFoldSegment.isValid()) {
					this.foldSegments.appendBottom(this.currentFoldSegment.group);
					this.previousFoldSegment = this.currentFoldSegment;
				}
				this.currentFoldSegment = null;
			}
			
			return this.currentFoldSegment
		},

		finalize: function() {
			document.deselectAll();
			this.group.selected = true;
			if(this.dirty)
				this.reconstructPath();
			this.colorize();
			this.hitSegment = null;
		},

		reconstructPath: function() {
			this.path.remove();
			var children = this.foldSegments.children;
			this.path = this.createLinePath(children.last.children.path.data.pt1);
			for(var i = children.length - 1; i > -1; i--) {
				var foldSegment = children[i];
				this.path.lineTo(foldSegment.children.first.data.pt2);
			}
		},

		colorize: function() {
			if(!values.alternate) {
				var color = document.currentStyle.fillColor;
				if(!color || color.components.first >= 1)
					color = values.oddColor;
				this.foldSegments.children.each(function(foldSegment, i) {
						foldSegment.children.path.fillColor = color;
				});
			} else {
				// code for even odd filling
				var l = this.foldSegments.children.length;
				this.foldSegments.children.each(function(foldSegment, i) {
					foldSegment.children.path.fillColor = (l - i).isOdd() ? values.oddColor : values.evenColor;
				});
			}
		},

		statics: {
			getHitFolder: function(point) {
				var folder;
				var hitResult = document.hitTest(point, null, document.activeView.zoom * values.width/2);
				if(hitResult && hitResult.item && hitResult.item.name == 'path') {
					var group = hitResult.item.parent.parent;
					folder = new Folder({
						path: hitResult.item,
						hitPoint: hitResult.point
					});
				}
				return folder;
			},

			tracePath: function(path) {
				var folder = new Folder({
					width: values.width,
					tracePath: path
				});
				folder.render();
				folder.finalize();
			},

			getSelectedFolder: function() {
				var selectedFolder;
				if(document) {
					var groups = document.getItems({
						type: Group,
						selected: true
					});
					for(var i = 0, l = groups.length; i < l; i++) {
						var group = groups[i];
						if(group.name == 'folder') {
							selectedFolder = new Folder({ group: group });
							continue;
						}
					}
				}
				return selectedFolder;
			},
			
			getSelectedFolders: function() {
				var selectedFolders = [];
				if(document) {
					var groups = document.getItems({
						type: Group,
						selected: true
					});
					for(var i = 0, l = groups.length; i < l; i++) {
						var group = groups[i];
						if(group.name == 'folder') {
							selectedFolders.push(new Folder({ group: group }));
						}
					}
				}
				return selectedFolders;
			},

			colorizeSelectedFolders: function() {
				var selectedFolders = Folder.getSelectedFolders();
				selectedFolders.each(function(folder) {
					folder.colorize();
				});
			}
		}
	});

	Folder.inject({
		_beans: true,
		getWidth: function() {
			if(this._width === undefined) {
				this._width = this.group.data.width || values.width;
			}
			return this._width
		},

		setWidth: function(width) {
			if(width)
				this.group.data.width = width;
			this._width = width;
		},
		
		getTeeth: function() {
			if(this._teeth === undefined) {
				this._teeth = this.group.data.teeth || values.teeth;
			}
			return this._teeth
		},

		setTeeth: function(teeth) {
			if(teeth !== null)
				this.group.data.teeth = teeth;
			this._teeth = teeth;
		},
	})

	var dialogItems = { 
		width: {
			type: 'number', label: 'Width',
			steppers: true,
			units: 'point', range: [0, 1000],
			onChange: function(value) {
				script.preferences.width = value;
			} 
		},
		opacity: {
			type: 'number', label: 'Opacity',
			steppers: true,
			units: 'percent', range: [0, 100],
			onChange: function(value) {
				script.preferences.opacity = value;
				if(document) {
					document.selectedItems.each(function(item) {
						if(item.name == 'folder')
							item = item.children.foldSegments;
						if(item.name == 'foldSegments') {
							item.children.each(function(foldSegment) {
								foldSegment.children.path.opacity = value / 100;
							});
						}
					});
				}
				
			} 
		},
		teeth: {
			type: 'number', label: 'Teeth',
			steppers: true,
			range: [0, 1000],
			onChange: function(value) {
				script.preferences.teeth = value;
				if(document) {
					var folder = Folder.getSelectedFolder()
					if(folder) {
						folder.teeth = value;
						folder.render();
						folder.finalize();
					}
				}
			}
		},
		alternateColors: {
			type: 'checkbox', label: 'Alternate Colors',
			onChange: function(value) {
				values.alternate = value;
				script.preferences.alternate = value;
				Folder.colorizeSelectedFolders();
			}
		},
		evenColor: {
			type: 'color',
			label: 'Set odd color',
			onChange: function(value) {
				script.preferences.oddColor = value;
				Folder.colorizeSelectedFolders();
			}
		},
		oddColor: {
			type: 'color',
			label: 'Set even color',
			onChange: function(value) {
				script.preferences.evenColor = value;
				Folder.colorizeSelectedFolders();
			}
		}
	}

	var palette = new Palette('Ribbon Folder', dialogItems, values);
};