Function.prototype.inherits = function(superclass) { 
	var x = function() {}; 
	x.prototype = superclass.prototype; 
	this.prototype = new x(); 
} 

function MountView() {
};

MountView.prototype = {
	init : function(canvasElement) {
		if (!canvasElement) return;
		this.canvas = canvasElement;		
		this.ctx = this.canvas.getContext("2d");
		this.displayDPI = 96;
		this.drawScale = 1;
		this.maximumDrawSize = [1000,1000];
		this.fixedCanvasSize = true;
		this.finish = MountView.FINISHES['glossyBlack'];
		this.aspectRatio = 0.15;
		this.mountElements = [];
		this.position = [0, 0];
		this.savedPositions = [];
		this.ctxScale = [1, 1];
		this.savedScales = [];
		this.elementSpacing = 10.0;
		this.needsDisplay = false;
		this.frontOpacity = 1.0;
		this.labelsDiv = null;
		this.optic = null;
	},

	setMountElementsFromDOM : function(parentElement) {
		var mountElements = $.map($("li", parentElement), function(listEl, i) { 
			var el = MountViewElement.createFromDOM(listEl);
			return el;
		});
		this.setMountElements(mountElements);
	},
		
	getDrawSize : function() {
		var drawWidth = 1.0;
		var drawHeight = 1.0;
		var carryThreadLength = 0.0;
		for (var elIdx in this.mountElements) {
			var element = this.mountElements[elIdx];
			
			// Don't clip the first element
			if (elIdx == 0) {
				drawHeight += element.getDrawAscent(this.getAspectRatio());
			}
			
			if (elIdx == this.mountElements.length-1) {	
				drawHeight += element.getDrawDescent(this.getAspectRatio());
			}
			
			var elWidth = element.getMaxWidth();
			if (elWidth > drawWidth) {
				drawWidth = elWidth;
			}
			
			// Translate if can't receive all the threads of the previous element
			var dy = carryThreadLength - element.getFemaleThreadDepth();
			if (dy > 0) {
				drawHeight += dy;
			}
			
			// Translate for the next element
			drawHeight += element.getDrawHeight() + this.elementSpacing;
			carryThreadLength = element.getMaleThreadLength();
		}
		return {x:drawWidth, y:drawHeight};
	},
	
	scaleToFit : function() {
		var drawSize = this.getDrawSize();
		var hScale =  this.maximumDrawSize[0] / drawSize.x;
		var vScale =  this.maximumDrawSize[1] / drawSize.y;
		var scale = (hScale < vScale) ? hScale : vScale;
	    this.setDrawScale(scale / (this.displayDPI / 25.4));
		this.resizeCanvas();
    },
	
	resizeCanvas : function() {
		var drawSize = this.getDrawSize();
		var canvasWidth = drawSize.x * this._getTransformScale();
		var canvasHeight = drawSize.y * this._getTransformScale();		

        if (this.fixedCanvasSize) {
            canvasWidth = this.maximumDrawSize[0];
            canvasHeight = this.maximumDrawSize[1];
        }

		this.canvas.width = canvasWidth;
		this.canvas.height = canvasHeight;
		$(this.canvas).width(canvasWidth / this.displayDPI + 'in')
					  .height(canvasHeight / this.displayDPI + 'in')
					  .css({position: "absolute", top: "0", right: "0"});
		
		this.setNeedsDisplay();
	},
	
	draw : function() {
		this.save();
		
		// Clear
		this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
		$(".mountLabel", this.labelsDiv).remove();

		// Center drawing
		this.translate(this.canvas.width/2, 0); // margin
		var s = this._getTransformScale();
		this.scale(s,s);
				
		// Draw elements
		var carryThreadLength = 0.0;
		for (var elIdx in this.mountElements) {
			var element = this.mountElements[elIdx];
			
			// Don't clip the first element
			if (elIdx == 0) {
				this.translate(0, element.getDrawAscent(this.getAspectRatio()));
			}
			
			// Translate if can't receive all the threads of the previous element
			var dy = carryThreadLength - element.getFemaleThreadDepth();
			if (dy > 0) {
				this.translate(0, dy);
			}
			
			// Draw the element
			this.save();
			element.drawElement(this);
			this.restore();
			
			// Translate for the next element
			this.translate(0, element.getBaseHeight() + this.elementSpacing);
			carryThreadLength = element.getMaleThreadLength();
		}
		
        this.restore();
		this.needsDisplay = false;
	},
	
	getLabelsDiv : function() {
		if (!this.labelsDiv) {
		    var canvasPos = $(this.canvas).position();
			this.labelsDiv = $("<div class=\"mountCanvas\"></div>")
				.css("margin", "0 auto")
				.css("position", "absolute")
				.css("top", canvasPos.top)
				.css("left", canvasPos.left)
				.css("zIndex", 2)
				.appendTo($(this.canvas).parent());
		}
		return this.labelsDiv				
			.width($(this.canvas).width())
			.height($(this.canvas).height());
	},
	
	setNeedsDisplay : function() {
//	    this.draw();
		var self = this;
		$(this.canvas).queue(function() { 
			self.draw(); 
			$(this).dequeue();
		});
	},
	
	getMaximumDrawSize : function() {
		return {width:this.maximumDrawSize[0], height:this.maximumDrawSize[1]};
	},
	
	setMaximumDrawSize : function(width, height) {
		// Create a test element with the given dimentions, then determine the pixel size
		var tempDiv = $("<div></div>").width(width).height(height);
		tempDiv.appendTo($(this.canvas).parent());
		var pxWidth = tempDiv.width() * this.getPixelInchScale();
		var pxHeight = tempDiv.height() * this.getPixelInchScale();
		tempDiv.remove();
		
		this.maximumDrawSize = [pxWidth, pxHeight];
		this.scaleToFit();
	},
	
	getMountElements : function() {
		return this.mountElements;
	},
	
	setMountElements : function(els) {
		this.mountElements = els;
	},
	
	addMountElement : function(el) {
		this.mountElements.push(el);
	},

	getAspectRatio : function() {
		return this.aspectRatio;
	},
	
	setAspectRatio : function(ratio) {
		this.aspectRatio = ratio;
		this.setNeedsDisplay();
	},
	
	getDisplayDPI : function() {
		return this.displayDPI;
	},
	
	setDisplayDPI : function(dpi) {
		this.displayDPI = dpi;
		this.setNeedsDisplay();
	},
	
	getPixelInchScale : function() {
		return this.getDisplayDPI() / 96.0;
	},
	
	getDrawScale : function() {
		return this.drawScale;
	},
	
	setDrawScale : function(scale) {
		this.drawScale = scale;
		this.setNeedsDisplay();
	},
	
	getFrontOpacity : function() {
	    return this.frontOpacity;
    },
	
	setFrontOpacity : function(op) {
	    this.frontOpacity = op;
	    this.setNeedsDisplay();
	},
	
	_getTransformScale : function() {
		return this.displayDPI / 25.4 * this.drawScale;
	},
	
	getElementSpacing : function() {
		return this.elementSpacing;
	},
	
	setElementSpacing : function(spacing) {
		this.elementSpacing = spacing;
		this.setNeedsDisplay();
	},
	
	getOptic : function() {
		return this.optic;
	},
	
	setOptic : function(optic) {
		this.optic = optic;
	},
	
	getFinish : function() {
		return this.finish;
	},
	
	setFinishName : function(name) {
		if (MountView.FINISHES[name]) {
			this.finish = MountView.FINISHES[name];
		} else {
			this.finish = MountView.FINISHES['glossyBlack'];
		}
	},
	
	getContext : function() {
		return this.ctx;
	},
	
	translate : function(dx, dy) {
		this.ctx.translate(dx, dy);
		this.position = [this.position[0]+(dx*this.ctxScale[0]), 
		                 this.position[1]+(dy*this.ctxScale[1])];
	},
	
	scale : function(sx, sy) {
		this.ctx.scale(sx,sy);
		this.ctxScale = [this.ctxScale[0]*sx, this.ctxScale[1]*sy];
	},
	
	save : function() {
		this.ctx.save();
		this.savedPositions.push(this.position);
		this.savedScales.push(this.ctxScale);
	},
	
	restore : function() {
		this.ctx.restore();
		this.position = this.savedPositions.pop();
		this.ctxScale = this.savedScales.pop();
	},
	
	drawCylinder : function(x, y1, y2) {
		this.drawCone(x, y1, x, y2);
	},

	drawCone : function(x1, y1, x2, y2) {
		var aspect = this.getAspectRatio();
		this.save();
		this.scale(1.0, aspect);		
		this._drawFace(x1, y1/aspect-1, x2, y2/aspect, false);   // back
		this._drawFace(x1, y1/aspect-1, x2, y2/aspect, true);	 // front
		this.restore();	
	},
		
	drawRing : function(x1, x2, y) {
	    if (x1 > x2) {
			var aspect = this.getAspectRatio();
			this.save();
			this.scale(1.0, aspect);
    	    this.ctx.globalAlpha = this.frontOpacity;
  			this.ctx.beginPath();
  			this.ctx.arc(0, y/aspect-1, x1, 0, 2*Math.PI, true);
  			this.ctx.arc(0, y/aspect+1, x2, 0, 2*Math.PI, false);
			
			var context = this.ctx;
  			this._fillFinish('bottom', function(gradSteps) {
					var grad = context.createLinearGradient(-x1, y, x1, y);
					return MountView._addGradientSteps(grad, gradSteps);
				});
  			this.ctx.closePath();
			this.restore();
  		}
	},
	
	drawThread : function(width, partHeight, threadHeight, pitch) {
		var aspectRatio = this.getAspectRatio();
	
		this.save();
		this.scale(1, aspectRatio);
		this.ctx.strokeStyle = "#050505";
		this.ctx.lineCap = "butt";
		this.ctx.lineWidth = pitch;
	    this.ctx.globalAlpha = this.frontOpacity;

		for (var y = partHeight; y > (partHeight - threadHeight); y -= pitch) {
		    this.save();
  			this.ctx.beginPath();
  			this.ctx.translate(0, y/aspectRatio);
		    this.ctx.rotate(-3.0*Math.PI/180);
  			this.ctx.arc(0, 0, width/2, 0, Math.PI, true);
  			this.ctx.stroke();
  			this.ctx.closePath();
  			this.restore();
		}

		this.restore();
	},
	
	drawLabel : function(label, ringDiameter, ringHeight, labelHeight_mm) {
		if (labelHeight_mm == null) {
			labelHeight_mm = ringHeight;
		}
	
	    if (this.ctx.fillText) {
	        // Use canvas text drawing 
	        this.save();
	        this.ctx.fillStyle = this.finish.label;
	        
    		var fontSize = (labelHeight_mm < 4) ? (0.125 * labelHeight_mm) + "mm" : "1mm";
	        this.ctx.font = fontSize + " sans-serif";
	        this.ctx.textAlign = "center";
    		
	        var width = this.ctx.measureText(label);
	        this.ctx.fillText(label, 0.0, 0.0); //-0.5 * width, 0.0);
	        this.restore();
	    } else {	    
    		var width = 120;
    		var labelEl = document.createElement("div");
    		var labelTextNode = document.createTextNode(label);
    		labelEl.appendChild(labelTextNode);
    		labelEl.setAttribute("class", "mountLabel");

    		var leftPos = this.position[0] - (0.5 * width * this.getPixelInchScale());
    		var aspectRatio = this.getAspectRatio();
    		var topPos = this.position[1] + (ringHeight - ringDiameter/2 * aspectRatio - labelHeight_mm) * this._getTransformScale();
    		labelHeight_mm *= this.getDrawScale();
    		var fontSize = (labelHeight_mm < 4) ? (0.75 * labelHeight_mm) + "mm" : "3mm";

    		leftPos = (leftPos  / this.displayDPI) + "in";
    		topPos = (topPos / this.displayDPI) + "in";

    		$(labelEl).css({
    			textAlign: "center",
    			verticalAlign: "bottom",
    			color: this.finish.label,
    			position: "absolute",
    			width: width + "px",
    			height: labelHeight_mm + "mm",
    			fontSize: fontSize,
    			fontFamily: "Helvetica, Arial, sans-serif",
    			fontWeight: "bold",
    			left: leftPos,
    			top: topPos,
    			zIndex: 2
    		});
    		this.getLabelsDiv().append(labelEl); 
    	}
	},

	_fillFinish : function(side, gradientFunc) {
		if (0) {
			this.ctx.fillStyle = "#555555";
			this.ctx.fill();
		}
		var finishSide = this.finish[side];
		if (typeof(finishSide) == "string") {
			this.ctx.fillStyle = finishSide;
			this.ctx.fill();
		} else {
			for (l in finishSide) {
				var finishLayer = finishSide[l];
				if (typeof(finishLayer) == "string") {
					this.ctx.fillStyle = finishLayer;
					this.ctx.fill();
				} else {
					var grad = gradientFunc(finishLayer);
					this.ctx.fillStyle = grad;
					this.ctx.fill();
				}
			}
		}
	},
	
	_drawFace : function(x1, y1, x2, y2, isFrontFace) {
        this.ctx.save();
		this.ctx.beginPath();
		this.ctx.arc(0, y1, x1, Math.PI, 0, !isFrontFace);
		this.ctx.arc(0, y2, x2, 0, Math.PI, isFrontFace);
		
		var slope = (y1 - y2) / (x1 - x2);
		var midX = (x1 + x2) / 2;
		var midY = (y1 + y2) / 2;
		
//		if (isFrontFace) {
		    this.ctx.globalAlpha = this.frontOpacity;
//		}
		
		context = this.ctx;
		this._fillFinish( (isFrontFace) ? 'front' : 'back', 
		                function(gradSteps) {
							var grad;
							if (slope == Infinity) {
								grad = context.createLinearGradient(-x1, midY, x1, midY);
							} else {
								var dy = 2*midX/slope;
								grad = context.createLinearGradient(-midX, midY, midX, (isFrontFace) ? midY+dy : midY-dy);
							}
							return MountView._addGradientSteps(grad, gradSteps);
						});
		this.ctx.closePath();
		this.ctx.restore();
	}
};

MountView._gripGradient = function(isOutset, numGrips) {
	var gradient = [0.0, "rgba(0,0,0,0)"];

	// Add grips
	var thetaIncr = Math.PI / numGrips;
	var gripThirdWidth = 0.18 / numGrips;
	for (theta = 0.0; theta < Math.PI; theta += thetaIncr) {
	    var x = 0.5 * Math.cos(theta) + 0.5;
	    var dx = Math.sin(theta) * gripThirdWidth;
		
		// Don't put grips at the extremes
	    if (x < dx) continue;
	    if (x > 1-dx) continue;
		
	    gradient.push(x-dx, "rgba(0,0,0,0)");
	    if (isOutset) {
	    	gradient.push(x, "rgba(100,100,100,0.5)");
  			gradient.push(x+dx, "rgba(15,15,15,0.5)");
  		} else {
  		    gradient.push(x, "rgba(15,15,15,0.5)");
  		    gradient.push(x+dx, "rgba(100,100,100,0.5)");
  		}
	    gradient.push(x+2*dx, "rgba(0,0,0,0)");
	}
    gradient.push(1, "rgba(0,0,0,0)");
	return gradient;
};

MountView._addGradientSteps = function(gradient, steps) {
	for (i = 0; i < steps.length; i+=2) {
		gradient.addColorStop(steps[i], steps[i+1]);
	}
	return gradient;
};

MountView.FINISHES = {
	'glossyBlack': { 
		front:  [[0, "#222222", 0.23, "#333333", 0.25, "#888888", 0.27, "#333333", 1, "#111111"]],
		back:   [[0, "#000000", 0.73, "#131313", 0.75, "#555555", 0.77, "#111111", 1, "#000000"]],
		bottom: "#050505",
		label: "#dddddd"
	},                 
	'matteBlack': { 
		front:  [[0, "#222222", 0.25, "#444444", 1, "#111111"]],
		back:   [[0, "#050505", 0.75, "#222222", 1, "#000000"]],
		bottom: "#050505",
		label: "#dddddd"
	},             
	'matteSilver': {
		front:  [[0, "#555555", 0.25, "#888888", 1, "#444444"]],
		back:   [[0, "#333333", 0.75, "#666666", 1, "#333333"]],
		bottom: "#333333",
		label: "#111111"
	},
	'goPanoGrip': {
		front: [[0, "#222222", 0.25, "#333333", 1, "#111111"], MountView._gripGradient(true, 18)],
		back: [[0, "#000000", 0.73, "#131313", 0.75, "#555555", 0.77, "#111111", 1, "#000000"]],
		bottom: "#111111",
		label: "#dddddd"
	},
	'oneVRGrip': {
		front: [[0, "#3d6ab2", 0.25, "#487dd1", 1, "#355c9a"], MountView._gripGradient(false, 80)],
		back: [[0, "#000000", 0.73, "#131313", 0.75, "#555555", 0.77, "#111111", 1, "#000000"]],
		bottom: "#355c9a",
		label: "#111111"
	},
	'mirror': {
		front:  [[0, "#B4B6C7", 0.25, "#DCDFF2", 1, "#B4B6C7"]],
		back:   "#B4B6C7",
		bottom: "#B4B6C7",
		label: "#111111"
	}
};


function MountViewElement() {
	this.baseHeight = 4.0;
	this.femaleThreadDepth = 3.0;
	this.maleThreadLength = 2.0;
};
MountViewElement.prototype = {
	init : function(baseHeight, femaleThreadDepth, maleThreadDepth) {
		this.baseHeight = baseHeight;
		this.femaleThreadDepth = femaleThreadDepth;
		this.maleThreadLength = maleThreadLength;
	},
	getBaseHeight : function() {
		return this.baseHeight;
	},
	getFemaleThreadDepth : function() {
		return this.femaleThreadDepth;
	},
	getMaleThreadLength : function() {
		return this.maleThreadLength;
	},
	getMaxWidth : function() {
		var maxWidth = 0.0;
		for (var partIdx in this.drawParts) {
			var part = this.drawParts[partIdx];
			if (part.partWidth > maxWidth) {
				maxWidth = part.partWidth;
			}
		}
		return maxWidth;
	},
	getDrawHeight : function() { 
		return this.getBaseHeight();
	},
	getDrawAscent : function(aspectRatio) {
		var diam = (this.drawParts) ? this.drawParts[0].partWidth : 0;
		return diam/2 * aspectRatio;
	},
	getDrawDescent : function(aspectRatio) {
		var maxDescent = 0;
		if (this.drawParts)	for (var i=this.drawParts.length - 1; i>=0; i--) {
			var diam = this.drawParts[i].partWidth;
			var height = this.drawParts[i].partHeight;
			var thisDescent = diam/2 * aspectRatio;
			if (this.drawParts[i].thread) thisDescent += height;
			if (thisDescent > maxDescent) maxDescent = thisDescent;
		}
		return maxDescent;
	},
	cropElementToHeight : function(cropHeight) {
		if (!this.elementParts) {
			this.elementParts = this.drawParts;
		}
		var remainingHeight = cropHeight;
		var croppedParts = [];
		for (var p=this.elementParts.length-1; p>=0; p--) {
			var part = this.elementParts[p];
			if (part.partHeight > remainingHeight) {
				var remain = part.partHeight - remainingHeight;
				part.partHeight = remainingHeight;
				for (var y=1; y<part.partProfile.length; y+=2) {
					part.partProfile[y] -= remain;
				}
				croppedParts.unshift(part);
				break;
			}
			remainingHeight -= part.partHeight;
			croppedParts.unshift(part);
		}
		this.drawParts = croppedParts;
		this.baseHeight = cropHeight - this.maleThreadLength;
	},
	drawElement : function(view) { 
		for (var partIdx in this.drawParts) {
			var part = this.drawParts[partIdx];
			view.setFinishName(part.finish);
			if (part.partProfile) {
    			for (var profileIdx = 0; profileIdx < part.partProfile.length - 2; profileIdx+=2) {
    				this.drawProfile(view, part.partProfile, profileIdx);
    			}
			}
			if (part.offsetPartProfiles) {
			    for (var offsetIdx in part.offsetPartProfiles) {
			        var profile = part.offsetPartProfiles[offsetIdx];
			        view.translate(profile[0], profile[1]);
        			for (var profileIdx = 2; profileIdx < profile.length - 2; profileIdx+=2) {
        				this.drawProfile(view, profile, profileIdx);
        	        }
			        view.translate(-profile[0], -profile[1]);
			    }
			}
			if (part.partLabel) {
				view.drawLabel(part.partLabel, part.partWidth, part.partHeight);
			}
			if (part.thread) {
				view.drawThread(part.thread.diam, part.partHeight, part.thread.height, part.thread.pitch);
			}
			view.translate(0,part.partHeight);
		}
	},
	drawProfile : function(view, prof, idx) {
		// categorize
		if (prof[idx+0] == prof[idx+2]) {
			// cylinder
			view.drawCylinder(prof[idx+0]/2, prof[idx+1], prof[idx+3]);
		} else
		if (prof[idx+1] == prof[idx+3]) {
			// ring
			view.drawRing(prof[idx+0]/2, prof[idx+2]/2, prof[idx+1]);
		} else {
			// conic
			view.drawCone(prof[idx+0]/2, prof[idx+1], prof[idx+2]/2, prof[idx+3]);
		}
	}
};

MountViewElement.createFromDOM = function(domElement, optic) {	
	
	// Optics
	var isOneVR = /360 One VR/.test($(domElement).text());
	var isGoPanoPlus = /^GoPano Plus$/.test($(domElement).text());
	var isGoPano = /^GoPano$/.test($(domElement).text());
	if  (isOneVR || isGoPano || isGoPanoPlus) {
	    var opticDesc = $(domElement).attr("opticDesc");
	    var height = 0;
	    if (opticDesc) {
	        var res = /^([\d.]+)/.exec(opticDesc);
	        height = +res[1];
	    }
		var optic = null;
		if (isOneVR) optic = new MountViewOneVROptic(height);
		if (isGoPano) optic = new MountViewGoPanoOptic(height);
		if (isGoPanoPlus) optic = new MountViewGoPanoPlusOptic(height);
		var result = [optic];

		var fovDesc = $(domElement).attr("fovDesc");
		if (fovDesc) {
			var res = /^([\d.]+),([\d.]+),([\d.]+),([\d.]+),([\d.]+),([\d.]+)$/.exec(fovDesc);
			result.unshift(new MountViewCameraFieldOfView(+res[1], +res[2], +res[3], +res[4], +res[5], +res[6], optic));
		}
		return result;
	}

	// Quick Release
	var isQuickRelease = /Quick Release/.test($(domElement).text());
	if (isQuickRelease) {
		return new MountViewGoPanoQuickReleaseAdapter();
	}

	// Close-ups
	var res = /^([\d.]+)mm \+([\d.]+) Close-Up/.exec($(domElement).text());
	if (res) {
		return new MountViewCloseUp(+res[1], +res[2]);
	}
	
	// SLR Bracket
	var isSLRBracket = /SLR Bracket/.test($(domElement).text());
	if (isSLRBracket) {
		return new MountViewSLRBracket();
	}
	
	// Adjustable Camera Mount
	var res = /Adjustable Camera Mount/.exec($(domElement).text());
	if (res) {
	    var mountOffset = 20.0;
	    var mountLength = 200.0;

	    var mountDesc = $(domElement).attr("mountDesc");
	    if (mountDesc) {
            var res = /^([\d.]+),([\d.]+)$/.exec(mountDesc);
            mountOffset = +res[1];
            mountLength = +res[2];
	    }
	    
	    return new MountViewAdjustableCameraMount(mountLength, mountOffset);
	}
	
	// Camera Adapters
	var adapterDesc = $(domElement).attr("adapterDesc");
	if (adapterDesc) {
		var res = /^([\d.]+),([\d.]+),([\d.]+),(cylindrical|conical|curved|stepped),(glossyBlack|matteBlack|matteSilver)$/.exec(adapterDesc);
		return new MountViewCameraAdapter($(domElement).text(), +res[1], +res[2], +res[3], res[4], res[5]);
	}
	
	// Unknown camera adater
	var res = /^Unknown ([\d.]+)mm/.exec($(domElement).text());
	if (res) {
	    return new MountViewCameraAdapter("Unknown", +res[1], +res[1], 20, 'cylindrical', 'matteSilver');
	}
	
	// Step rings
	var res = /^([\d.]+) - ([\d.]+)mm Step/.exec($(domElement).text());
	if (res) {
		return new MountViewStepRing(+res[1], +res[2]);
	}
	
	// Spacer rings
	var res = /^([\d.]+) x ([\d.]+)mm Spacer Ring/.exec($(domElement).text());
	if (res) {
	    return new MountViewSpacerRing(+res[1], +res[2]);
	}
	
};

function MountViewStepRing(from, to, height) {
	this.fromSize = +from;
	this.toSize = +to;
	this.femaleThreadPitch = (this.toSize < 41) ? 0.5 : 0.75;
	this.maleThreadPitch = (this.fromSize < 41) ? 0.5 : 0.75;

	var baseDiam = 0.0;
	var topInnerDiam = 0.0;
	var bottomInnerDiam = 0.0;

	if (from > to) {
		// Step down
		baseDiam = this.fromSize;
		topInnerDiam = bottomInnerDiam = this.toSize;
		if (!height) height = 3.0;
		this.femaleThreadDepth = 5.0;
		this.maleThreadLength = 2.0;
	} else {
		// Step up
		baseDiam = this.toSize + 2.0;
		topInnerDiam = this.toSize;
		bottomInnerDiam = this.fromSize - 2.0;
		if (!height) height    = (to < 41) ? 3.0 : (to < 67) ? 4.0 : 5.0;
		this.femaleThreadDepth = (to < 41) ? 2.5 : (to < 67) ? 3.0 : 4.0;
		this.maleThreadLength  = (from < 41) ? 1.5 : (from < 67) ? 2.0 : 3.0;
	}
	this.baseHeight = height;
	
	this.drawParts = [
		{ finish: 'glossyBlack', 
		  partLabel: (this.fromSize + ' - ' + this.toSize + 'mm'),
		  partHeight: this.baseHeight,
		  partWidth: baseDiam,
		  partProfile: [ topInnerDiam, 0.0,
		                 baseDiam, 0.0, 
		                 baseDiam, this.baseHeight,
		 				 this.fromSize, this.baseHeight ]
		},
		{ finish: 'glossyBlack', 
		  thread: {diam: this.fromSize, height: this.maleThreadLength - this.maleThreadPitch, pitch: this.maleThreadPitch},
		  partHeight: this.maleThreadLength,
		  partWidth: this.fromSize,
		  partProfile: [ this.fromSize, 0.0,
		 				 this.fromSize, this.maleThreadLength, 
		                 bottomInnerDiam, this.maleThreadLength] 
		}
	];
};
MountViewStepRing.prototype = new MountViewElement();

function MountViewCloseUp(size, diopter) {
	MountViewStepRing.call(this, size, size, 8.0);
	this.diopter = diopter;
	this.drawParts[0].partLabel = "+" + this.diopter + " Close-Up";
}
MountViewCloseUp.inherits(MountViewStepRing);


function MountViewSpacerRing(size, length) {
	MountViewStepRing.call(this, size, size, length);
	this.drawParts[0].partLabel = length + "mm Spacer Ring";
}
MountViewSpacerRing.inherits(MountViewStepRing);

function MountViewSLRBracket() {
	this.toSize = 67.0;
	this.femaleThreadPitch = 0.75;
	this.drawParts = [
		{ 	finish: 'glossyBlack',
			partLabel: "SLR Bracket",
			partHeight: 12.0,
			partWidth: 100.0,
			partProfile: [ 67.0, 0.0, 100.0, 0.0, 100.0, 12.0, 67.0, 12.0 ]
		}
	];
};
MountViewSLRBracket.prototype = new MountViewElement();

function MountViewAdjustableCameraMount(mountLength, offset) {
    this.fromSize = 67.0;
    this.toSize = 67.0;
    this.baseHeight = offset;
    this.femaleThreadPitch = 0.75;
	this.femaleThreadDepth = 8.0;
    this.drawParts = [
        {
            finish: 'matteBlack',
            partHeight: 0.0,
            partWidth: 15.0,
            offsetPartProfiles: [ [-30.0, 0.0, 12.0, 0.0, 15.0, 0.0, 15.0, mountLength, 12.0, mountLength],
                                  [30.0, 0.0, 12.0, 0.0, 15.0, 0.0, 15.0, mountLength, 12.0, mountLength] ]
        },
        {
            finish: 'glossyBlack',
            partLabel: "Adjustable Camera Mount",
            partHeight: 8.0,
            partWidth: 80.0,
            partProfile: [ 67.0, 0.0, 80.0, 0.0, 80.0, 8.0, 67.0, 8.0 ]
        },
    ];
}
MountViewAdjustableCameraMount.prototype = new MountViewElement();

function MountViewCameraAdapter(name,from,to,height,shape,finish) {
	this.fromSize = +from;
	this.toSize = +to;
	this.baseHeight = +height;
	this.shape = shape;
	this.finish = finish;
	this.femaleThreadPitch = (this.toSize < 41) ? 0.5 : 0.75;
	this.maleThreadLength = 0;
	this.femaleThreadDepth = 3;
	
	var profile = [];
    var maxSize = (this.toSize > this.fromSize) ? this.toSize : this.fromSize;
	switch (shape) {
		case 'cylindrical':
			profile = [maxSize, 0.0, 
			           maxSize, this.baseHeight];
			break;
                case 'stepped':
		    profile = [this.toSize, 0.0,
			       this.toSize, 10.0,
			       this.fromSize, 10.0,
			       this.fromSize, height];
		    break;
		case 'conical':
		case 'curved':
			profile = [this.toSize, 0.0,
			           this.toSize, 10.0,
			   		   this.fromSize, height - 10.0,
					   this.fromSize, height ];
	}
	this.drawParts = [
		{
			finish: this.finish,
			partLabel: name,
			partWidth: maxSize + 2.0,
			partHeight: this.baseHeight,
			partProfile: profile
		}
	];
};
MountViewCameraAdapter.prototype = new MountViewElement();

function MountViewGoPanoQuickReleaseAdapter() {
	this.fromSize = 67.0;
	this.toSize = 67.0;
	this.baseHeight = 10.0;
	this.shape = 'cylindrical';
	this.maleThreadLength = 3.0;
	this.femaleThreadDepth = 9.0;
	
	this.drawParts = [
		{ finish: 'glossyBlack', partWidth: 94.0, partHeight: 7.0, partProfile: [ 67.0, 0.0, 94.0, 0.0, 94.0, 7.0, 92.0, 7.0, 67.0, 7.0 ] },
		{ finish: 'glossyBlack', partWidth: 94.0, partHeight: 3.0, partProfile: [ 67.0, 0, 92.0, 0.0, 92.0, 0.1, 94.0, 0.1, 94.0, 3.0, 67.0, 3.0 ] },
		{ finish: 'glossyBlack', partWidth: 67.0, partHeight: 3.0, partProfile: [ 67.0, 0.0, 67.0, 3.0, 65.0, 3.0 ],
		  thread: {diam: 67.0, height: 3.0, pitch: 0.75} }
	];
};
MountViewGoPanoQuickReleaseAdapter.prototype = new MountViewElement();

function MountViewOptic() {
	this.mirrorElementHeight = 39.0;
	this.mirrorApexHeight = 151.0;
	this.getMirrorElementHeight = function() { return this.mirrorElementHeight; };
	this.getMirrorBaseHeight = function() { return this.mirrorApexHeight + this.mirrorElementHeight; };
	this.getMirrorApexHeight = function() { return this.mirrorApexHeight; };
	this.get72mmMirrorProfile = function() {
		return [
			72.86,	0,
			72.86,	4.72,
			64.40,  13.04,
			58.24,	18.15,
			52.70,	22.13,
			48.00,	25.09,
			43.30,	27.69,
			40.14,	29.256,
			36.18,  31.025,
			32.32,  32.553,
			28.14,	34.004,
			24.38,	35.139,
			20.66,	36.109,
			17.02,  36.92,
			13.38,  37.595,
			10.06,  38.099,
			6.350,  39.000
		];
	};
	this.get88mmMirrorProfile = function() {
		return [
        88.64, 0.0,
        88.64, 2.0,
        83.45, 7.2698,
        76.73, 13.450,
        70.84, 18.272,
        65.53, 22.172,
        60.65, 25.405,
        56.09, 28.135,
        51.78, 30.470,
        47.68, 32.487,
        43.75, 34.242,
        39.96, 35.777,
        36.28, 37.123,
        32.71, 38.306,
        29.22, 39.344,
        25.80, 40.253,
        22.45, 41.047,
        19.14, 41.734,
        15.88, 42.323,
        12.66, 42.821,
        9.464, 43.233,
        6.291, 43.563,
        3.134, 43.814,
        0    , 43.988,
		];
	};
};
MountViewOptic.prototype = new MountViewElement();

function MountViewGoPanoPlusOptic(drawHeight) {
	if (!drawHeight) drawHeight = 208.0;
	this.fromSize = 67.0;
	this.maleThreadLength = 9.0;
	this.baseHeight = 208.0 - this.maleThreadLength;
	this.femaleThreadDepth = 0.0;
	this.mirrorApexHeight = this.baseHeight - 49.0;
	
	var mirrorProfile = this.get88mmMirrorProfile();
	this.elementParts = [
		{ finish: 'matteBlack', partWidth: 100.0, partHeight: 5.0, partProfile: [ 100.0, 0.0, 100.0, 2.0, 96.0, 5.0, 1.0, 5.0 ] },
		{ finish: 'mirror', partWidth: 88.64, partHeight: 44.0, partProfile: mirrorProfile },
		{ finish: 'glossyBlack', partWidth: 6.0, partHeight: 56.0, partProfile: [ 9.0, 0.0, 9.0, 2.0, 6.0, 2.0, 6.0, 56.0 ] },
		{ finish: 'glossyBlack', partWidth: 71.0, partHeight: 7.0, partProfile: [ 71.0, 0.0, 71.0, 7.0, 65.0, 7.0 ] },
		{ finish: 'glossyBlack', partWidth: 71.0, partHeight: 5.0, partProfile: [ 71.0, 0.0, 71.0, 5.0 ] },
		{ finish: 'goPanoGrip', partWidth: 73.0, partHeight: 75.0, partProfile: [ 71.0, 0.0, 73.0, 0.0, 73.0, 75.0, 71.0, 75.0 ] },
		{ finish: 'glossyBlack', partWidth: 71.0, partHeight: 5.0, partProfile: [ 71.0, 0.0, 71.0, 5.0, 67.0, 5.0 ] },
		{ finish: 'glossyBlack', partWidth: 67.0, partHeight: 9.0, partProfile: [ 67.0, 0.0, 67.0, 9.0, 60.0, 9.0 ],
		  thread: {diam: 67.0, height: 7.0, pitch: 0.75} }
	];
	this.drawParts = this.elementParts;
	if (drawHeight) this.cropElementToHeight(drawHeight);
};
MountViewGoPanoPlusOptic.prototype = new MountViewOptic();

function MountViewGoPanoOptic(drawHeight) {
	if (!drawHeight) drawHeight = 195.0;
	this.fromSize = 67.0;
	this.maleThreadLength = 9.0;
	this.baseHeight = 195.0 - this.maleThreadLength;
	this.femaleThreadDepth = 0.0;
	this.mirrorApexHeight = this.baseHeight - 49.0;
	
	var mirrorProfile = this.get72mmMirrorProfile();
	this.elementParts = [
		{ finish: 'glossyBlack', partWidth: 95.0, partHeight: 10.0, partProfile: [ 95.0, 0.0, 78.0, 9.0, 78.0, 10.0, 1.0, 10.0 ] },
		{ finish: 'mirror', partWidth: 72.86, partHeight: 39.0, partProfile: mirrorProfile },
		{ finish: 'glossyBlack', partWidth: 6.0, partHeight: 45.0, partProfile: [ 9.0, 0.0, 9.0, 2.0, 6.0, 2.0, 6.0, 45.0 ] },
		{ finish: 'glossyBlack', partWidth: 71.0, partHeight: 7.0, partProfile: [ 71.0, 0.0, 71.0, 7.0, 65.0, 7.0 ] },
		{ finish: 'glossyBlack', partWidth: 71.0, partHeight: 5.0, partProfile: [ 71.0, 0.0, 71.0, 5.0 ] },
		{ finish: 'goPanoGrip', partWidth: 73.0, partHeight: 75.0, partProfile: [ 71.0, 0.0, 73.0, 0.0, 73.0, 75.0, 71.0, 75.0 ] },
		{ finish: 'glossyBlack', partWidth: 71.0, partHeight: 5.0, partProfile: [ 71.0, 0.0, 71.0, 5.0, 67.0, 5.0 ] },
		{ finish: 'glossyBlack', partWidth: 67.0, partHeight: 9.0, partProfile: [ 67.0, 0.0, 67.0, 9.0, 60.0, 9.0 ],
		  thread: {diam: 67.0, height: 7.0, pitch: 0.75} }
	];
	this.drawParts = this.elementParts;
	if (drawHeight) this.cropElementToHeight(drawHeight);
};
MountViewGoPanoOptic.prototype = new MountViewOptic();

function MountViewOneVROptic(drawHeight) {
	if (!drawHeight) drawHeight = 197.5;
	this.fromSize = 67.0;
	this.maleThreadLength = 4.0;
	this.baseHeight = 197.5 - this.maleThreadLength;
	this.femaleThreadDepth = 0.0;
	this.mirrorApexHeight = this.baseHeight - 49.0;

	var mirrorProfile = this.get72mmMirrorProfile();
	this.elementParts = [
		{ finish: 'matteBlack', partWidth: 92.0, partHeight: 12.0, partProfile: [ 92.0, 0.0, 80.0, 10.0, 80.0, 12.0, 1.0, 12.0 ] },
		{ finish: 'mirror', partWidth: 72.86, partHeight: 39.0, partProfile: mirrorProfile },
		{ finish: 'matteBlack', partWidth: 9.0, partHeight: 47.0, partProfile: [ 12.5, 0.0, 12.5, 2.0, 9.0, 2.0, 9.0, 47.0 ] },
		{ finish: 'matteBlack', partWidth: 76.0, partHeight: 17.0, partProfile: [ 74.0, 0.0, 74.0, 1.5, 76.0, 6.0, 74.0, 8.5, 76.0, 10.0, 76.0, 11.5, 74.0, 17.0 ] },
		{ finish: 'oneVRGrip', partWidth: 76.0, partHeight: 62.0, partProfile: [ 74.0, 0.0, 76.0, 0.0, 76.0, 62.0, 74.0, 62.0 ] },
		{ finish: 'glossyBlack', partWidth: 82.0, partHeight: 16.5, partProfile: 
			[ 74.0, 0.0, 76.0, 6.0, 76.0, 7.5, 73.0, 9.0, 76.0, 10.5, 76.0, 15.5, 82.0, 15.5, 82.0, 16.5, 67.0, 16.5 ] },
		{ finish: 'glossyBlack', partWidth: 67.0, partHeight: 4.0, partProfile: [ 67.0, 0.0, 67.0, 4.0 ],
		  thread: {diam: 67.0, height: 3.0, pitch: 0.75} }
	];
	this.drawParts = this.elementParts;
	if (drawHeight) this.cropElementToHeight(drawHeight);
};
MountViewOneVROptic.prototype = new MountViewOptic();

function MountViewCameraFieldOfView(minAngle, maxAngle, selectedAngle, minWD, maxWD, distanceFromMirror, optic) {
	this.fromSize = 0.0;
	this.toSize = 0.0;
	this.maleThreadLength = 0.0;
	this.femaleThreadDepth = 100.0;
	this.yTop = 0;
	this.minAngle = minAngle * Math.PI / 180 / 2;
	this.maxAngle = maxAngle * Math.PI / 180 / 2;
	this.selectedAngle = selectedAngle * Math.PI / 180 / 2;
	
	this.setOptic = function(optic) {
		this.optic = optic;
		this.yTop = optic.getBaseHeight() - optic.getMirrorBaseHeight();
		this.maleThreadLength = optic.getMaleThreadLength();
	};
	if (optic) this.setOptic(optic);
	
	this.getMaxWidth = function() {
	    var dy = distanceFromMirror + this.optic.getMirrorElementHeight();
		var width = 2 * Math.tan(this.maxAngle) * dy;
		return width;
	};
	
	this.getDrawAscent = function(aspectRatio) {
		return 0;
	};
	
	this.getDrawDescent = function(aspectRatio) {
		return 0;
	};
	
	this.getDrawHeight = function() {
		return distanceFromMirror - this.optic.getMirrorBaseHeight() + this.yTop;
	};
	
	this.drawElement = function(view) {
		if (!this.optic) {
			this.setOptic( view.getOptic() );
		}
		
		var ctx = view.getContext();
	    view.save();
		view.translate(0, -view.position[1]/view.ctxScale[1]);
		view.translate(0, this.yTop);
	    
	    var dy = distanceFromMirror + this.optic.getMirrorElementHeight();
		var minTan = Math.tan(this.minAngle);
		var maxTan = Math.tan(this.maxAngle);
	    var xMin = minTan * dy;
	    var xMax = maxTan * dy;
		var xSel = Math.tan(this.selectedAngle) * dy;

		// View range
	    ctx.beginPath();
	    ctx.moveTo(0.0, dy);
	
	    ctx.lineTo(xMin, 0);
	    ctx.lineTo(xMax, 0);
        ctx.lineTo(0.0, dy);
	    ctx.lineTo(-xMax, 0);
	    ctx.lineTo(-xMin, 0);
        ctx.lineTo(0.0, dy);
        ctx.fillStyle = "#FCED75";
        ctx.fill();
		ctx.closePath();
		
		
		// Selected view
		ctx.beginPath();
	    ctx.moveTo(-xSel, 0);
	    ctx.lineTo(0.0, dy);
	    ctx.lineTo(xSel, 0);
		ctx.strokeStyle = "#4B89D0";
		ctx.stroke();
		ctx.closePath();
	    
	    view.restore();
    };
};
MountViewCameraFieldOfView.prototype = new MountViewElement();

function renderMountForList(listElement, canvasElement)
{
	mountView = new MountView();
	mountView.init(canvasElement);
	mountView.setDisplayDPI(96*2);

    var spacing = $(canvasElement).attr("elementSpacing");
    if (spacing) {
        mountView.setElementSpacing(+spacing);
    }

	mountView.setMountElementsFromDOM(listElement);	
	return mountView;
};

function renderMountForDOM(element)
{
	// Create a canvas within the element
	if ($("canvas", element).length == 0) {
		$("ul,ol",element).wrap("<canvas></canvas>");
	}
    var mountCanvas = $("canvas", element)[0];
    if (mountCanvas) {
        return renderMountForList(element, mountCanvas);
    };
};

