/*
      mustache.js — Logic-less templates in JavaScript
      See http://mustache.github.com/ for more info.
*/
(function(window){var Mustache=function(){var Renderer=function(){};Renderer.prototype={otag:"{{",ctag:"}}",pragmas:{},buffer:[],pragmas_implemented:{"IMPLICIT-ITERATOR":true},context:{},render:function(template,context,partials,in_recursion){if(!in_recursion){this.context=context;this.buffer=[];}
if(!this.includes("",template)){if(in_recursion){return template;}else{this.send(template);return;}}
template=this.render_pragmas(template);var html=this.render_section(template,context,partials);if(in_recursion){return this.render_tags(html,context,partials,in_recursion);}
this.render_tags(html,context,partials,in_recursion);},send:function(line){if(line!=""){this.buffer.push(line);}},render_pragmas:function(template){if(!this.includes("%",template)){return template;}
var that=this;var regex=new RegExp(this.otag+"%([\\w-]+) ?([\\w]+=[\\w]+)?"+
this.ctag);return template.replace(regex,function(match,pragma,options){if(!that.pragmas_implemented[pragma]){throw({message:"This implementation of mustache doesn't understand the '"+
pragma+"' pragma"});}
that.pragmas[pragma]={};if(options){var opts=options.split("=");that.pragmas[pragma][opts[0]]=opts[1];}
return"";});},render_partial:function(name,context,partials){name=this.trim(name);if(!partials||partials[name]===undefined){throw({message:"unknown_partial '"+name+"'"});}
if(typeof(context[name])!="object"){return this.render(partials[name],context,partials,true);}
return this.render(partials[name],context[name],partials,true);},render_section:function(template,context,partials){if(!this.includes("#",template)&&!this.includes("^",template)){return template;}
var that=this;var regex=new RegExp(this.otag+"(\\^|\\#)\\s*(.+)\\s*"+this.ctag+"\n*([\\s\\S]+?)"+this.otag+"\\/\\s*\\2\\s*"+this.ctag+"\\s*","mg");return template.replace(regex,function(match,type,name,content){var value=that.find(name,context);if(type=="^"){if(!value||(that.is_array(value)&&value.length===0)){return that.render(content,context,partials,true);}else{return"";}}else if(type=="#"){if(that.is_array(value)){return that.map(value,function(row){return that.render(content,that.create_context(row),partials,true);}).join("");}else if(that.is_object(value)){return that.render(content,that.create_context(value),partials,true);}else if(typeof value==="function"){return value.call(context,content,function(text){return that.render(text,context,partials,true);});}else if(value){return that.render(content,context,partials,true);}else{return"";}}});},render_tags:function(template,context,partials,in_recursion){var that=this;var new_regex=function(){return new RegExp(that.otag+"(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?"+
that.ctag+"+","g");};var regex=new_regex();var tag_replace_callback=function(match,operator,name){switch(operator){case"!":return"";case"=":that.set_delimiters(name);regex=new_regex();return"";case">":return that.render_partial(name,context,partials);case"{":return that.find(name,context);default:return that.escape(that.find(name,context));}};var lines=template.split("\n");var i;for(i=0;i<lines.length;i++){lines[i]=lines[i].replace(regex,tag_replace_callback,this);if(!in_recursion){this.send(lines[i]);}}
if(in_recursion){return lines.join("\n");}},set_delimiters:function(delimiters){var dels=delimiters.split(" ");this.otag=this.escape_regex(dels[0]);this.ctag=this.escape_regex(dels[1]);},escape_regex:function(text){if(!arguments.callee.sRE){var specials=['/','.','*','+','?','|','(',')','[',']','{','}','\\'];arguments.callee.sRE=new RegExp('(\\'+specials.join('|\\')+')','g');}
return text.replace(arguments.callee.sRE,'\\$1');},find:function(name,context){name=this.trim(name);function is_kinda_truthy(bool){return bool===false||bool===0||bool;}
var value;if(is_kinda_truthy(context[name])){value=context[name];}else if(is_kinda_truthy(this.context[name])){value=this.context[name];}
if(typeof value==="function"){return value.apply(context);}
if(value!==undefined){return value;}
return"";},includes:function(needle,haystack){return haystack.indexOf(this.otag+needle)!=-1;},escape:function(s){s=String(s===null?"":s);return s.replace(/&(?!\w+;)|["'<>\\]/g,function(s){switch(s){case "&": return "&amp;";case "\\": return "\\\\";case '"': return '&quot;';case "'": return '&#39;';case "<": return "&lt;";case ">": return "&gt;";default: return s;}});},create_context:function(_context){if(this.is_object(_context)){return _context;}else{var iterator=".";if(this.pragmas["IMPLICIT-ITERATOR"]){iterator=this.pragmas["IMPLICIT-ITERATOR"].iterator;}
var ctx={};ctx[iterator]=_context;return ctx;}},is_object:function(a){return a&&typeof a=="object";},is_array:function(a){return Object.prototype.toString.call(a)==='[object Array]';},trim:function(s){return s.replace(/^\s*|\s*$/g,"");},map:function(array,fn){if(typeof array.map=="function"){return array.map(fn);}else{var r=[],l=array.length,i;for(i=0;i<l;i++){r.push(fn(array[i]));}
return r;}}};return({name:"mustache.js",version:"0.3.1-dev",to_html:function(template,view,partials,send_fun){var renderer=new Renderer();if(send_fun){renderer.send=send_fun;}
renderer.render(template,view,partials);if(!send_fun){return renderer.buffer.join("\n");}}});}();window['PhotoMosaic']={};window['PhotoMosaic'].Mustache=Mustache;})(window);

/*
    jQuery photoMosaic v1.4
    requires jQuery 1.5+ & Mustache (included above)
*/

/*
eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('(g($){k E=g(){};$.20(E.2Q,{2y:g(22,1h,i){k 21={F:\'c\',p:\'T\',A:2,e:3,o:2T,8:\'1Z\',1t:P,1C:1z,1r:1z,1d:1Q,2e:P,1O:1Q};4.7=$.20({},21,1h);4.Z=$(22);4.W=(23.2Z(2n 23())+s.2Y(s.1r()*2X));4.14=\'2l\'+4.W;4.6=[];4.e=[];4.1W=(4.7.o-(4.7.A*(4.7.e-1)))%4.7.e;4.29=((4.7.o-4.1W)-(4.7.A*(4.7.e-1)))/4.7.e;4.2F=\' \'+\'<1J W="2W\'+4.W+\'" 2s="E">\'+\'{{#e}}\'+\'<2c 1U="31:2C; 1V:0 {{^13}}{{A}}N 0 0{{/13}}">\'+\'{{#6}}\'+\'<2g 1U="o:{{#o}}{{v}}{{/o}}N; 8:{{#8}}{{v}}{{/8}}N; 1V:0 {{^13}}0 {{A}}N 0{{/13}}">\'+\'{{#H}}<a 2H="{{1a}}" {{#17}}2S="2O"{{/17}} {{#12}}32="{{12}}"{{/12}}>{{/H}}\'+\'<I r="{{r}}" 1U="\'+\'o:{{#o}}{{l}}{{/o}}N; \'+\'8:{{#8}}{{l}}{{/8}}N; \'+\'{{#1i}}{{1y}}:-{{1B}}N;{{/1i}}" \'+\'1H="{{16}}"/>\'+\'{{#H}}</a>{{/H}}\'+\'</2g>\'+\'{{/6}}\'+\'</2c>\'+\'{{/e}}\'+\'</1J>\';d(4.7.F===\'1G\'&&4.7.p===\'\'){K.M("D: L: 1u 1M 2d 1a 1I.");m}d(4.7.F===\'1G\'&&4.7.p===\'T\'){K.M(\'D: L: 1u 1M 2d 1a 1I.\');m}d(4.7.F===\'c\'&&4.7.p===\'\'){K.M("D: L: 1u 24 2f 33.");m}d(4.7.F===\'c\'&&4.7.p===\'T\'){d(2V(T)!==\'36\'){4.7.p=T}u{K.M(\'D: L: 1E 24 2f "T" 2L 2K 27 1T.\');m}}d(4.7.p.q-1<4.26){K.M(\'D: L: "2N" 2U a 0-Y (0 = 2a 2P 2b).\'+\'1u 2b 1N 1T 3a 2a 1I Y (\'+4.26+\')\');m}k h=4;d(4.7.F===\'1b\'){4.7.p=4.2p();$.1p(h.1s()).1R(g(){h.Z.1b(h.1e());h.1n()})}d(4.7.F===\'1G\'){$.3q(4.7.p,g(O){d($(O).19(\'25\').q>0){h.7.p=$(O).19(\'25\');h.7.p=h.2D();$.1p(h.1s()).1R(g(){h.Z.1b(h.1e());h.1n()})}u{K.M(\'D: L: 1E 1M 37 3n\\\'t 27 1T 3t 1N 3m.\');m}})}d(4.7.F===\'c\'){$.1p(4.1s()).1R(g(){h.Z.1b(h.1e());h.1n()})}},1e:g(){k h=4,$14=$(\'#\'+4.14),i,j;$.Q(4.7.p,g(i){k f={},$I=$14.19(\'I[r="\'+4.r+\'"]\'),1g;f.r=4.r;f.o={};f.8={};f.A=h.7.A;f.16=4.16;f.o.X=$I.o();f.8.X=$I.8();f.o.l=h.29;f.8.l=s.x((f.8.X*f.o.l)/f.o.X);d(h.7.1d){d(h.7.2e){1g=h.7.1d+\'[\'+h.W+\']\'}u{1g=h.7.1d}f.12=1g}d(h.7.1t&&4.1v){f.H=P;f.1a=4.1v;f.17=h.7.1C;d(f.17&&f.12){38 f.12}}u d(h.7.1t){f.H=P;f.1a=4.r;f.17=h.7.1C}u{f.H=1z}h.6.z(f)});4.6=4.2w(4.6);4.6.39(g(a,b){d(h.7.1r){m(0.5-s.1r())}u{m(a.8.X-b.8.X)}});4.6.3e();k 1o=[],1k=P;3j(4.6.q>0){d(1k){1o.z(4.6.3i())}u{1o.z(4.6.3h())}1k=!1k}4.6=1o;k G=0;w(i=0;i<4.6.q;i++){d(G===4.7.e){G=0}d(!4.e[G]){4.e[G]=[]}4.e[G].z(4.6[i]);G++}k c={e:[]},1j=[];w(i=0;i<4.e.q;i++){k J=0;w(j=0;j<4.e[i].q;j++){J=J+4.e[i][j].8.l}J=J+(4.e[i].q-1)*4.7.A;1j.z(J);c.e[i]={};c.e[i].6=4.e[i];c.e[i].8=J;c.e[i].A=4.7.A}k 1X=4.2o(1j),1Y=4.2m(1j),28=s.x((1X+1Y)/2);d(4.7.8===\'1Z\'){c=4.1x(c,28)}u{c=4.1x(c,4.7.8)}m D.3o.3g(4.2F,c)},1x:g(c,1w){c=4.2j(c);w(i=0;i<c.e.q;i++){c=4.2k(c,i);d(c.e[i].8>1w){c.e[i]=4.2G(c.e[i],1w)}u{c.e[i]=4.2I(c.e[i],1w)}}m c},2G:g(9,8){k C=9.6.q,S=9.8-8,10=S%C,y=s.x(S/C),1m=y+10,2A=s.x(y/2),2h=s.x((y+10)/2),2E=4.2i(9.6),i;w(i=0;i<C;i++){d(i===2E.Y){9.6[i].8.v=9.6[i].8.l-1m}u{9.6[i].8.v=9.6[i].8.l-y}9.6[i].o.v=9.6[i].o.l;9.6[i].1i={1y:\'3f\',1B:s.x((9.6[i].8.l-9.6[i].8.v)/2)}}m 9},2I:g(9,8){k C=9.6.q,S=8-9.8,10=S%C,y=s.x(S/C),1m=y+10,2A=s.x(y/2),2h=s.x((y+10)/2),2B=4.2z(9.6),i;w(i=0;i<C;i++){d(i===2B.Y){9.6[i].8.v=9.6[i].8.l+1m}u{9.6[i].8.v=9.6[i].8.l+y}9.6[i].o.v=9.6[i].o.l;9.6[i].o.l=s.x((9.6[i].o.l*9.6[i].8.v)/9.6[i].8.l);9.6[i].8.l=9.6[i].8.v;9.6[i].1i={1y:\'2C\',1B:s.x((9.6[i].o.l-9.6[i].o.v)/2)}}m 9},2o:g(B){k 11=0,i;w(i=0;i<B.q;i++){d(11===0){11=B[i]}u d(B[i]<11){11=B[i]}}m 11},2m:g(B){k 1f=0,i;w(i=0;i<B.q;i++){d(B[i]>1f){1f=B[i]}}m 1f},2z:g(6){k R=0,1D=0,i;w(i=0;i<6.q;i++){d(R===0){R=6[i].8.l}u d(6[i].8.l<R){R=6[i].8.l;1D=i}}m{8:R,Y:1D}},2i:g(6){k 1c=0,1A=0,i;w(i=0;i<6.q;i++){d(6[i].8.l>1c){1c=6[i].8.l;1A=i}}m{8:1c,Y:1A}},2j:g(c){c.e[c.e.q-1].13=P;m c},2k:g(c,i){c.e[i].6[c.e[i].6.q-1].13=P;m c},2w:g(6){k V=[];$.Q(6,g(i){d(3b(4.8.l)){V.z(i)}});$.Q(V,g(i){K.M(\'D: L: 1E 3c f 3k 3s 2u 3u 1N 3p.\\n\'+6[V[i]].r);k 2r=6.3r(V[i]+1);6.q=V[i];6.z.1P(6,2r)});m 6},1s:g(){k 1F=$.2t(),1K=[],$6=$(\'<1J>\').18({\'W\':4.14,\'2s\':\'2l\'});$.Q(4.7.p,g(i){k 1q=$.2t(),$2v=$(\'<I>\').2M(1q.1L).2u(1q.1L).18({r:4.r});$6.2x($2v);1K.z(1q)});$.1p.1P(1Q,1K).34(1F.1L,$(\'35\').2x($6));m 1F.30()},2p:g(){k p=[],$6=4.Z.19(\'I\'),i;w(i=0;i<$6.q;i++){k f={};f.r=($6.15(i).2J(\'a\').q>0&&4.7.1t)?$6.15(i).2J(\'a\').18(\'2H\'):$6.15(i).18(\'r\');f.16=$6.15(i).18(\'1H\');p.z(f)}m p},2D:g(){k p=[];4.7.p.19(\'U\').Q(g(i){k U={},O=$(4);U.16=O.1l(\'1H\').1S();U.r=O.1l(\'r\').1S();U.1v=O.1l(\'1v\').1S();p.z(U)});m p},1n:g(){k $2q=4.Z.1l().15(0);d($.2R(4.7.1O)){4.7.1O.1P(4,[$2q])}}});$.3l.E=g(1h){4.Q(g(i){d(!4.E){4.E=2n E();4.E.2y(4,1h,i)}});m 4}})(3d);',62,217,'||||this||images|opts|height|col|||json|if|columns|image|function|self|||var|adjusted|return||width|gallery|length|src|Math||else|constraint|for|floor|divy|push|padding|list|count|PhotoMosaic|photoMosaic|input|current_col|link|img|col_height|console|ERROR|log|px|data|true|each|smallest_height|diff|PMalbum|photo|to_delete|id|original|index|obj|mod|smallest|modal|last|preload|eq|caption|external|attr|find|path|html|largest_height|modal_name|makeMosaic|biggest|modal_text|options|adjustment|col_heights|bool|children|divy_mod|modalCallback|order|when|dfd|random|preloadify|links|No|url|target_height|adjustHeights|type|false|index_of_largest|value|external_links|index_of_smallest|The|deferred|xml|title|specified|div|promises|resolve|XML|was|modal_ready_callback|apply|null|then|text|found|style|margin|col_mod|shortest_col|tallest_col|auto|extend|defaults|el|Date|JSON|photos|current_album|be|average_col_height|col_width|the|album|ol|file|modal_group|object|li|offset_mod|findLargestImage|markLastColumn|markLastImageInColumn|PM_preloadify|getBiggest|new|getSmallest|constructGalleryFromHTML|node|rest|class|Deferred|load|item|errorCheck|append|init|findSmallestImage|offset|smallest_image|left|constructGalleryFromXML|largest_image|template|scaleColumnDown|href|scaleColumnUp|parent|not|can|error|start_album|_blank|first|prototype|isFunction|target|300|uses|typeof|photoMosaic_|10000|round|parse|promise|float|rel|defined|done|body|undefined|either|delete|sort|at|isNaN|following|jQuery|reverse|top|to_html|pop|shift|while|failed|fn|malformed|couldn|Mustache|skipped|get|slice|to|or|and'.split('|'),0,{}))
*/



(function($) {

    if(typeof console === "undefined") {
        console = {
            log: function(msg) {
                console.errors.push(msg);
            },
            errors: []
        };
    }

    var photoMosaic = function() { };

    $.extend(photoMosaic.prototype, {

        init: function(el, options, i){
            var defaults = {
                input : 'json', // json, html, xml
                gallery : 'PMalbum', // json object, xml file path
                padding : 2,
                columns : 3,
                width : 'auto', // auto (str) or (int) 
                height : 'auto', // auto (str) or (int)
                links : true,
                external_links: false,
                random : false,
                force_order : false,
                auto_columns : false,
                ideal_column_width : 100,
                show_loading : false,
                modal_name : null,
                modal_group : true,
                modal_ready_callback : null
            };
			
            this.opts = $.extend({}, defaults, options);
            this.obj = $(el);
            this.id = (Date.parse(new Date()) + Math.round(Math.random() * 10000));

            this.preload = 'PM_preloadify' + this.id;

            this.images = [];
            this.columns = [];

            this.template = ' ' +
				'<div id="photoMosaic_' + this.id + '" class="photoMosaic">' +
					'{{#columns}}' +
						'<ol style="float:left; margin:0 {{^last}}{{padding}}px 0 0{{/last}}">' +
							'{{#images}}' +
								'<li style="width:{{#width}}{{constraint}}{{/width}}px; height:{{#height}}{{constraint}}{{/height}}px; margin:0 {{^last}}0 {{padding}}px 0{{/last}}">' +
									'{{#link}}<a href="{{path}}" {{#external}}target="_blank"{{/external}} {{#modal}}rel="{{modal}}"{{/modal}} {{#caption}}title="{{caption}}"{{/caption}}>{{/link}}' +
										'<img src="{{src}}" style="' +
											'width:{{#width}}{{adjusted}}{{/width}}px; ' +
											'height:{{#height}}{{adjusted}}{{/height}}px; ' +
											'{{#adjustment}}{{type}}:-{{value}}px;{{/adjustment}}" ' +
										'title="{{caption}}"/>' +
									'{{#link}}</a>{{/link}}' +
								'</li>' +
							'{{/images}}' +
						'</ol>' +
					'{{/columns}}' +
				'</div>';

            this.loading_template = ' ' +
                '<div id="photoMosaic_' + this.id + '" class="photoMosaic">' +
                    '<div class="photoMosaicLoading">loading gallery...</div>' +
                '</div>';

            if(this.opts.width === 'auto') {
                this.opts.width = this.obj.width();
            }

            // Error Checks
            if ( this.opts.input === 'xml' && this.opts.gallery === '' ) {
                console.log("PhotoMosaic: ERROR: No XML file path specified.");
                return;
            }
            if ( this.opts.input ==='xml' && this.opts.gallery === 'PMalbum' ) {
                console.log('PhotoMosaic: ERROR: No XML file path specified.');
                return;
            }
            if ( this.opts.input === 'json' && this.opts.gallery === '' ) {
                console.log("PhotoMosaic: ERROR: No JSON object defined.");
                return;
            }
            if ( this.opts.input ==='json' && this.opts.gallery === 'PMalbum' ) {
                if ( typeof(PMalbum) !== 'undefined' ) {
                    this.opts.gallery = PMalbum;
                } else {
                    console.log('PhotoMosaic: ERROR: The JSON object "PMalbum" can not be found.');
                    return;
                }
            }
            if ( this.opts.gallery.length - 1 < this.current_album ) {
                console.log('PhotoMosaic: ERROR: "start_album" uses a 0-index (0 = the first album).'
                     + 'No album was found at the specified index ('+ this.current_album +')');
                return;
            }

            var self = this;

            // loading message
            if ( this.opts.show_loading ) {
                this.obj.html( PhotoMosaic.Mustache.to_html(this.loading_template, {}) );
            }
            
            // html -- construct json -- preload -- mosaic -- write
            if ( this.opts.input === 'html' ) {
                this.opts.gallery = this.constructGalleryFromHTML();
                this.autoCols();
                $.when(self.preloadify()).then(function() {
                    self.obj.html( self.makeMosaic() );
                    self.modalCallback();
                });
            }

            // xml -- construct json -- preload -- mosaic -- write
            if ( this.opts.input === 'xml' ){
                $.get(this.opts.gallery, function(data){
                    if ( $(data).find('photos').length > 0 ) {
                        self.opts.gallery = $(data).find('photos');
                        self.opts.gallery = self.constructGalleryFromXML();
                        self.autoCols();
                        $.when(self.preloadify()).then(function() {
                            self.obj.html( self.makeMosaic() );
                            self.modalCallback();
                        });
                    } else {
                        console.log('PhotoMosaic: ERROR: The XML either couldn\'t be found or was malformed.');
                        return;
                    }
                });
                
            }

            // json -- preload -- mosaic -- write
            if ( this.opts.input === 'json' ) {
                this.autoCols();
                $.when(this.preloadify()).then(function() {
                    self.obj.html( self.makeMosaic() );
                    self.modalCallback();
                });
            }
        },

        makeMosaic: function() {
			
            var self = this,
                $preload = $('#' + this.preload),
                i,
                j;

            // get image sizes, set modalhook, & get link paths
            $.each(this.opts.gallery, function(i) {
                var image = {},
                    $img = $preload.find('img[src="'+ this.src +'"]'),
                    modal_text;
                    
                // image sizes
                image.src = this.src;
                image.width = {};
                image.height = {};
                image.padding = self.opts.padding;
                image.caption = this.caption;

                image.width.original = $img.width();
                image.height.original = $img.height();
                image.width.adjusted = self.col_width;
                image.height.adjusted = Math.floor((image.height.original * image.width.adjusted) / image.width.original);

                // modal hooks
                if (self.opts.modal_name) {
                    if (self.opts.modal_group) {
                        modal_text = self.opts.modal_name/* + '[' + self.id + ']'*/;    
                    } else {
                        modal_text = self.opts.modal_name;
                    }
                    image.modal = modal_text;
                }
                
                // link paths
                if (self.opts.links && this.url) {
                    image.link = true;
                    image.path = this.url;
                    image.external = self.opts.external_links;
                    delete image.modal;
                } else if (self.opts.links) {
                    image.link = true;
                    //image.path = this.src;
					image.path = $('img[src="'+ this.src +'"]').attr('zoom');
                    image.external = self.opts.external_links;
                } else {
                    image.link = false;
                }

                self.images.push(image);
            });

            // ERROR CHECK: remove any images that failed to load
            this.images = this.errorCheck(this.images);

            // alt sort images by height (tall, short, tall, short)
            if (!this.opts.force_order) {
                this.images.sort(function(a,b) {
                    if (self.opts.random) {
                        return (0.5 - Math.random());
                    } else {
                        return (a.height.original - b.height.original);
                    }
                });
                this.images.reverse();
            }
            
            var order = [],
                bool = true;
            
            if (!this.opts.force_order) {
                while (this.images.length > 0) {
                    if (bool) {
                        order.push(this.images.shift());
                    } else {
                        order.push(this.images.pop());
                    }
                    bool = !bool;
                }
                this.images = order;
            }

            // deal into columns
            var current_col = 0;

            for (i = 0; i < this.images.length; i++) {
                if (current_col === this.opts.columns) {
                    current_col = 0;
                }

                if (!this.columns[current_col]) {
                    this.columns[current_col] = [];
                }
                this.columns[current_col].push(this.images[i]);

                current_col++;
            }
            
            // unfortunate special-case "force order"
            if (this.opts.force_order) {
                var forced_cols = [];
                for (i = 0; i < this.columns.length; i++) {
                    for (j = 0; j < this.columns[i].length; j++) {
                        if (!forced_cols[i]) {
                            forced_cols[i] = [];
                        }
                        forced_cols[i].push(this.images[0]);
                        this.images.shift();
                    }
                }
                this.columns = forced_cols;
            }
            
            // construct template object &
            // get column heights (img height adjusted for col width)
            var json = {columns:[]},
                col_heights = [];
            
            for (i = 0; i < this.columns.length; i++) {
                var col_height = 0;

                for (j = 0; j < this.columns[i].length; j++) {
                    col_height = col_height + this.columns[i][j].height.adjusted;
                }
                col_height = col_height + (this.columns[i].length - 1) * this.opts.padding;
                col_heights.push(col_height);
                
                json.columns[i] = {};
                json.columns[i].images = this.columns[i];
                json.columns[i].height = col_height;
                json.columns[i].padding = this.opts.padding;
            }
            
            // normalize column heights
            var shortest_col = this.getSmallest(col_heights),
                tallest_col = this.getBiggest(col_heights),
                average_col_height = Math.floor((shortest_col + tallest_col) / 2);

            if (this.opts.height === 'auto') {
                json = this.adjustHeights(json, average_col_height);
            } else {
                json = this.adjustHeights(json, this.opts.height);
            }

            return PhotoMosaic.Mustache.to_html(this.template, json);
        },
        
        adjustHeights: function(json, target_height) {
            json = this.markLastColumn(json);
            
            for (i = 0; i < json.columns.length; i++) {
                json = this.markLastImageInColumn(json, i);
                    
                if(json.columns[i].height > target_height) {
                    json.columns[i] = this.scaleColumnDown(json.columns[i], target_height);
                } else {
                    json.columns[i] = this.scaleColumnUp(json.columns[i], target_height);
                }
            }
            
            return json;
        },
        
        autoCols: function(){
            var max_width = this.opts.width,
                ideal_width = this.opts.ideal_column_width,
                num_images = eval(this.opts.gallery).length,
                cols = 0,
                ratio = {w:4, h:3},
                i = 0;

            if(this.opts.auto_columns) {
                while(cols === 0) {
                    if(num_images <= ((i + ratio.w) * (i + ratio.h))) {
                        cols = i + ratio.w;
                    } else {
                        ++i;
                    }
                }
                this.opts.width = ((cols * ideal_width) >= max_width) ? max_width : cols * ideal_width;
                this.opts.columns = cols;
            }
            
            this.col_mod = (this.opts.width - (this.opts.padding * (this.opts.columns - 1))) % this.opts.columns;
            this.col_width = ((this.opts.width - this.col_mod) - (this.opts.padding * (this.opts.columns - 1))) / this.opts.columns;            
        },
        
        scaleColumnDown: function(col, height) {
            var count = col.images.length,
                diff = col.height - height,
                mod = diff % count,
                divy = Math.floor(diff / count),
                divy_mod = divy + mod,
                offset = Math.floor(divy / 2),
                offset_mod = Math.floor((divy + mod) / 2),
                largest_image = this.findLargestImage(col.images),
                i;

            for (i = 0; i < count; i++) {
                if(i === largest_image.index) {
                    col.images[i].height.constraint = col.images[i].height.adjusted - divy_mod;
                } else {
                    col.images[i].height.constraint = col.images[i].height.adjusted - divy;
                }
                col.images[i].width.constraint = col.images[i].width.adjusted;
                col.images[i].adjustment = {
                    type : 'top',
                    value : Math.floor((col.images[i].height.adjusted - col.images[i].height.constraint) / 2)
                };
            }
            
            return col;
        },
        
        scaleColumnUp: function(col, height) {
            var count = col.images.length,
                diff = height - col.height,
                mod = diff % count,
                divy = Math.floor(diff / count),
                divy_mod = divy + mod,
                offset = Math.floor(divy / 2),
                offset_mod = Math.floor((divy + mod) / 2),
                smallest_image = this.findSmallestImage(col.images),
                i;
 
            for (i = 0; i < count; i++) {
                if(i === smallest_image.index) {
                    col.images[i].height.constraint = col.images[i].height.adjusted + divy_mod;
                } else {
                    col.images[i].height.constraint = col.images[i].height.adjusted + divy;
                }
                col.images[i].width.constraint = col.images[i].width.adjusted;
                col.images[i].width.adjusted = Math.floor((col.images[i].width.adjusted * col.images[i].height.constraint) / col.images[i].height.adjusted);
                col.images[i].height.adjusted = col.images[i].height.constraint;

                col.images[i].adjustment = {
                    type : 'left',
                    value : Math.floor((col.images[i].width.adjusted - col.images[i].width.constraint) / 2)
                };
            }

            return col;
        },
        
        getSmallest: function(list) {
            var smallest = 0,
                i;
                
            for (i = 0; i < list.length; i++) {
                if (smallest === 0) {
                    smallest = list[i];
                } else if (list[i] < smallest) {
                    smallest = list[i];    
                }
            }

            return smallest;
        },
        
        getBiggest: function(list) {
            var biggest = 0,
                i;

            for (i = 0; i < list.length; i++) {
                if (list[i] > biggest) {
                    biggest = list[i];
                }
            }

            return biggest;
        },

        findSmallestImage: function(images) {
            var smallest_height = 0,
                index_of_smallest = 0,
                i;
                
            for (i = 0; i < images.length; i++) {
                if(smallest_height === 0) {
                    smallest_height = images[i].height.adjusted;
                } else if(images[i].height.adjusted < smallest_height) {
                    smallest_height = images[i].height.adjusted;
                    index_of_smallest = i;
                }
            }
            
            return { 
                height : smallest_height,
                index : index_of_smallest
            };
        },

        findLargestImage: function(images) {
            var largest_height = 0,
                index_of_largest = 0,
                i;
                
            for (i = 0; i < images.length; i++) {
                if(images[i].height.adjusted > largest_height) {
                    largest_height = images[i].height.adjusted;
                    index_of_largest = i;
                }
            }
            
            return { 
                height : largest_height,
                index : index_of_largest
            };
        },
        
        markLastColumn: function(json) {
            json.columns[json.columns.length - 1].last = true;
            return json;
        },
        
        markLastImageInColumn: function(json, i) {
            json.columns[i].images[json.columns[i].images.length - 1].last = true;
            return json;
        },
        
        errorCheck: function(images){
            var to_delete = [],
                i;

            $.each(images, function(i) {
                if(isNaN(this.height.adjusted)){
                    to_delete.push(i);
                }
            });

            for (i = to_delete.length - 1; i >= 0; --i) {
                console.log('PhotoMosaic: ERROR: The following image failed to load and was skipped.\n' + images[to_delete[i]].src);
                var rest = images.slice( to_delete[i] + 1 );
                images.length = to_delete[i];
                images.push.apply(images, rest);
            };

            return images;
        },
        
        preloadify: function(){
            var deferred = $.Deferred(),
                promises = [],
                $images = $('<div>').attr({
                    'id': this.preload,
                    'class' : 'PM_preloadify'
                });

            $.each(this.opts.gallery, function(i) {
                var dfd = $.Deferred(),
                    $item = $('<img>').error(dfd.resolve).load(dfd.resolve).attr({src : this.src});
                $images.append($item);
                promises.push(dfd);
            });
            
            $.when.apply(null, promises).done(deferred.resolve, $('body').append($images));
            
            return deferred.promise(); 
        },
        
        constructGalleryFromHTML: function(){
            var gallery = [],
                $images = this.obj.find('img'),
                i;

            for (i = 0; i < $images.length; i++) {
				
                var image = {};

                image.src = ($images.eq(i).parent('a').length > 0 && this.opts.links) ? $images.eq(i).parent('a').attr('href') : $images.eq(i).attr('src');
                image.caption = $images.eq(i).attr('title');

                gallery.push(image);
            } 

            return gallery;
        },

        constructGalleryFromXML: function(){
            var gallery = [];
            
            this.opts.gallery.find('photo').each(function(i){
                var photo = {},
                    data = $(this);
                
                photo.caption = data.children('title').text();
                photo.src = data.children('src').text();
                photo.url = data.children('url').text();
                
                gallery.push(photo);
            });
            
            return gallery;
        },
        
        modalCallback: function() {
            var $node = this.obj.children().eq(0)[0];
            if($.isFunction(this.opts.modal_ready_callback)){
                this.opts.modal_ready_callback.apply(this, [$node]);
            }
        }

    });

    $.fn.photoMosaic = function(options) {
        this.each(function(i) {
            if (!this.photoMosaic) {
                this.photoMosaic = new photoMosaic();
                this.photoMosaic.init(this, options, i);
            }
        });
        return this;
    };
})(jQuery);
