var Point = Class.create({
	/*
	 * Creates a new instance of this class from the given integer x and y coordinates.  Note that instances of this class may not actually represent points,
	 * but rather deltas, sizes, or any other x,y integer pairs.
	 */
	initialize: function(x, y) {
		if (x instanceof Array) {
			this.x = Math.round(x[0]);
			this.y = Math.round(x[1]);
		} else {
			this.x = Math.round(x);
			this.y = Math.round(y);
		}
	},
	
	/*
	 * Adds this point value to the given point value (or, more likely, a delta) and returns the point result.
	 */
	add: function(a) {
		var x = a.x + this.x;
		var y = a.y + this.y;
		return new Point(x, y);
	},
	
	/*
	 * Subtracts the given point value (or, more likely, a delta) from this point value and returns the point result.
	 */
	sub: function(a) {
		var x = this.x - a.x;
		var y = this.y - a.y;
		return new Point(x, y);
	},
	
	/*
	 * Multiplies this point value times a multiplier, returning the resulting point.  Typically used for scaling.
	 */
	mul: function(m) {
		var x = Math.round(this.x * m);
		var y = Math.round(this.y * m);
		return new Point(x, y);
	},
	
	/*
	 * Divides this point value by a divisor, returning the resulting point.  Typically used for scaling.  Returns null
	 * if the divisor is zero.
	 */
	div: function(d) {
		if (d == 0)
			return null;
		var x = Math.round(this.x / d);
		var y = Math.round(this.y / d);
		return new Point(x, y);
	},
	
	equals: function(a) {
		return a.x == this.x && a.y == this.y;
	},
	
	type: 'Point'
});

var Line = Class.create({
	/*
	 * Creates a new instance of this class from the given start point and end point.
	 */
	initialize: function(start, end) {
		this.start = start;
		this.end = end;
		this.dx = end.x - start.x;
		this.dy = end.y - start.y;
		
		// if line is vertical, m will be +/- Infinity and b will be 0...
		this.m = this.dy/this.dx;
		this.b = start.y - (this.m * start.x);
	},

	/*
	 * Returns an Intercept instance that describes the x values on this line at the given y value, or null if there is no intercept.  For all intercepts
	 * except a horizontal line (m == 0), this will be a zero-length line describing the single point of intercept.
	 */
	yIntercept: function(y) {
		if (!this.rangeY().contains(y))
			return null;
		if (this.m == 0)
			return new Intercept(this, true, 0);
		var ip = (this.dx == 0) ? new Point(this.start.x, y) : new Point((y - this.b)/this.m, y);
		var dySignum = 0;
		var isNode = this.start.y == y || this.end.y == y;
		if (isNode) {
			var dy = ((this.start.y == y) ? this.end.y : this.start.y) - y;
			dySignum = (dy < 0) ? -1 : 1;
		}
		return new Intercept(new Line(ip, ip), isNode, dySignum);
	},
	
	rangeX: function() {
		return new Range(this.start.x, this.end.x);
	},
	
	rangeY: function() {
		return new Range(this.start.y, this.end.y);
	},
	
	length: function() {
		var d = this.sub(this.end, this.start);
		return Math.sqrt(Math.pow(d.x, 2) + Math.pow(d.y, 2));
	},
	
	type: 'Line'
});

var Intercept = Class.create({
	initialize: function(interceptLine, isNode, dySignum) {
		this.interceptLine = interceptLine;
		this.isNode = isNode;
		this.dySignum = dySignum;
	},
	
	smallestX: function() {
		return this.interceptLine.rangeX().start;
	},
	
	type: 'Intercept'
});

var Range = Class.create({
	initialize: function(a, b) {
		if (a > b) {
			this.start = b;
			this.end = a;
		} else {
			this.start = a;
			this.end = b;
		}
	},
	
	contains: function(x) {
		return x >= this.start && x <= this.end;
	},
	
	size: function() {
		return this.end - this.start;
	},
	
	type: 'Range'
});

var Polygon = Class.create({
	/*
	 * Creates a new instance of this class with the given list of nodes.  The nodes are a string of ordered, semicolon-separated, comma-separated x,y pairs.
	 * For example, a triangle could be specified as '0,0;5,5;0,5'.  If at least three valid nodes were decoded, the valid flag is set.
	 */
	initialize: function(node_xys) {
		this.valid = false;
		this.nodes = [];
		var pairs = node_xys.split(';');
		for (var i = 0; i < pairs.length; i++) {
			var xy = pairs[i].split(',');
			if (xy.length < 2)
				continue;
			var pnt = new Point(parseInt(xy[0]), parseInt(xy[1]));
			if (isNaN(pnt.x) || isNaN(pnt.y))
				continue;
			this.nodes.push(pnt);
		}
		if (this.nodes.length < 3)
			return;
		this.valid = true;
	},
	
	/*
	 * Returns true if the point defined by the given [x,y] pair is inside this instance.
	 */
	contains: function(point) {
		var intrcpts = this.intercepts(point.y);
		var transCount = 0;
		var onNode = null;
		for (var i = 0; i < intrcpts.length; i++) {
			var intrcpt = intrcpts[i];
			if (intrcpt.interceptLine.rangeX().contains(point.x))
				return true;
			var sx = intrcpt.smallestX();
			if (sx > point.x)
				return transCount %2 == 1;
			if (intrcpt.isNode) {
				if (intrcpt.interceptLine.rangeX().size() > 0)
					continue;
				if (onNode) {
					if (intrcpt.dySignum != onNode)
						transCount++;
					onNode = null;
				}
				else {
					onNode = intrcpt.dySignum;
				}
			}
			else
				transCount++;
		}
		return false;
	},
	
	/*
	 * Returns an array of all the intercepts in this instance at the given y, sorted by x.
	 */
	intercepts: function(y) {
		var result = [];
		var edges = this.getEdges();
		for (var i = 0; i < edges.length; i++) {
			var yi = edges[i].yIntercept(y);
			if (yi) 
				result.push(yi);
		}
		result.sort(
			function(a,b) {
				return a.smallestX() - b.smallestX();
			});
		return result;
	},
	
	/*
	 * Returns an array of Line instances, one for each edge of the polygon.
	 */
	getEdges: function() {
		if (!this.valid)
			return null;
		var edges = [];
		for (var i = 0; i < this.nodes.length; i++) {
			var start = this.nodes[i];
			var end = this.nodes[(i + 1) % this.nodes.length];
			edges.push(new Line(start, end));
		}
		return edges;
	},

	type: 'Polygon'
});
