;(function($, undefined) {
'use strict';

O_O.widget.SocialShare.Controller = function($elem, props, msgSys, env) {
	O_O.lib.Controller.apply(this, arguments);

	this.view = O_O.mm.oNew(O_O.widget.SocialShare.View, $elem, props,
		scopeC(this._controllerCb, this), env);
};

O_O.widget.SocialShare.Controller.prototype = {
	render: function(config) {
		return this.view.render(config.text, config.style);
	},

	_controllerCb: function(type, payload) {
		switch (type) {
		case 'editWidget':
			return this.msgSys.pub('Shell', {
				msg: 'editWidget',
				data: {
					view: payload.view,
					cfgId: this.getCfg()
				}
			});
		}
	},

	/* unused functions (here to override abstract controller) */

	_eventH: null
};

O_O.obj.inherit(O_O.lib.Controller, O_O.widget.SocialShare.Controller);

}(ps$));
;(function($, undefined) {
'use strict';

O_O.widget.SocialShare.View = function($elem, props, controllerCb, env) {
	O_O.lib.View.apply(this, arguments);
};

O_O.widget.SocialShare.View.prototype = {

	CSS_CLASS_EXPANDED: 'expanded',
	GPLUS_CLASS: "google_plusone_share",

	_init: function() {
		this._bindEvents();
		this._shareLibSetup();
	},

	_shareLibSetup: function() {
		var f_dev = (this.env('custom').isDevDomain);

		SocialShareUtil.setFB_ID(f_dev);
		SocialShareUtil.loadTwitter();
	},

	render: function(text, style) {
		return this.tpl('main', {text: text}, scopeC(function(html){
			this._clearElem(); // clear all but config
			this.$elem.append(html);
		}, this));
	},

	_toggleMore: function(e) {
		e.preventDefault();
		var $this = $(e.currentTarget),
			text = ($this.html()==='Less')?'More':'Less';
		this.$elem.find('.more').toggle();
		$this.html(text).attr('title', text);
	},
	_toggleShare: function(e) {
		e.preventDefault();
		if(e.handleObj.namespace === 'close-social' &&
			$(e.originalEvent.target).closest(this.$elem).length === 1) {
			return;
		}
		if ($(e.target).parent().hasClass('anchor-wrapper')) return;
		var $this = this.$elem.find('.share'),
			text = ($this.hasClass('showing'))?'Share This':'Done Sharing';

		if(!$this.hasClass('showing')) {
			$('body').onC('click.close-social', this._toggleShare, this);
		} else {
			$('body').off('click.close-social');
		}
		this.$elem.find('.share-options').toggleClass('show-share');
		$this.toggleClass('showing').attr('title', text);
	},
	_mkHref: function() {
		var url = window.location.href;
		return url.replace('?edit', '');
	},
	_getSiteName: function() {
		var name = this.env('custom').site_name;
		if (typeof name !== 'undefined') return name;
		return '';
	},
	_serviceNames: { // class name in tpl: arg name in socialShare library
		'google_plusone_share': 'gplus',
		'facebook': 'facebook',
		'twitter': 'twitter',
		'pinterest': 'pinterest'
	},
	_mkShareAttr: function(service) {
		var attr = {url: this._mkHref()};
		switch(service) {
			case 'twitter':
				attr.twitter = {text: this._getSiteName()};
				break;
			case 'facebook':
				var server = this.env('custom').server.web;
				attr.redirectUrl = server + '/ext/facebook_redirect.php';
				break;
			default:
		}
		return attr;
	},
	_mkEventLabel: function() {
		return (((isset(C2_CFG)) ? C2_CFG.customEnv.id : '') + window.location.pathname);
	},
	_socialShare: function(shareType) {
		var service = this._serviceNames[shareType];
		if (!service) throw new Error(shareType + ' is not a valid service name');
		socialShare(service, this._mkShareAttr(service), this._shareCb.bind(this, service));
	},
	_shareCb: function(service) {
		// only called if service is 'twitter'.
		var action = service + '-share-success',
		    label = this._mkEventLabel();

		this._gaEvent('SocialShare', action, label);
	},
	_bindEvents: function() {
		this.$elem.onC('click', 'a', function(e) {
			e.preventDefault();

			var $this = $(e.currentTarget);
			if(!$this.hasClass('less-more') && !$this.hasClass('share')) {
				var shareType = $(e.currentTarget).attr('class'),
				    label = this._mkEventLabel();

				this._socialShare(shareType);

				this._gaEvent('SocialShare', shareType, label);
			}
		}, this)
			.onC('click', '.less-more', this._toggleMore, this)
			.onC('click', '.share', this._toggleShare, this)
			.onC('click', 'a', function(e) {
				e.preventDefault();
			}, this);
	}
};

O_O.obj.inherit(O_O.lib.View, O_O.widget.SocialShare.View);

}(ps$));
(function($) {

O_O.widget.StaticImage.Controller = function($elem, props, msgSys, env) {
	O_O.lib.Controller.apply(this, arguments);

	this.view = O_O.mm.oNew(O_O.widget.StaticImage.View, $elem, props,
		scopeC(this._controllerCb, this), env);
};

O_O.widget.StaticImage.Controller.prototype = {

	render: function(cfg) {
		if (cfg) this.cfg = this.cfg;

		if (!cfg.image || !cfg.image.length)
			return this.view.render(null);

		var img_info = cfg.image[0];

		var static_tuple = {
			id: img_info.image_id,
			config_tuple: this.getCfg(),
			config: JSON.stringify(cfg)
		};

		var m = O_O.model.get('StaticImage', static_tuple);
		return m.load().then(scopeC(function (data) {
			var szOpt = {
				width: cfg.image_width,
				height: -1,
				mode: 'fit'
			};

			// FIXME: chain the deferred return with this?
			this.view.render(m.getLinkForSize(szOpt));
		}, this));
	},

	/* unused functions (here to override abstract controller) */

	_controllerCb: function(type, payload) {
		switch (type) {
		case 'editWidget':
			return this.msgSys.pub('Shell', {
				msg: 'editWidget',
				data: {
					view: payload.view,
					cfgId: this.getCfg()
				}
			});
		}
	},

	_eventH: function(type, data) {}
};

O_O.obj.inherit(O_O.lib.Controller, O_O.widget.StaticImage.Controller);

})(ps$);
(function($) {

O_O.widget.StaticImage.View = function($elem, props, controllerCb, env) {
	O_O.lib.View.apply(this, arguments);
};

O_O.widget.StaticImage.View.prototype = {
	$img:		null,

	render: function(url)
	{
		var dfr = $.Deferred().resolve();
		
		this._clearElem();
		
		if (!this.$img) this.$img = $('<img/>');

		if (url) {
			// FIXME: replace with scheduler
			this.$img
				.appendTo(this.$elem)
				.attr('src', url);
		}

		return dfr.promise();
	},
};

O_O.obj.inherit(O_O.lib.View, O_O.widget.StaticImage.View);

})(ps$);
;(function($, undefined) {
'use strict';

O_O.widget.VimeoViewer.Controller = function($elem, props, msgSys, env) {
	O_O.lib.Controller.apply(this, arguments);

	this.view = O_O.mm.oNew(O_O.widget.VimeoViewer.View, $elem, props,
		scopeC(this._controllerCb, this), env);
};

O_O.widget.VimeoViewer.Controller.prototype = {
	render: function (cfg) {
		this.config = cfg || this.config;
		var contentPath = null;
		var url = null;
		var dfr;
		let isAlbum = false;

		switch (cfg.list_type) {
			case 'vimeo_user':
				if (!empty(cfg.vimeo_user))
					contentPath = cfg.vimeo_user + '/videos';
				break;
			case 'vimeo_album':
				if (!empty(cfg.vimeo_album))
					contentPath = 'album/' + cfg.vimeo_album + '/videos';
					isAlbum = true;
				break;
		}

		if (contentPath) {
			/*
				With the configurator's realtime updates we get a lot of partial
				vimeo user names.  Gotta track the latest one to make sure it
				ultimately gets rendering priority.
				TODO: Debounce the debouncer?
			*/

			url = 'https://vimeo.com/api/v2/' + contentPath + '.json';

			if (this.currentUrl === url) {
				dfr = $.Deferred().resolve().promise();
			} else {
				this.currentUrl = url;
				dfr = this.view.render('loading', '');

				/**
				 * Fetch user or album metadata, Iterate through avaiable content pages, Render video collection:
				 */
				this._getContentMetadata(cfg, isAlbum)
				.then(this._getUserVideoCollection.bind(this), this._failedVideoRequest.bind(this));
			}

		} else dfr = this.view.render('loading', '');

		return dfr;
	},

	_getContentMetadata: function (cfg, isAlbum) {
		const contentPath = isAlbum ? 'album/' + cfg.vimeo_album : cfg.vimeo_user;
		const infoUrl = 'https://vimeo.com/api/v2/' + contentPath + '/info.json ';
		const userInfo = $.ajax({
			url: infoUrl,
			error: function (error) {
				return (error);
			},
			success: function (data) {
				return data;
			}
		});

		return userInfo;
	},

	_getUserVideoCollection: function (userInfo) {
		const userVideoCount = userInfo['total_videos_uploaded'] || userInfo['total_videos'] // Total number of videos uploaded by user. User and Album objects use different keys.
		const maxVideoPages = 3; // <-- Vimeo's Simple API returns a maximum of 3 pages of user content. Set this to an int < 3.
		const userVideoPages = []; // Array of user videos. Ordinal int keys.
		const totalVideoPages = Math.ceil(userVideoCount / 20); // Vimeo returns 20 items per page.
		let currentVideoPageLimit = 0; // Number of pages that will be fetched from the API.
		let currentVideoPage = 1; // First page of user video arrays.
		let userVideoUrl = ''; // Current API endpoint for user video page.

		/**
		 * Set currentVideoPageLimit to max number of pages that can be fetched from the API
		 */
		currentVideoPageLimit = Math.min(totalVideoPages, maxVideoPages);

		for (var i = 0; i < currentVideoPageLimit; ++i) {
			userVideoUrl = this.currentUrl + '?page=' + currentVideoPage;
			userVideoPages[i] = $.ajax({
				url: userVideoUrl,
				error: function (error) {
					return (error);
				},
				success: function (data) {
					return data;
				}
			});

			currentVideoPage++;
		}

		$.when.apply($, userVideoPages)
			.then(function () {
				this._renderUserVideoCollection(arguments);
			}.bind(this), this._failedVideoRequest.bind(this));

		return;
	},

	_renderUserVideoCollection: function (videoCollection) {
		// Recursively traverse video collection.
		const currentEmbeddableVideos = this._traverseVideoCollection(videoCollection);

		// Flatten array of videos.
		const unfilteredVideos = [].concat.apply([], currentEmbeddableVideos);

		// Filter embeddable videos. 
		// https://vimeo.zendesk.com/hc/en-us/articles/224817847-Privacy-settings-overview
		// The embed_privacy field can be set to different options depending on the Vimeo account. 
		// "nowhere" is the only option that prevents the video from displaying
		const embeddableVideos = unfilteredVideos.filter(function (videoItem) {
			return videoItem.embed_privacy !== 'nowhere';
		});

		this.view.render('videos', embeddableVideos);
	},

	/**
	 * Recursively traverse video collection.
	 */
	_traverseVideoCollection: function (videoCollection) {
		/* Response Array:
		 * videoCollection[0]: user videos payload
		 * videoCollection[1]: 'success' / 'fail'
		 * videoCollection[2]: xhr response object
		 */

		const isSimpleResponse = (typeof (videoCollection['1']) === 'string');
		const currentEmbeddableVideos = [];

		if (isSimpleResponse && videoCollection['1'] === 'success') {
			const videoItem = videoCollection[0];

			for (let videoIndex in videoItem) {
				if (videoItem.hasOwnProperty(videoIndex)) {
					currentEmbeddableVideos.push(videoItem[videoIndex]);
				}
			}

			return currentEmbeddableVideos;
		} else {
			for (let videoItem in videoCollection) {
				if (videoCollection.hasOwnProperty(videoItem)) {
					currentEmbeddableVideos.push(this._traverseVideoCollection(videoCollection[videoItem]));
				}
			}

			return currentEmbeddableVideos;
		}
	},

	_failedVideoRequest: function (response) {
		this.view.render('error', 'Failed to load videos:' + ' ' + response.responseText);
	},


	_controllerCb: function(type, payload) {
		var dfr;
		
		switch (type) {
		case 'editWidget':
			dfr = this.msgSys.pub('Shell', {
				msg: 'editWidget',
				data: {
					view: payload.view,
					cfgId: this.getCfg()
				}
			});
			break;
		case 'playVideo':
			dfr = this.msgSys.pub('Shell', {
					msg: 'event',
					data: {
						type: 'playVideo',
						data: payload
					}
				});
			break;
		}
		
		return dfr;
	},

	/* unused functions (here to override abstract controller) */

	_eventH: function(type, data) {
		switch (type) {
		case 'windowResize':
			this.view.checkLengths();
			break;
		}
	}
};

O_O.obj.inherit(O_O.lib.Controller, O_O.widget.VimeoViewer.Controller);

}(ps$));
;(function($, undefined) {
'use strict';

O_O.widget.VimeoViewer.View = function($elem, props, controllerCb, env) {
	O_O.lib.View.apply(this, arguments);

	this.instanceNum = O_O.widget.VimeoViewer.View.instanceCount++;
};

O_O.widget.VimeoViewer.View.prototype = {
	instanceNum: null,
	page: 0,

	_init: function () {
		this.setDataEvents();
		this._socialEvents();
	},

	toggleEdit: function(f_toggle) {
		O_O.lib.View.prototype.toggleEdit.apply(this, arguments);

		if (this.$editButton) {
			this.$editButton.css('float', 'none');
			this.$elem.append(this.$editButton);
		}
	},

	render: function(type, data) {
		if (!this.$content) {
			this.$content = $('<div></div>')
				.appendTo(this.$elem);
		}
		if(typeof data !== 'undefined') {
			this.videos = data;
		}

		this.$content.html(''); // clear old content beforehand

		var dfr;
		switch (type) {
		case 'videos':

			var cbName = 'JSONP_Widget_VimeoViewer_'+this.instanceNum,
				embedUrl = 'https://www.vimeo.com/api/oembed.json?url=' +
					encodeURIComponent(this.videos[this.page].url) +
					'&callback='+cbName +
					'&width=950&autoplay=false';
			window[cbName] = scopeC(function (video) {
				this.$elem.find('.vimeo_thumb').append(video.html);
			}, this);

			dfr = this.tpl('playlist',
				{
					videos: this.videos,
					current: this.page,
					embedUrl: embedUrl
				}, scopeC(function(html){
					this._gaEvent('VimeoViewer', 'view', this.videos[this.page].id + '');
					this.$content.html(html);
					this.checkLengths();
			}, this));
			break;
		case 'error':
		case 'loading':
			dfr = this.tpl(type, {message: data}, scopeC(function(html){
					this.$content.html(html);
			}, this));
			break;
		default:
			dfr = new $.Deferred().resolve();
			break;
		}

		return dfr.promise();
	},

	checkLengths: function() {
		this.$content.find('h4').truncateText();
		this.$content.find('p').truncateText();
	},

	_goToPage: function(page) {
		if(this.page !== page) {
			this.page = page;
			this.render('videos');
		}
	},
	_pageCB: function(e) {
		e.preventDefault();
		var type = $(e.currentTarget).data('act'),
			page = parseFloat(this.page),
			lastPage = this.videos.length;

		switch (type) {
		case 'prev':
			page--;
			if(page===-1) {
				page=lastPage-1;
			}
			break;
		case 'next':
			page++;
			if(page===lastPage) {
				page=0;
			}
			break;
		}

		this._goToPage(page);
	},
	_clickH: function(e) {
		e.preventDefault();
		var $this = $(e.currentTarget),
			act = $this.data('act');
		switch(act) {
		case 'prev':
		case 'next':
			this._pageCB(e);
			break;
		}

	},

	setDataEvents: function() {
		this.$elem.onC('click', 'li', function(event){
			var idx = $(event.target).closest('li').data('idx');
			var video = $.extend({}, this._videos[idx]);
			this.controllerCb('playVideo', {video: video, autoplay: true});
		}, this)
			.onC('click', 'button', this._clickH, this);
	},

	_mkEventLabel: function() {
		return (((isset(C2_CFG)) ? C2_CFG.customEnv.id : '') + window.location.pathname);
	},

	_mkURL: function(type, origin) {
		return 'http://api.addthis.com/oexchange/0.8/forward/'+type+'/offer?pubid=ra-51a66612171174a7&url='+encodeURIComponent(origin);
	},
	_toggleMore: function(e) {
		e.preventDefault();
		var $this = $(e.currentTarget),
			text = ($this.html()==='Less')?'More':'Less';
		this.$elem.find('.SocialShare .more').toggle();
		$this.html(text).attr('title', text);
	},
	_toggleShare: function(e) {
		e.preventDefault();
		var $this = this.$elem.find('.SocialShare .share'),
			text = ($this.hasClass('showing'))?'Share This':'Done Sharing';

		if(!$this.hasClass('showing')) {
			$('body').onC('click.close-social', this._toggleShare, this);
		} else {
			$('body').off('click.close-social');
		}
		this.$elem.find('.SocialShare .share-options').toggleClass('show-share');
		$this.toggleClass('showing').attr('title', text);
	},
	_socialEvents: function() {
		this.$elem.onC('click', '.SocialShare a', function(e) {
			e.preventDefault();
			var $this = $(e.currentTarget);
			if(!$this.hasClass('less-more') && !$this.hasClass('share')) {
				var shareType = $(e.currentTarget).attr('class'),
				    label = this._mkEventLabel();

				this._gaEvent('SocialShare', shareType, label);
				window.open(this._mkURL(shareType,location.href));
			}

		}, this)
			.onC('click', '.SocialShare .less-more', this._toggleMore, this)
			.onC('click', '.SocialShare .share', this._toggleShare, this)
			.onC('click', '.SocialShare .share, .SocialShare .less-more', function(e) {
				e.preventDefault();
				e.stopPropagation();
			}, this);
	}
};

O_O.widget.VimeoViewer.View.instanceCount = 0;

O_O.obj.inherit(O_O.lib.View, O_O.widget.VimeoViewer.View);

}(ps$));
;(function($, undefined) {
'use strict';

O_O.widget.Logo.Controller = function($elem, props, msgSys, env) {
	O_O.lib.Controller.apply(this, arguments);

	this.view = O_O.mm.oNew(O_O.widget.Logo.View, $elem, props,
		scopeC(this._controllerCb, this), env);
};

O_O.widget.Logo.Controller.prototype = {
	cfg: {},

	render: function(cfg) {
		this.cfg = $.extend(true, {}, cfg);
		this.cfg.config_tuple = this.getCfg();

		if (empty(cfg.text)) {
			this.cfg.text = this.env('custom').site_name;
		}

		var dfr = new $.Deferred();

		this.view.render(this.cfg).always(function() {
			dfr.resolve();	// must always resolve!
		});

		return dfr.promise();
	},

	_controllerCb: function(type, payload) {
		switch (type) {
		case 'editWidget':
			return this.msgSys.pub('Shell', {
				msg: 'editWidget',
				data: {
					view: payload.view,
					cfgId: this.getCfg()
				}
			});
		case 'maxSize':
			this.cfg.f_max_size = payload;
			break;
		}
	},

	_msgCb: function(channel, payload) {
		var d = payload.data;
		switch (payload.msg) {
		case 'event':
			return this._eventH(d.type, d.data);
		case 'newNav':
			if(this._isForceMenu())
				this.view.checkDelivery(this.cfg, true);
			break;
		}
	},

	_eventH: function(type, data) {
		switch (type) {
		case 'lockMode':
			this.view.disableLinks(!!data.f_lock);
			break;
		case 'modeSwitched':
			if(this._isForceMenu())
				this.view.checkDelivery(false);
			break;
		case 'windowResize':
			if(this._isForceMenu())
				this.view.checkDelivery(true);
			break;
		}
	},

	_isForceMenu: function() {
		return (!!this.cfg.f_max_size ||
			(typeof this.cfg.f_force_menu_button !== 'undefined' &&
			(this.cfg.f_force_menu_button === 'h' || this.cfg.f_force_menu_button === 'v')));
	}
};

O_O.obj.inherit(O_O.lib.Controller, O_O.widget.Logo.Controller);

}(ps$));
;(function($, undefined) {
'use strict';

O_O.widget.Logo.View = function($elem, props, controllerCb, env) {
	O_O.lib.View.apply(this, arguments);

	this._disabled = false;
	this.$elem.onC('click', 'A', function(e) {
		if (this._disabled) {
			e.preventDefault(); // disable links to other modes/pages
			e.stopPropagation();
		}
	}, this);
};

O_O.widget.Logo.View.prototype = {
	_disabled: null,

	hasMirror: null,
	$mirror: null,

	model: null,
	logoData: {},
	/* logoData struct
	{
		logo: {
			image_id: id,
			startW: 0,
			startH: 0,
			width: 0,//					req
			height: 0,//				req
			maxW: 0,
			maxH: 0,
			type: 'text' || 'image'//	req
		},
		nav: {
			width: 0,//					req
			height: 0//					req
		},
		forceMobile: false,//			req
		others: {
			width: 0,//					req
			height: 0//					req
		}
	}
	*/
	disableLinks: function(f_disabled) {
		this._disabled = f_disabled;
	},

	// [NAB] consider function name that better reflects what the function
	//  does (checks the integrity of the logo data), rather than what
	// it is used for - e.g. logoDataIsClean()
	sizeFromData: function(resize) {
		var	cfg = this.cfg,
			image_id = !empty(cfg.image) && !empty(cfg.image[0]) ?
				cfg.image[0].image_id : null,
			f_retina = (cfg.image_f_retina === 't'),
			ld = this.logoData.logo,
			resp = true;

		// short out if resizing
		if ((isset(resize) && !!resize) ||
			this.logoData.responsiveState !== O_O.browser.getResponsiveState())
				return false;

		if (!isset(ld.width))
			resp = false;
		else if (ld.type === 'image') {
			// check if image has changed
			if (ld.image_id !== image_id)
				resp = false;
			// check if retina option has changed
			else if (ld.f_retina !== f_retina)
				resp = false;
		}

		if (!resp)
			this.logoData.logo = {writeDOM:true};

		return resp;
	},

	_isForceMenu: function() {
		return (isset(this.cfg.f_force_menu_button) &&
			(this.cfg.f_force_menu_button === 'h' || this.cfg.f_force_menu_button === 'v'));
	},

	render: function(cfg)
	{
		var dfr;

		this.cfg = $.extend(true, this.cfg || {}, cfg);

		this._clearElem(); // clear all but config

		this.model = this.model || O_O.model.get('Generic', 'logoNavCollision');
		this.model.load().done(scopeC(function(logoData){
			this.logoData = logoData;
			if (!isset(this.logoData.logo))
				this.logoData.logo = [];
			if (!isset(this.logoData.display))
				this.logoData.display = [];

			var	checkDelivery = true,
				sizeFromData = this.sizeFromData();

			switch (cfg.type) {
			case 'text':
				dfr = this.renderTemplate('logoText', {
					style: '',
					text: cfg.text
				}, true, sizeFromData);
				break;
			case 'image':
				if (!cfg.image || !cfg.image.length) {
					// HACK: fallback to text
					this.cfg.type = 'text';
					dfr = this.renderTemplate('logoText', {
						style: '',
						text: cfg.text
					}, true, sizeFromData);
					break;
				}

				var img_info = cfg.image[0],
					static_tuple = {
						id: img_info.image_id,
						config_tuple: cfg.config_tuple,
						config: JSON.stringify(cfg)
					},
					sm = O_O.model.get('StaticImage', static_tuple);

				checkDelivery = !!this.logoData.logo.writeDOM;

				dfr = sm.load({f_force: checkDelivery})
					.then(scopeC(function (data) {
						var opts = {
							width: cfg.image_width,
							height: cfg.image_height,
							mode: 'fit',
							orig: cfg.image_orig_enable || true
						},
							measure = this._measure(sizeFromData, null, data, img_info);

						this.logoImgData = data;

						this.renderTemplate('logoImage', {
							src: sm.getLinkForSize(opts),
							width: measure.width,
							height: measure.height,
							text: cfg.text
						}, checkDelivery);
					}, this));

				break;
			}
		}, this));

		return dfr;
	},

	siblingOffset: function(siblings, dimension, offset) {
		offset = 0;
		for (var i = 0, ct = siblings.length; i < ct; i++) {
			offset += $(siblings[i])[dimension]();
			offset += (dimension.search('width') !== -1 ||
						dimension.search('Width') !== -1) ? 4 : 0;
		}
		return offset;
	},

	renderTemplate: function (tpl, d, checkDelivery)
	{
		if (d.src) this.logoSrc = d.src;
		d.src = d.src || this.logoSrc;
		return this.tpl(tpl, d).done(scopeC(function(html) {
			this._clearElem();
			this.$elem.append(html);
			if (!!checkDelivery) this.checkDelivery(true);
			//When same wiget settings are getting printed twice.
			//Confirm whether we need to use this same DOM there as well.
			if (!!this.hasMirror) {
				this.$mirror.find('a').replaceWith(html);
			}
			if (this.hasMirror === null) {
				this.$mirror = this.$elem.closest('.stack-top')
								.find('.Logo.widget:eq(1)');
				if (this.$mirror.length) this.hasMirror = true;
				else this.hasMirror = false;
			}
		},this));
	},

	_measure: function(sizeFromData, resize, data, img_info, rewrite) {
		var	cfg = this.cfg,
			f_retina = (cfg.image_f_retina === 't'),
			ld = this.logoData.logo,
			width, height, maxH, maxW;

		//quick return if cached.
		if (sizeFromData)
			return {width: ld.width, height: ld.height};

		//awful hack to normalize layout in Sonnet
		this.$elem.closest('#c2').addClass('measure');

		//prep variable.
		var startW = ld.startW ||
				data.screen_width_max || data.screen_width,
			startH = ld.startH ||
				data.screen_height_max || data.screen_height;

		width = startW;
		height = startH;

		//Divide by 2 for retina
		if (f_retina) {
			width /= 2;
			height /= 2;
		}

		maxH = (!!resize) ? this.$elem.css('max-height') :
			ld.maxH || this.$elem.css('max-height');
		maxW = (!!resize) ? this.$elem.css('max-width') :
			ld.maxW || this.$elem.css('max-width');

		//Max dimensions return weird in each browser and are sometimes set weird.
		if (maxH.search('px') === -1 || maxW.search('px') === -1) {
			if (maxH.search('vh') !== -1)
				maxH = $('body').height() * (parseFloat(maxH) / 100) + 'px';
			else if (maxH.search('%') !== -1)
				maxH = this.$elem.parent().height() * (parseFloat(maxH) / 100) + 'px';
			if (maxW.search('vw') !== -1)
				maxW = $('body').width() * (parseFloat(maxW) / 100) + 'px';
			else if (maxW.search('%') !== -1)
				maxW = this.$elem.parent().width() * (parseFloat(maxW) / 100) + 'px';
		}
		//Back to regular in Sonnet
		this.$elem.closest('#c2').removeClass('measure');
		//Use max dimensions to math actual display size.
		if (maxH.search('px') !== -1) {
			this.controllerCb('maxSize', true);
			width = Math.min(width, width / height * parseFloat(maxH));
			height = startH / startW * width;
		}
		if (maxW.search('px') !== -1) {
			this.controllerCb('maxSize', true);
			height = Math.min(height, height / width * parseFloat(maxW));
			width = startW / startH * height;
		}
		//Remove pixel fraction display errors.
		width = Math.floor(width);
		height = Math.floor(height);

		if (width === ld.width ||
			height === ld.width)
			rewrite = false;
		//Cache sizes.
		this.logoData.logo = {
			image_id: img_info.image_id,
			startW: startW,
			startH: startH,
			width: width,
			height: height,
			maxW: maxW,
			maxH: maxH,
			type: cfg.type,
			f_retina: f_retina
		};

		this.logoData.responsiveState = O_O.browser.getResponsiveState();

		this.model.update(this.logoData);

		//Rewrite dom on change dimensions
		if (!!rewrite)
			this.renderTemplate('logoImage', {
				width: width,
				height: height,
				text: cfg.text
			});

		//Return dimensions for nav hide processing.
		return {width: width, height: height};
	},

	checkDelivery: function(resize) {
		var	cfg = this.cfg,
			image_id = !empty(cfg.image) && !empty(cfg.image[0]) ?
				cfg.image[0].image_id : null,
			sizeFromData = this.sizeFromData(image_id, resize),
			$display = this.$elem.parent(),
			$c2 = this.$elem.closest('#c2'),
			isPrepped = false,
			img_info = (cfg.type === 'image') ?
				cfg.image[0] : null;

		this.cfg = $.extend(true, this.cfg || {}, cfg);

		if (!!resize) {
			if (!!this.cfg.f_max_size ||
				this.logoData.responsiveState !== O_O.browser.getResponsiveState() ||
				(cfg.f_force_menu_button === 'v' && $display.height !== this.logoData.display.height) ||
				(cfg.f_force_menu_button === 'h' && $display.width !== this.logoData.display.width))
					this.logoData.display = [];
			else return;
		}
		if (cfg.type === 'image' && isset(this.logoImgData))
			this._measure(sizeFromData, resize, this.logoImgData, img_info, true);
		else if (cfg.type === 'text' && !sizeFromData) {
			var $a = this.$elem.find('a');
			this.logoData.logo = {
				width: $a.width(),
				height: $a.height(),
				type: 'text'
			};
		}
		if ((!sizeFromData || typeof this.logoData.forceMobile === 'undefined') &&
			this.logoData.responsiveState !== 'handhelds') {
			if (this.cfg.f_force_menu_button === 'h') {
				isPrepped = this._checkHorizontal($c2, $display);
			} else if (this.cfg.f_force_menu_button === 'v') {
				isPrepped = this._checkVertical($c2, $display);
			}
			if (!isPrepped) return;
			this._prepped($c2);
		} else {
			this._prepped($c2);
		}
		this.model.update(this.logoData);
	},

	_checkHorizontal: function($c2, $display) {
		// only check delivery of first Logo widget in Otis
		if (C2_CFG.theme === 'Otis' && this.$elem.parent().hasClass('logo-holder')) {
			return;
		}
		var bodyW = this.logoData.display.width || $display.width(),
			otherW = (typeof this.logoData.others !== 'undefined' && !!this.logoData.others) ?
				this.logoData.others.width :
					this.siblingOffset(this.$elem.siblings(':not(.show-nav)'), 'outerWidth'),
			logoW = this.logoData.logo.width || this.$elem.width();
		this.logoData.logo.width = logoW;
		this.logoData.display.width = bodyW;

		if (!otherW)
			this.logoData.others = false;
		else
			this.logoData.other = {width: otherW};

		if (bodyW - logoW - otherW < 25) {
			this._toggleMenuButton($c2, true);
			this.logoData.forceMobile = true;
		} else {
			this._toggleMenuButton($c2, false);
			this.logoData.forceMobile = false;
		}
		return true;
	},

	_checkVertical: function($c2, $display) {
		var bodyH = this.logoData.display.height || $display.height(),
			otherH = (typeof this.logoData.others !== 'undefined' && !!this.logoData.others) ?
			this.logoData.others.height :
			this.siblingOffset(this.$elem.siblings(':not(.Logo, .show-nav)'), 'outerHeight'),
			logoH = this.logoData.logo.height || this.$elem.height();
		this.logoData.logo.height = logoH;
		this.logoData.display.height = bodyH;

		if (!otherH)
			this.logoData.others = false;
		else
			this.logoData.other = {height: otherH};
		if (bodyH - logoH - otherH < 25) {
			this._toggleMenuButton($c2, true);
			this.logoData.forceMobile = true;
		} else {
			this._toggleMenuButton($c2, false);
			this.logoData.forceMobile = false;
		}

		return true;
	},

	_toggleMenuButton: function($c2, f) {
		$c2.toggleClass('menu-button', f);
	},

	_prepped: function($c2) {
		$c2.removeClass('nav-logo-prep');
	}
};

O_O.obj.inherit(O_O.lib.View, O_O.widget.Logo.View);

}(ps$));
;(function($, undefined) {
'use strict';

O_O.widget.Logo.Editor = function() {};

O_O.widget.Logo.View.prototype.editorRefresh = function ($form, e)
{
	if (e) {
		var $target = $(e.currentTarget);
		if (($target.attr('name') == 'type') && 
		    (e.type == 'change') && ($target.val() == 'image')) {
		    	// HACK: trigger a click of the ArchivePicker edit button
			var imageEditButton = $form.find('INPUT[name=image]').parent().find('BUTTON.edit');
			imageEditButton.trigger('click');
		}
	}
	
	return true;
}

}(ps$));
;(function($, undefined) {
'use strict';

O_O.widget.Instagram.Controller = function($elem, props, msgSys, env) {
	O_O.lib.Controller.apply(this, arguments);

	this.view = O_O.mm.oNew(O_O.widget.Instagram.View, $elem, props,
		scopeC(this._controllerCb, this), env);
};

O_O.widget.Instagram.Controller.prototype = {
	token: null,
	authing: false,
	insta_id: false,

	render: function(config) {
		this.config = config;

		// this.view.displayLikes = config.displayLikes === 'true' ||
		// 	config.displayLikes === 't';
		this.view.displayCaption = config.displayCaption === 'true' ||
			config.displayCaption === 't';
		this.view.displayList = 'true';
		// this.view.displayList = config.f_displayList === 'true' ||
		// 	config.f_displayList === 'f';
		this.view.limit = parseFloat(config.limit) || 16;

		/*if (config.token) {
			this.token = this.view.token = config.token;
			this._render();
			return $.Deferred().resolve().promise();
		}*/
		return this.attemptTokenFetch();
	},

	/* unused functions (here to override abstract controller) */

	attemptTokenFetch: function() {
		var fetchUrl = '/psapi/v2/mem/user/keychain/instagram/?'+
			'api_key=PS631731c7&fields=json';

		return $.ajax(fetchUrl, {
			dataType: 'json',
			data: {
				user_id: this.env('custom').id
			}
		}).then(this.processTokenResult.bind(this));
	},

	processTokenResult: function(result) {
		if (result.data === null) {
			this.view.token = null;
			this.view.renderPlaceholder();
		} else {
			var json = result.data.json;
			var keychainData = JSON.parse(json).instagram;
			var token = keychainData.access_token;
			this.view.token = token;
			this.token = token;
			var insta_id = '/' + keychainData.id + '/media';
			this.view.MEDIA_ENDPOINT = insta_id;
			this.view.FULL_URL = this.view.BASE_URL + this.view.MEDIA_ENDPOINT;

			if (typeof keychainData.username != 'undefined') {
				this.view.username = keychainData.username;
			}
		}

		this.view._authDfr = $.Deferred()
		.done(function() {
			this.authing = true;
			this.attemptTokenFetch();
		}.bind(this));

		if (this.authing) {
			this._openEdit();
		}

		this._render();
	},


	_openEdit: function() {
		if (this.env('edit')) {
			this._controllerCb('editWidget', {
				view: this.view
			});
		}
	},

	_render: function() {
		if (this.view.$main) {
			this.view.$main.empty();
			// this.view.$main.toggleClass('grid', this.config.f_displayList !== 't')
			this.view.getFeedPage();
		} else {
			this.view.renderMain();
		}
	},

	_controllerCb: function(type, d) {
		if (type === 'editWidget') {
			return this.msgSys.pub('Shell', {
				msg: 'editWidget',
				data: {
					view: d.view,
					cfgId: this.getCfg()
				}
			});
		}else if (type === 'attemptFetch') {
			this.attemptTokenFetch()
			.then(function() {
			this.view.getFeedWithToken(this.token)
				.done(this._openEdit.bind(this));
			}.bind(this));
		} else {
			return this.msgSys.pub('Shell', {
				msg: type,
				data: d
			});
		}
	},

	_eventH: function(type, data) {
		if (type !== 'Instagram') return;

		switch (data.cmd) {
		case 'authenticate':
			if (!this.token) this.view.authenticate();
			break;
		case 'getFeedWithToken':
			var f = scopeC(function() {
				this.view.getFeedWithToken(data.data.token)
					.done(scopeC(function() {
						this._controllerCb('editWidget', {
							view: this.view
						});
					}, this));
			}, this);
			if (!this.view.$main) {
				this.view.renderMain().done(f());
			} else {
				f();
			}
			break;
		case 'getFeedPage':
			if (!this.token) return;
			var d = data.data;
			this.view.getFeedPage(d.page);
			break;
		}
	}
};

O_O.obj.inherit(O_O.lib.Controller, O_O.widget.Instagram.Controller);

}(ps$));
;(function($, undefined) {
'use strict';

O_O.widget.Instagram.View = function($elem, props, controllerCb, env) {
	O_O.lib.View.apply(this, arguments);
};

O_O.widget.Instagram.View.prototype = {
	MAX_IMAGES: 300,

	// instagram constants
	API_KEY: '3784835324880606',
	BASE_URL: 'https://graph.instagram.com',
	MEDIA_ENDPOINT: '',
	FIELDS: 'caption,media_url,permalink,media_type',
	FULL_URL: null,
	WIDTH: 300,
	HEIGHT: 300,

	// ps environment
	SUBHOST_PROD: 'photoshelter.com',
	SUBHOST_STAGE: 'stage.photoshelter.com',
	SUBHOST_DEV: 'dev.bitshelter.com',
	SUBHOST_QA: 'qa.bitshelter.com',

	// oauth
	_targetOrigin: null,
	oauthURI: null,
	redirectURI: null,
	redirectURIEnd: '/ext/oauth_redirect?path=/index/hash/edit/instagram/',

	_authDfr: null,
	token: null,
	username: null,
	displayCaption: null,
	limit: null,
	numRendered: 0,
	placeholderRendered: false,
	pagination: false,

	$main: null,
	$nextLink: null,
	$prevLink: null,

	viewMap: {
		main: '.main',
		placeholder: '.placeholder',
		nextLink: '.next',
		prevLink: '.prev',
		loading: '.loading-c2'
	},

	_init: function() {
		var host = this.env('custom').host_ps,
		    subHost = host.substr(host.indexOf('.') + 1);

		if (window.location.hostname.slice(-22) == this.SUBHOST_STAGE)
			subHost = this.SUBHOST_STAGE;

		switch (subHost) {
		case this.SUBHOST_DEV:
			this._targetOrigin = 'https://ps.' + this.SUBHOST_DEV;
			break;
		case this.SUBHOST_QA:
			this._targetOrigin = 'http://qa.' + this.SUBHOST_QA;
			break;
		case this.SUBHOST_STAGE:
			this._targetOrigin = 'https://' + this.SUBHOST_STAGE;
			break;
		case this.SUBHOST_PROD:
		default:
			this._targetOrigin = 'https://' + 'www.' + this.SUBHOST_PROD;
			break;
		}

		if(!this.FULL_URL)
			this.FULL_URL = this.BASE_URL + this.MEDIA_ENDPOINT;
		this.redirectURI = this._targetOrigin + this.redirectURIEnd;
		this.oauthURI = this._targetOrigin + '/ext/oauth_lab?service=instagram';
		this.setViewEvents();
	},

	updatePageLinks: function(pagination) {
		if (!pagination.next) {
			this.$nextLink.hide();
		} else {
			this.$nextLink
				.show()
				.unbind("click")
				.click(function(event){
					this.FULL_URL = pagination.next;
					event.preventDefault();
					this.getFeed(this.token);
				}.bind(this))
				.attr('href',"#");
		}
		if (!pagination.previous) {
			this.$prevLink.hide();
		} else {
			this.$prevLink
				.show()
				.unbind("click")
				.click(function(event){
					this.FULL_URL = pagination.previous;
					event.preventDefault();
					this.getFeed(this.token);
				}.bind(this))
				.attr('href',"#");
		}
		this.bindEvents();
	},

	renderItem: function(item) {
		var link = item.permalink;
		var img = item.media_url;
		var params = {
			link: link,
			url: img,
			width: this.WIDTH,
			height: this.HEIGHT
		};
		// if (this.displayLikes) {
		// 	var numLikes = item.likes.count;
		// 	params.likesCount = numLikes;
		// }
		// else params.likesCount = '';
		if (this.displayCaption &&
			typeof item.caption !== 'undefined' &&
			item.caption !== null)
			params.caption = item.caption;
		else
			params.caption = '';
		if(item.media_type == 'IMAGE')
			this.tpl('item', params, scopeC(function(html) {
				this.$main.append(html);
			}, this));
		else if(item.media_type == 'VIDEO')
		this.tpl('video', params, scopeC(function(html) {
			this.$main.append(html);
		}, this));
	},

	renderFeed: function(items, count) {
		this.$main.empty(); // avoid duplicates that appear due to auth cb hell
		this.$elem.find(this.viewMap.placeholder).remove();
		for (var i=0, len=items.length; i<len; i++) {
			this.renderItem(items[i]);
		}
	},

	renderPlaceholder: function(msg = null) {
		if(this.placeholderRendered) return;

		msg = msg ? msg : 'Instagram photos to display here.';
		$('<span class="placeholder">')
			.html(msg)
			.appendTo(this.$elem);
		this.placeholderRendered = true;
	},

	renderMain: function() {
		var vm = this.viewMap;
		var $dfr = $.Deferred();
		this.tpl('main', {list: this.displayList}, scopeC(function(html) {
			this.$elem.append(html);
			this.$main = this.$elem.find(vm.main);

			$dfr.resolve();
			if (this.$nextLink) return;
		}, this));
		return $dfr.promise();
	},

	getFeed: function(token, page) {
		var params = {
			access_token: token,
			fields: this.FIELDS,
			limit: this.limit
		};

		if (page && this.numRendered > 0) {
			params.max_id = page;
		}

		//Validation for users with previous IG API config
		if(this.FULL_URL.includes("undefined")){
			this.renderPlaceholder();
			return null;
		}

		this.$main.append('<div class="loading-c2"><div class="bar-holder"><div class="loading-bar"></div></div></div>');
		return $.getJSON(this.FULL_URL, params)
		.success(function(data){
			this.username = this.username;
			this.renderFeed(data.data);
			this.numRendered = data.data.length;

			this.tpl('pagination', {}, scopeC(function(html) {
				var vm = this.viewMap;
				if (!this.pagination)
					this.$elem.append(html);
				this.pagination = true;
				// this.$nextLink.empty();
				// this.$prevLink.empty();
				this.$nextLink = this.$elem.find(vm.nextLink);
				this.$prevLink = this.$elem.find(vm.prevLink);
				this.updatePageLinks(data.paging);
			}.bind(this)));

		}.bind(this))
		.error(function(data){
			console.log(data);
			this.renderPlaceholder(data.responseText);
		}.bind(this))
		.always(function(){
			this.$main.find(this.viewMap.loading).remove();
		}.bind(this));


	},

	getFeedWithToken: function(token) {
		this.token = token;
		return this.getFeed(token);
	},

	getFeedPage: function(page) {
		if (this.numRendered >= this.MAX_IMAGES) return;
		return this.getFeed(this.token, page);
	},

	setViewEvents: function() {
		window.addEventListener('message', scopeC(function(e) {
			if (e.origin !== this._targetOrigin) return;

			this._authDfr.resolve(e.data);
		}, this));
	},

	setScrollableElement: function() {
		var context = document.getElementById('mode-instagram');
		var scrollElem = this.$elem.closest('.scrollable', context);
		return scrollElem.length == 0 ? window : scrollElem[0];
	},

	scrollToTop: function() {
		var scrollableElem = this.setScrollableElement(),
			scrollY = scrollableElem.scrollY || scrollableElem.scrollTop,
			scrollElemIsWindow = scrollableElem == window,
			currentTime = 0,
			// Math for ease-in-out transition animation over 2 seconds
			time = Math.max(.1, Math.min(Math.abs(scrollY - 0) / 2000, .8));

		function tick() {
			currentTime += 1 / 60;
			var p = currentTime / time;
			var t = -0.5 * (Math.cos(Math.PI * p) - 1);

			if (p < 1) {
				requestAnimationFrame(tick);
				var nextPosition = scrollY + ((0 - scrollY) * t);
				scrollElemIsWindow ? scrollableElem.scrollTo(0, nextPosition) : scrollableElem.scrollTop = nextPosition;
			} else {
				scrollElemIsWindow ? scrollableElem.scrollTo(0, 0) : scrollableElem.scrollTop = 0;
			}
		}
		tick();
	},

	bindEvents: function() {
		var scrolling = false;
		this.$elem.on('click touchend', '.next', function (e) {
			// Handle double-tap event.
			if (scrolling) {
				e.preventDefault();
			} else if (e.type == 'touchend' || e.type == 'click' && !scrolling) {
				scrolling = true;
				this.scrollToTop();
				// Propagate click event to activate link on iOS Safari
				if (e.type == 'touchend') { e.currentTarget.click(); }
			} 
		}.bind(this));
	}
};

O_O.obj.inherit(O_O.lib.View, O_O.widget.Instagram.View);

}(ps$));
;(function($, undefined) {
'use strict';

O_O.widget.Instagram.Editor = function() {};

O_O.widget.Instagram.View.prototype.committed = false;

O_O.widget.Instagram.View.prototype.authenticate = function($form, alreadyAuthed) {
	var url = this.oauthURI,
	    msg = '';

	if (alreadyAuthed) {
		msg = 'Note: To authenticate as another user please make sure' +
		' you are logged into the new account on Instagram first.';
	}
	this.tpl('auth_link', {
		url: url,
		msg: msg
	}, scopeC(function(html) {
		$form.append(html);
	}, this));
};

O_O.widget.Instagram.View.prototype.editorRender = function($form, dfn, cfg, saveCb) {
	if (!this.token || !this.username) this.authenticate($form);
	else {
		this.tpl('auth_form', {
			token: this.token,
			cfg: cfg
		}, scopeC(function(html) {
			$form.html(html);
			$form.find('[name=limit]').val(cfg.limit);
			$form.find('[data-value="' + cfg.limit + '"]')
				.removeAttr('tabindex').closest('li').addClass('current');
			if (!this.committed) {
				saveCb();
				this.committed = true;
				this.controllerCb('editWidget', {
					view: this
				});
			} else {
				if (!!this.username)
					$form.find('.status').html('You are authenticated as <b>' + O_O.tplescape(this.username) + '</b>');
				else
					$form.find('.status').html('You are authenticated.');
				this.authenticate($form, true);
				this.getFeedWithToken(this.token);
			}
		}, this));
	}
	return true;
};

O_O.widget.Instagram.View.prototype.editorRefresh = function($form) {
	var $token = $form.find('input[name="token"]');
	$token.val(this.token);
	return true;
};
}(ps$));
