/// <reference path="Mediasite.Geometry.js"/>

if (!Mediasite) {
    var Mediasite = {};
}
Mediasite.Player = Mediasite.Player || {};


//=============================================================================
// MultiViewLayout Module
//=============================================================================
Mediasite.Player.MultiViewLayout = (function (geometry, undefined) {

    //-------------------------------------------------------------------------
    // LayoutEngine
    //-------------------------------------------------------------------------
    function LayoutEngine(repertoire) {
		var that = this;
		var _repertoire = repertoire || LayoutCalculatorRepertoire.defaultRepertoire();

		this.scoreCalculator = calculateLayoutScore;

        this.getLayout = function (layoutSpec) {
			
			var scoredLayouts = this.getAllLayouts(layoutSpec);
			
			if (scoredLayouts.length > 0) {
				return scoredLayouts[0];
			} else {
				return new Layout();
			}

        };
		
		this.getAllLayouts = function(layoutSpec) {
            var i, layout,
            layouts = [];


            // Get and score the result from each layout calculator.
			_repertoire.each(function (description, calculator) {
				if (calculator.supportsLayoutSpec(layoutSpec)) {
					layout = calculator.getLayout(layoutSpec);
					layout.score = that.scoreCalculator(layoutSpec, layout);
					layout.description = description;
					layouts.push(layout);
				}
			});

			// Sort the resulting layouts by score.
			layouts.sort(function(a, b) { return b.score - a.score; });


            return layouts;
		};

    }
	
	function LayoutCalculatorRepertoire () {
		var _entries = [];
		var that = this;
		
		this.add = function (description, calculator) {
			_entries.push({ description: description, calculator: calculator });
			return that;
		};
		
		this.clear = function () {
			_entries = [];
			return that;
		};
		
		this.each = function (func) {
			var i;
			for (i = 0; i < _entries.length; i++) {
				func(_entries[i].description, _entries[i].calculator);
			}
			return that;
		};
	}
	LayoutCalculatorRepertoire.defaultRepertoire = function() {
		return new LayoutCalculatorRepertoire()
			.add('Balanced Horizontal',                      new LayoutCalculator.BalancedHorizontal())
			.add('Balanced Vertical',                        new LayoutCalculator.BalancedVertical())
			.add('Biased Horizontal + Balanced Vertical',    new LayoutCalculator.BiasedHorizontal(new LayoutCalculator.BalancedVertical()))
			.add('Biased Horizontal + Balanced Horizontal',  new LayoutCalculator.BiasedHorizontal(new LayoutCalculator.BalancedHorizontal()))
			.add('Biased Horizontal + Two Rows',             new LayoutCalculator.BiasedHorizontal(new LayoutCalculator.TwoRows()))
			.add('Biased Horizontal + Two Columns',          new LayoutCalculator.BiasedHorizontal(new LayoutCalculator.TwoColumns()))
	    	.add('Biased Vertical + Balanced Horizontal',    new LayoutCalculator.BiasedVertical(new LayoutCalculator.BalancedHorizontal()))
			.add('Biased Vertical + Balanced Vertical',      new LayoutCalculator.BiasedVertical(new LayoutCalculator.BalancedVertical()))
			.add('Biased Vertical + Two Rows',               new LayoutCalculator.BiasedVertical(new LayoutCalculator.TwoRows()))
			.add('Biased Vertical + Two Columns',            new LayoutCalculator.BiasedVertical(new LayoutCalculator.TwoColumns()))
			.add('Two Rows',                                 new LayoutCalculator.TwoRows())
			.add('Two Columns',                              new LayoutCalculator.TwoColumns());
	};
	
	
	function calculateLayoutScore(layoutSpec, layout) {
		var scoreBasis, rawScore;
		var order, streamIdentifier, streamBox, area, areaWeight;
		
		// The golden ratio is about perfect for our 
		// weight falloff calculation.
		var phi = (1 + Math.sqrt(5)) / 2;
		var weightDegeneration = 1 / phi;
		
		function getArea(streamSpec) {
			var identifier = streamSpec.identifier;
			var box = layout.findBox(identifier);
			return box.rect.area();
		}
		
		scoreBasis = layoutSpec.boundingSize.area();
		rawScore = 0;
		
		for (order = 0; order < layoutSpec.streamSpecs.length; order++) {
			area = getArea(layoutSpec.streamSpecs[order]);
			areaWeight = Math.pow(weightDegeneration, order);
			rawScore += area * areaWeight;
		}
		
		// Normalize the score so that 1.0 is perfect.
		return rawScore / scoreBasis;
	}
	
	function calculateMinorLayoutScore(layoutSpec, layout) {
		var scoreBasis, rawScore;
		var order, streamIdentifier, streamBox, area, areaWeight;
		
		function getArea(streamSpec) {
			var identifier = streamSpec.identifier;
			var box = layout.findBox(identifier);
			return box.rect.area();
		}
		
		scoreBasis = layoutSpec.boundingSize.area();
		rawScore = 0;
		
		for (order = 0; order < layoutSpec.streamSpecs.length; order++) {
			area = getArea(layoutSpec.streamSpecs[order]);
			areaWeight = 1;
			rawScore += area * areaWeight;
		}
		
		// Normalize the score so that 1.0 is perfect.
		return rawScore / scoreBasis;
	}


    function LayoutSpec() {
        this.streamSpecs = [];
    }
    LayoutSpec.prototype = {
        streamSpecs: [],
        boundingSize: geometry.Size.Zero(),
        gutterBetween: 15,
        balance: false,
        overlap: false,

        getTwisted: function () {
            var twistedSpec = new LayoutSpec();
            twistedSpec.boundingSize = new geometry.Size(this.boundingSize.height(), this.boundingSize.width());
			twistedSpec.gutterBetween = this.gutterBetween;
			twistedSpec.balanced = this.balanced;
			twistedSpec.overlap = this.overlap;
            for (i = 0; i < this.streamSpecs.length; i++) {
                normalStream = this.streamSpecs[i];
                twistedStream = { identifier: normalStream.identifier, aspectRatio: (1 / normalStream.aspectRatio) };
                twistedSpec.streamSpecs.push(twistedStream);
            }
            return twistedSpec;
        }
    };

    function StreamSpec() {
        this.identifier = '';
        this.aspectRatio = 16 / 9;
    }


    function Layout() {
        this._boxes = [];
    }
    Layout.prototype = {
        addBox: function (box) {
            this._boxes.push(box);
        },
        findBox: function (identifier) {
            var i;
            for (i = 0; i < this._boxes.length; i++) {
                if (this._boxes[i].identifier === identifier) {
                    return this._boxes[i];
                }
            }
            return undefined;
        },
        allBoxes: function () {
            return this._boxes.slice(0);
        },

        occupiedRect: function () {
            var minLeft = Number.MAX_VALUE,
                maxRight = Number.MIN_VALUE,
                minTop = Number.MAX_VALUE,
                maxBottom = Number.MIN_VALUE,
                width,
                height;

            // There's not really a sensible result if the layout has no boxes.
            if (this._boxes.length < 1) {
                return new geometry.Rect(0, 0, 0, 0);
            }

            this.forEachBox(function (box) {
                minLeft = Math.min(minLeft, box.rect.left());
                maxRight = Math.max(maxRight, box.rect.right());
                minTop = Math.min(minTop, box.rect.top());
                maxBottom = Math.max(maxBottom, box.rect.bottom());
            });

            width = maxRight - minLeft;
            height = maxBottom - minTop;

            return new geometry.Rect(minLeft, minTop, width, height);
        },

        translate: function (x, y) {
            this.forEachBox(function (box) {
                box.rect = new geometry.Rect(box.rect.left() + x, box.rect.top() + y, box.rect.width(), box.rect.height());
            });
        },

        moveTo: function (x, y) {
            var occupied = this.occupiedRect();
            var xDiff, yDiff;

            xDiff = x - occupied.left();
            yDiff = y - occupied.top();

            this.translate(xDiff, yDiff);
        },

        centerWithin: function (containerSize) {
            var occupied, widthDiff, heightDiff, newX, newY;

            occupied = this.occupiedRect();
            widthDiff = containerSize.width() - occupied.width();
            heightDiff = containerSize.height() - occupied.height();

            newX = widthDiff / 2;
            newY = heightDiff / 2;

            this.moveTo(newX, newY);
        },

        forEachBox: function (func) {
            var i;
            for (i = 0; i < this._boxes.length; i++) {
                func(this._boxes[i]);
            }
        },

        rotateRight: function (boundingSize) {
            var newLeft;

            this.forEachBox(function (box) {
                newLeft = boundingSize.height() - box.rect.bottom();
                box.rect = new geometry.Rect(newLeft, box.rect.left(), box.rect.height(), box.rect.width());
            });
        },

        rotateLeft: function (boundingSize) {
            var newTop;

            this.forEachBox(function (box) {
                newTop = boundingSize.width() - box.rect.right();
                box.rect = new geometry.Rect(box.rect.top(), newTop, box.rect.height(), box.rect.width());
            });
        },

        flipHorizontal: function (boundingSize) {
            this.forEachBox(function (box) {
                box.rect = copyRect(box.rect, { left: boundingSize.width() - box.rect.right() });
            });
        },
        
        scale: function (factor) {
            this.forEachBox(function (box) {
                box.rect = new geometry.Rect(box.rect.left() * factor, box.rect.top() * factor, box.rect.width() * factor, box.rect.height() * factor);
            });
        }
    };

    function StreamBox(identifier, rect) {
        this.identifier = identifier;
        this.rect = rect;
    }
	StreamBox.prototype = {
		area: function() { return this.rect.width() * this.rect.height(); }
	};



    var LayoutCalculator = {};

	
	//----------------------------------------------------------------------------
	// LayoutCalculator objects must implement the following interface.
	// 
	//    supportsLayoutSpec(layoutSpec)  :  returns Boolean
	//    getLayout(layoutSpec)           :  returns Layout
	//
	//----------------------------------------------------------------------------



    // BalancedHorizontal
    // ------------------
    // Boxes have equal height and are ordered left to right.
    //
    LayoutCalculator.BalancedHorizontal = function () {
        this.supportsLayoutSpec = function (layoutSpec) {
            return layoutSpec.streamSpecs.length > 0 && !layoutSpec.overlap;
        };

        this.getLayout = function (layoutSpec) {
            var layout = new Layout(),
                pseudoAspect = 0,
                nextLeft,
                i, gutterlessWidth, gutterlessBoundingRect, combinedRect, streamSpec, streamRect;

            // 1. Calculate pseudo aspect ratio of all stream boxes lined up with equal heights.
            for (i = 0; i < layoutSpec.streamSpecs.length; i++) {
                pseudoAspect += layoutSpec.streamSpecs[i].aspectRatio;
            }

            // 2. Calculate the rectangle of said pseudo aspect ratio that fits within the bounding area.
            gutterlessWidth = layoutSpec.boundingSize.width() - (layoutSpec.streamSpecs.length - 1) * layoutSpec.gutterBetween;
            gutterlessBoundingRect = new geometry.Rect(0, 0, gutterlessWidth, layoutSpec.boundingSize.height());
            combinedRect = getFittedRect(gutterlessBoundingRect, pseudoAspect);

            // 3. Divide into rectangles for the streams.
            nextLeft = combinedRect.left();
            for (i = 0; i < layoutSpec.streamSpecs.length; i++) {
                streamSpec = layoutSpec.streamSpecs[i];
                streamRect = new geometry.Rect(nextLeft, combinedRect.top(), combinedRect.height() * streamSpec.aspectRatio, combinedRect.height());
                layout.addBox(new StreamBox(streamSpec.identifier, streamRect));
                nextLeft = streamRect.right() + layoutSpec.gutterBetween;
            }

            // 4. Return the layout.
			layout.calculatedBy = 'BalancedHorizontal';
            return layout;
        };
    };


    // BalancedVertical
    // ------------------
    // Boxes have equal width and are ordered top to bottom.
    //
    LayoutCalculator.BalancedVertical = function () {
        var horizontalStrategy = new LayoutCalculator.BalancedHorizontal();

        this.supportsLayoutSpec = function (layoutSpec) {
            return layoutSpec.streamSpecs.length > 0 && !layoutSpec.overlap;
        };

        this.getLayout = function (layoutSpec) {
            var i, twistedSpec, layout;

            // 1. Twist the layout spec.
            twistedSpec = layoutSpec.getTwisted();

            // 2. Get the twisted layout.
            layout = horizontalStrategy.getLayout(twistedSpec);

            // 3. Untwist the layout.
            layout.rotateRight(twistedSpec.boundingSize);

			layout.calculatedBy = 'BalancedVertical';
            return layout;
        };
    };
	
	
    // TwoRows
    // ------------------
    // Boxes are arranged in two rows. When the number of streams is odd, 
	// the top row has one fewer than the bottom.
    //
    LayoutCalculator.TwoRows = function () {
		var rowStrategy = new LayoutCalculator.BalancedHorizontal();
		
		
        this.supportsLayoutSpec = function (layoutSpec) {
            return layoutSpec.streamSpecs.length > 2 && !layoutSpec.overlap;
        };

        this.getLayout = function (layoutSpec) {
            var layout, 
				stageHeight = layoutSpec.boundingSize.height(),
				stageWidth = layoutSpec.boundingSize.width();
				
			
			// 1. Start building the row layout specs.
			var streamsInTopRow = Math.floor(layoutSpec.streamSpecs.length / 2);
			var streamsInBottomRow = layoutSpec.streamSpecs.length - streamsInTopRow;
			
			var topRowSpec = newRowLayoutSpec(layoutSpec);
			topRowSpec.streamSpecs = layoutSpec.streamSpecs.slice(0, streamsInTopRow);
			var bottomRowSpec = newRowLayoutSpec(layoutSpec);
			bottomRowSpec.streamSpecs = layoutSpec.streamSpecs.slice(streamsInTopRow);
				
			// 2. Calculate bounding size.
			// Assuming that we correctly calculate the "just fits" layout width, 
			// the height set for each row's spec won't matter.
			var layoutWidth = calcJustFitsWidth(layoutSpec.boundingSize.height(), topRowSpec.streamSpecs, bottomRowSpec.streamSpecs, layoutSpec.gutterBetween);
			layoutWidth = Math.min(layoutWidth, layoutSpec.boundingSize.width());
			topRowSpec.boundingSize = bottomRowSpec.boundingSize = new geometry.Size(layoutWidth, layoutSpec.boundingSize.height());
			
			// 3. Calculate row layouts.
			var topRowLayout = rowStrategy.getLayout(topRowSpec);
			var bottomRowLayout = rowStrategy.getLayout(bottomRowSpec);
			
			// 4. Assemble and arrange the final layout.
			bottomRowLayout.moveTo(bottomRowLayout.occupiedRect().left(), topRowLayout.occupiedRect().bottom() + layoutSpec.gutterBetween);
			
			layout = new Layout();
			topRowLayout.forEachBox(function(box) {
				layout.addBox(box);
			});
			bottomRowLayout.forEachBox(function(box) {
				layout.addBox(box);
			});
			layout.centerWithin(layoutSpec.boundingSize);
			
			layout.calculatedBy = 'TwoRows';
			return layout;
        };
		
		
		function calcJustFitsWidth(layoutHeight, topRowStreamSpecs, bottomRowStreamSpecs, gutter) {
			// The following formula calculates the perfect layout width through the power of maths.
			// 
			//      (aTop x aBottom x H)  -  (aTop x aBottom x gVert)  +  (aTop x gBottom)  +  (aBottom x gTop)
			// W = --------------------------------------------------------------------------------------------
			//                                          (aTop + aBottom)
			// 
			// 
			//    W:       layout width                      gVert: gutter between rows
			//    H:       layout height                     gTop:  sum top row gutters
			//    aTop:    sum top box aspect ratios         gBottom: sum bottom row gutters
			//    aBottom: sum bottom box aspect ratios
			//    
			
			var gVert = gutter;
			var gTop = (topRowStreamSpecs.length - 1) * gutter;
			var aTop = sumRowAspectRatios(topRowStreamSpecs);
			var gBottom = (bottomRowStreamSpecs.length - 1) * gutter;
			var aBottom = sumRowAspectRatios(bottomRowStreamSpecs);
			
			return ((aTop * aBottom * layoutHeight) - (aTop * aBottom * gVert) + (aTop * gBottom) + (aBottom * gTop)) / (aTop + aBottom);
		}
		
		function sumRowAspectRatios(streamSpecs) {
			var i, 
				aspect = 0;
				
			for (i = 0; i < streamSpecs.length; i++) {
				aspect += streamSpecs[i].aspectRatio;
			}
			
			return aspect;
		}
		
		function newRowLayoutSpec(fullLayoutSpec) {
			var result = new LayoutSpec();
			result.gutterBetween = fullLayoutSpec.gutterBetween;
			result.balanced = true;
			return result;
		}
		
    };


    // TwoColumns
    // ------------------
    // Boxes are arranged in two rows. When the number of streams is odd, 
	// the top row has one fewer than the bottom.
    //
    LayoutCalculator.TwoColumns = function () {
		var otherStrategy = new LayoutCalculator.TwoRows();
		
		
        this.supportsLayoutSpec = function (layoutSpec) {
            return layoutSpec.streamSpecs.length > 2 && layoutSpec.streamSpecs.length % 2 === 1 && !layoutSpec.overlap;
        };

        this.getLayout = function (layoutSpec) {
            var i, twistedSpec, layout;

            // 1. Twist the layout spec.
            twistedSpec = layoutSpec.getTwisted();

            // 2. Get the twisted layout.
            layout = otherStrategy.getLayout(twistedSpec);

            // 3. Untwist the layout.
            layout.rotateRight(twistedSpec.boundingSize);
			layout.flipHorizontal(layoutSpec.boundingSize);

			layout.calculatedBy = 'TwoColumns';
            return layout;
        };
    };
	
	
    // BiasedHorizontal
    // ------------------
    // Horizontal side-by-side layout in which the primary box is given more visual weight.
    //
    LayoutCalculator.BiasedHorizontal = function (minorStrategy, minorColumnWidthFraction) {
        var otherStrategy = minorStrategy;
		var minorColumnWidthFraction = minorColumnWidthFraction || 0.25;

        this.supportsLayoutSpec = function (layoutSpec) {
			var minorSpec, minorSpecClaimsSupport;
			
			minorSpec = new LayoutSpec();
			minorSpec.streamSpecs = layoutSpec.streamSpecs.slice(1);
			minorSpecClaimsSupport = otherStrategy.supportsLayoutSpec(minorSpec);
            return layoutSpec.streamSpecs.length > 1 && !layoutSpec.balanced && minorSpecClaimsSupport;
        };

        this.getLayout = function (layoutSpec) {
            var layout, 
				minorColumnWidth, 
				majorColumnWidth, 
				majorColumn, 
				majorRect, 
				minorLayoutSpec,
				minorLayout, 
				minorOccupiedRect, 
				gutter = layoutSpec.gutterBetween, 
				stageWidth = layoutSpec.boundingSize.width(), 
				stageHeight = layoutSpec.boundingSize.height(), 
				majorAspect = layoutSpec.streamSpecs[0].aspectRatio;
		
			
			// 1. Calculate the major rect to fit the default major column width.
			minorColumnWidth = stageWidth * minorColumnWidthFraction;
			majorColumnWidth = stageWidth - minorColumnWidth - gutter;
			majorRect = getFittedRect(newRectAtZero(majorColumnWidth, stageHeight), majorAspect);
			
			// 2. Calculate the minor layout to fit the stage width not used by the major rect and gutter.
			minorColumnWidth = stageWidth - majorRect.width() - gutter;
			minorLayoutSpec = new LayoutSpec();
            minorLayoutSpec.boundingSize = new geometry.Size(minorColumnWidth, stageHeight);
			minorLayoutSpec.gutterBetween = layoutSpec.gutterBetween;
            minorLayoutSpec.balanced = true;
            minorLayoutSpec.streamSpecs = layoutSpec.streamSpecs.slice(1);
            minorLayout = otherStrategy.getLayout(minorLayoutSpec);
			minorOccupiedRect = minorLayout.occupiedRect();
			
			// 3. Recalculate the major rect to fit the stage width not used by the minor layout and gutter.
			majorColumnWidth = stageWidth - minorOccupiedRect.width() - gutter;
			majorRect = getFittedRect(newRectAtZero(majorColumnWidth, stageHeight), majorAspect);
			
			// 4. Assemble and arrange the final layout.
			minorLayout.moveTo(majorRect.right() + gutter, minorOccupiedRect.top());
			
            layout = new Layout();
			layout.addBox(new StreamBox(layoutSpec.streamSpecs[0].identifier, majorRect));
            minorLayout.forEachBox(function (minorBox) {
                layout.addBox(minorBox);
            });
            layout.centerWithin(layoutSpec.boundingSize);
			
			layout.calculatedBy = 'BiasedHorizontal ' + minorLayout.calculatedBy;
			return layout;
        };
		
		function newRectAtZero(width, height) {
			return new geometry.Rect(0, 0, width, height);
		}
    };


    // BiasedVertical
    // ------------------
    // Vertical side-by-side layout in which the primary box is given more visual weight.
    //
    LayoutCalculator.BiasedVertical = function (minorStrategy, minorColumnWidthFraction) {
        var otherStrategy = minorStrategy;
        var minorRowHeightFraction = minorColumnWidthFraction || 0.25;

        this.supportsLayoutSpec = function (layoutSpec) {
			var minorSpec, minorSpecClaimsSupport;
			
			minorSpec = new LayoutSpec();
			minorSpec.streamSpecs = layoutSpec.streamSpecs.slice(1);
			minorSpecClaimsSupport = otherStrategy.supportsLayoutSpec(minorSpec);
            return layoutSpec.streamSpecs.length > 1 && !layoutSpec.balanced && minorSpecClaimsSupport;
            
        };

        this.getLayout = function (layoutSpec) {
            var layout, 
				minorRowHeight, 
				majorRowHeight, 
				majorRow, 
				majorRect, 
				minorLayoutSpec,
				minorLayout, 
				minorOccupiedRect, 
				gutter = layoutSpec.gutterBetween, 
				stageWidth = layoutSpec.boundingSize.width(), 
				stageHeight = layoutSpec.boundingSize.height(), 
				majorAspect = layoutSpec.streamSpecs[0].aspectRatio;
		
			
			// 1. Calculate the major rect to fit the default major row height.
			minorRowHeight = stageHeight * minorRowHeightFraction;
			majorRowHeight = stageHeight - minorRowHeight - gutter;
			majorRect = getFittedRect(newRectAtZero(stageWidth, majorRowHeight), majorAspect);
			
			// 2. Calculate the minor layout to fit the stage height not used by the major rect and gutter.
			minorRowHeight = stageHeight - majorRect.height() - gutter;
			minorLayoutSpec = new LayoutSpec();
            minorLayoutSpec.boundingSize = new geometry.Size(stageWidth, minorRowHeight);
			minorLayoutSpec.gutterBetween = layoutSpec.gutterBetween;
            minorLayoutSpec.balanced = true;
            minorLayoutSpec.streamSpecs = layoutSpec.streamSpecs.slice(1);
            minorLayout = otherStrategy.getLayout(minorLayoutSpec);
			minorOccupiedRect = minorLayout.occupiedRect();
			
			// 3. Recalculate the major rect to fit the stage height not used by the minor layout and gutter.
			majorRowHeight = stageHeight - minorOccupiedRect.height() - gutter;
			majorRect = getFittedRect(newRectAtZero(stageWidth, majorRowHeight), majorAspect);
			
			// 4. Assemble and arrange the final layout.
			minorLayout.moveTo(minorOccupiedRect.left(), majorRect.bottom() + gutter);
			
            layout = new Layout();
			layout.addBox(new StreamBox(layoutSpec.streamSpecs[0].identifier, majorRect));
            minorLayout.forEachBox(function (minorBox) {
                layout.addBox(minorBox);
            });
            layout.centerWithin(layoutSpec.boundingSize);
			
			layout.calculatedBy = 'BiasedVertical ' + minorLayout.calculatedBy;
			return layout;
        };
		
		function newRectAtZero(width, height) {
			return new geometry.Rect(0, 0, width, height);
		}
    };
    
    
    // PrimaryOnly
    // ------------------
    // Single box layout that displays just the primary stream.
    //
    LayoutCalculator.PrimaryOnly = function () {
        this.supportsLayoutSpec = function (layoutSpec) {
			return layoutSpec.streamSpecs.length > 0;
        };

        this.getLayout = function (layoutSpec) {
            var rect, 
                layout, 
				stageWidth = layoutSpec.boundingSize.width(), 
				stageHeight = layoutSpec.boundingSize.height(), 
				primaryAspect = layoutSpec.streamSpecs[0].aspectRatio,
                primaryIdentifier = layoutSpec.streamSpecs[0].identifier;
		
			
			rect = getFittedRect(newRectAtZero(stageWidth, stageHeight), primaryAspect);
            layout = new Layout();
			layout.addBox(new StreamBox(primaryIdentifier, rect));
			
			layout.calculatedBy = 'PrimaryOnly';
			return layout;
        };
		
		function newRectAtZero(width, height) {
			return new geometry.Rect(0, 0, width, height);
		}
    };




    function getFittedRect(rect, aspect) {
        var boundingAspect = rect.width() / rect.height();

        var height = rect.height();
        var width = rect.width();
        var top = rect.top();
        var left = rect.left();


        if (boundingAspect > aspect) {
            // Bounding Rect has wider aspect.
            width = height * aspect;
            left = (rect.width() - width) / 2.0 + rect.left();
        }
        else {
            // Bounding Rect has taller aspect.
            height = width / aspect;
            top = (rect.height() - height) / 2.0 + rect.top();
        }

        return new geometry.Rect(left, top, width, height);
    }

    function copyRect(rect, changeProps) {
        var left = 0,
            top = 0,
            width = 0,
            height = 0;

        if (changeProps) {
            left = (typeof changeProps.left === 'undefined') ? rect.left() : changeProps.left;
            top = (typeof changeProps.top === 'undefined') ? rect.top() : changeProps.top;
            width = (typeof changeProps.width === 'undefined') ? rect.width() : changeProps.width;
            height = (typeof changeProps.height === 'undefined') ? rect.height() : changeProps.height;
        }

        return new geometry.Rect(left, top, width, height);
    }


    // Public Module Defined
    return {
        LayoutEngine: LayoutEngine,
        Layout: Layout,
        LayoutSpec: LayoutSpec,
        StreamSpec: StreamSpec,
        LayoutCalculator: LayoutCalculator,
		LayoutCalculatorRepertoire: LayoutCalculatorRepertoire
    };

})(Mediasite.Geometry);

