define(["./Pages.API.class.js", "./TileCollection.class.js", 'jquery'], function (Pages, TileCollection, $) {

Pages.prototype.base_url = '/api/pages/v1/';
Pages.prototype.layout_id = 0;
Pages.prototype.isEditable = false;
Pages.prototype.isSafeMode = false;
Pages.prototype.tileCollection = new TileCollection();
Pages.prototype.blankComponent = '';
Pages.prototype.insertLocation = {x: null, y: null};
Pages.prototype.changed = false;
Pages.prototype.pageLeaveOverride = false;
Pages.prototype.currentlyDragging = false;
Pages.prototype.mouseY = 0;
Pages.prototype.custom_settings_modules = [];
Pages.prototype.layout_changed = false;

Pages.prototype.initEditable = function(layout_id, initialTiles, custom_settings_modules, layout_changed, isSafeMode)
{
	var grid = $('.grid-stack');

	this.layout_id = layout_id;
	this.isEditable = false;
    if (isSafeMode === true)
        this.isSafeMode = true;
	this.layout_changed = layout_changed;

	$('.pages-startup')
		.css('opacity', '1')
		.css('transition', 'moz-opacity 0.5s ease-in-out')
		.css('transition', 'webkit-opacity 0.5s ease-in-out')
		.css('transition', 'opacity 0.5s ease-in-out');

	this.initEditModeButtons();
	this.initMobileHandles();

	this.custom_settings_modules = custom_settings_modules;

	this.tileCollection.init(initialTiles, this.isSafeMode);

	// GridStack may decide to lay things out differently than we have stored (no overlaps, etc) so get the actual coords.
	this.tileCollection.updateGeometry(this.getGeometry());

	// Once the tileCollection is set up it is safe to enable edit mode
	this.beginEditMode();

	// "this" will be assigned to the jquery element in the event so grab a copy now
	var self = this;


	$('#pagegrid').animate({opacity:"1"}, 1500);

	// Monitor changes
	grid.on('dragstart resizestart', function()
	{
		self.currentlyDragging = true;

		setTimeout(function(){self.pageScroller(self);}, 50);
	});

	grid.on('dragstop', function(event)
	{
		var curGeometry = self.getGeometry();
		var hasChanged = self.tileCollection.checkForChanges(curGeometry);
		if (hasChanged)
		{
			self.saveLayout();
			self.addInterstitials();
			self.tileCollection.popHistory();
			self.tileCollection.updateGeometry(curGeometry);
			self.tileCollection.pushHistory();
		}

        // Notify any subscribed components they have been moved
		self._notifyTileMoved($(event.target));

		self.currentlyDragging = false;
	});

	grid.on('gsresizestop', function(event, ui)
	{
		var curGeometry = self.getGeometry();
		var hasChanged = self.tileCollection.checkForChanges(curGeometry);
		if (hasChanged)
		{
			self.saveLayout();
			self.addInterstitials();
			self.tileCollection.popHistory();
			self.tileCollection.updateGeometry(curGeometry);
			self.tileCollection.pushHistory();
		}

		// Notify any subscribed components they have been resized
        $(ui).one("transitionend", function () {
            self._notifyTileResized($(ui));
        });

		self.currentlyDragging = false;
	});

	// Some options controls need to suppress options popover from closing so using a global override
	window.cla.pagesEdit = window.cla.pagesEdit || {};
	window.cla.pagesEdit.popoverAutoClose = true;

	// Fix for broken Bootstrap popover hiding bug ( http://stackoverflow.com/a/14857326 - the final update meant for Bs 3.3.6 )
	// Also fixes a conflict between Select2 and Popover (deleting item from multi-select closes the popover)
	$(document).on('click', function (e) {
		$('.js-add-trigger, .js-delete-trigger, .js-settings-button').each(function () {
			//the 'is' for buttons that trigger popups
			//the 'has' for icons within a button that triggers a popup
			if (window.cla.pagesEdit.popoverAutoClose &&
				!$(this).is(e.target) &&
				$(this).has(e.target).length === 0 &&
				$('.popover').has(e.target).length === 0 &&
				!$(e.target).hasClass('select2-selection__choice__remove') &&
				($(e.target).parents('.select2-dropdown').length === 0) &&
				($(e.target).parents('.bootstrap-datetimepicker-widget').length === 0))
			{
				(($(this).popover('hide').data('bs.popover')||{}).inState||{}).click = false;  // fix for BS 3.3.6
			}

		});
	});

	var $saving = $('#page-saving-spinner').hide();
	var $saved = $('#page-saved-notice').hide();
	$(document)
		.ajaxStart(function () {
			$saving.show();
			$saved.hide();
		})
		.ajaxStop(function () {
			$saving.hide();
			$saved.show();
		});
};

Pages.prototype.initOnUnload = function () {
	var self = this;
	$(window).bind('beforeunload', function() {
			if (self.pageLeaveOverride) {
				self.pageLeaveOverride = false;
				return;
			}

			if (!self.changed)
				return;

			return lmsg('pages.edit.unsaved_changes');
		}
	);
};

Pages.prototype.initEditModeButtons = function()
{
	// "this" will be assigned to the jquery element in the onclick event so grab a copy now
	var self = this;

	$('.js-edit-publish-btn, .js-edit-cancel-btn, .js-edit-reset-btn').on('click', function() {
		$(this).attr('disabled', 'disabled');
		self.pageLeaveOverride = true;
		// Publish this page
		SendByPost({href: $(this).attr('href'), target: '_self'});
	});

	$('.js-change-col, .js-change-layout-bg-wrap, .js-page-layout').on('click', function(event) {
        $(this).attr('checked', 'checked');
        event.preventDefault();
        event.stopPropagation();
		self.pageLeaveOverride = true;
		// Publish this page
		SendByPost({href: $(this).attr('href'), target: '_self'});
	});

	// Toggles additonal settings for pages
	$('.js-pages-custom-toggle').on('click',function(){
	    $(this).parents('.pages-custom-item').toggleClass('active').siblings().removeClass('active');
	});

	$('.js-option-wrap-close-button').on('click',function(){
	    $('.pages-custom-item').removeClass('active');
	});


	$('.js-edit-pre-cancel-btn').on('click', function (event) {
		if (!self.changed)
		{
			event.preventDefault();
			event.stopPropagation();
			$('.js-edit-cancel-btn').click();
		}
	});

	var settings = $('.js-settings_app');

	settings.on('click', '.js-settings-button', function()
	{
		var item = $(this).closest('.grid-stack-item');
		var tile_id = item.data('tile_id');

		// Force any colour pickers to show the right colours
		$('.kolorPicker').trigger('kolorPickerUpdate');
	});


    // I have switched this from $('.grid-stack') as it wasn't saving
	var grid = $('body');

	// Delegated because settings form is built dynamically from a template
    grid.on('click', '.js-settings-submit', function(evt) {
		evt.preventDefault();
		self.changed = true;
		var tile_id = $(this).data('tile_id');

		// Hide the resize icon as the mouse could well be outside the tile by now
		$(this).closest('.grid-stack-item').find('.ui-resizable-handle').hide();

		// CKEditor is bad at syncing data so tell any settings that use it to sync
		$('tile-settings textarea').trigger('do-sync');

		self.tileCollection.saveTile(self.layout_id, tile_id, function(data)
		{
			var tile_block = $('.js-tile[data-tile_id="' + tile_id + '"]');

			// Hide the settings
			((tile_block.find('.js-settings-button').popover('hide').data('bs.popover')||{}).inState||{}).click = false;

			// Replace the current tile contents
			var header = $('.js-tile-header', tile_block);
			if ((data.header !== null) &&
				(data.header !== ''))
			{
				header.html(data.header);
				header.removeClass('hide-header sr-only');
			} else {
				header.addClass('hide-header sr-only');
			}
			$('.js-tile-body', tile_block).html(data.body);

			self._notifyTileAdded(tile_block);

			cla.showMessage(data.status.message);
		});
	});

	// Delay bootstrapping the options/delete buttons until the mouse is over a tile
	grid.on('mouseover', '.grid-stack-item', function(event)
	{
		self.initTileSettings($(event.target), []);
	});

	$('body').on('mousemove', function(e)
	{
		self.mouseY = e.clientY;
	});
};

Pages.prototype.initTileSettings = function(tileElement, library_modules)
{
	var tile = tileElement;
	if (!tileElement.hasClass('grid-stack-item'))
		tile = tileElement.closest('.grid-stack-item');

	if (tile.data('bootstrapped') !== true)
	{
		var app_target = tile.find('.js-settings_app');
		if (app_target.length > 0)
		{
			var injections = ['tile_settings', 'tile_library'];
			for (var i = 0; i < this.custom_settings_modules.length; i++)
			{
				if (injections.indexOf(this.custom_settings_modules[i]) === -1)
					injections.push(this.custom_settings_modules[i]);
			}
			for (i = 0; i < library_modules.length; i++)
			{
				if (injections.indexOf(library_modules[i]) === -1)
					injections.push(library_modules[i]);
			}

			angular.bootstrap(app_target[0], injections);
			var info = this.tileCollection.getTileById(tile.data('tile_id'));

			// TODO - Bug #368 - info should not contain null as all tiles should be loaded and available

			var noSettings = tile.find('.js-no-settings');
			if ((info !== null) &&
				(info.options.length === 0))
			{
				noSettings.prop('rel', 'tooltip');
				noSettings.show();
				noSettings.addClass('settings tile-buttons');
			}
			tile.data('bootstrapped', true);
		}
	}
};

Pages.prototype.pageScroller = function(pages)
{
	// Scroll the page if dragging a tile near the top or bottom
	if (pages.currentlyDragging)
	{
		var wnd = $(window);
		var navbar = $('.navbar-inner')[0].getBoundingClientRect();
		var viewPort = {
			width: wnd.width(),
			height: wnd[0].innerHeight
		};

		var y = pages.mouseY;

		var scrollTop = $(window).scrollTop();

		var edgeHeight = (viewPort.height / 5);
		var amount = 0;
		var maxScrollSpeed = 30;

		if ((y <= (edgeHeight)) &&
			(scrollTop > 0))
		{
			// Top of the page

			// Scale distance into scroll region to 0-1
			amount = y / edgeHeight;

			// Use sine to make a smooth ramp up from slow to fast scroll (1.5708 = pi/2 or 90 degrees in radians)
			amount = Math.sin(amount * 1.5708);

			// Scale up to a range from 0 to the fastest speed
			amount = maxScrollSpeed - (amount * maxScrollSpeed);

			scrollTop -= amount;
			$(window).scrollTop(scrollTop);
		} else if ((y >= (viewPort.height - edgeHeight)) &&
			(scrollTop < ($('body').prop('scrollHeight') - viewPort.height - maxScrollSpeed)))
		{
			// Bottom of the page

			// Scale distance into scroll region to 0-1
			amount = (viewPort.height - y) / edgeHeight;

			// Use sine to make a smooth ramp up from slow to fast scroll (1.5708 = pi/2 or 90 degrees in radians)
			amount = Math.sin(amount * 1.5708);

			// Scale up to a range from 0 to the fastest speed
			amount = maxScrollSpeed - (amount * maxScrollSpeed);

			scrollTop += amount;
			$(window).scrollTop(scrollTop);
		}

		// Carry on updating while we are dragging a tile
		setTimeout(function(){pages.pageScroller(pages);}, 50);
	}
};

Pages.prototype.initMobileHandles = function()
{
	// show drag handle on mobile devices

	if ( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {

		$('.grid-stack').find('.drag').addClass('show-handles');
	}
};

Pages.prototype.beginEditMode = function()
{
	this.isEditable = true;
	this.grid.enable();

	this.showTileButtons();

	this.tileCollection.pushHistory(this.getGeometry());

	this.initMobileHandles();

	var blankComponent = $('#blank-component');
	if (this.blankComponent === '')
		this.blankComponent = blankComponent.html();
	blankComponent.remove();

	this.insertLocation.x = null;
	this.insertLocation.y = null;

	var self = this;

	// Don't add interstitials on the initial DOM to help show the page quicker
	setTimeout(function()
	{
		self.addInterstitials();
	}, 250);

	// Handlers to keep track of the open "add tile" popover
	$(document).on('inserted.bs.popover', '.js-insert-target, .insert-bottom-target', function()
	{
		$(this).find('.inner').addClass('active');

		// add style class to component library popover
		$( '.js-insert-tile' ).closest('.popover').addClass( 'add-tile-popover' );
	});
	$(document).on('hidden.bs.popover', '.js-insert-target, .insert-bottom-target', function()
	{
		$(this).find('.inner').removeClass('active');
	});

	var gridstack = $('body');

	// Handler for tile deleting
	gridstack.on('click', '.js-delete-confirm', {self: self}, this._onDeleteTile);

	// Handler for a tile being added
	gridstack.on('add-new-tile', '#popover-container', {self: self}, this._onAddTile);

	if (!this.layout_changed)
		cla.showMessage(lmsg('pages.edit.edit_started'));
	else
		cla.showMessage(lmsg('pages.edit.layout_changed'));


	this.initOnUnload();
};

Pages.prototype.getInsertCoordinates = function()
{
	// Is it stored?
	if ((this.insertLocation.x !== null) &&
		(this.insertLocation.y !== null))
	{
		return this.insertLocation;
	}

	// Find shortest column and how long it is
	var geometry = this.getGeometry();
	var extents = [0, 0, 0, 0];
	for (var i = 0; i < geometry.length; i++)
	{
		var tile = geometry[i];
		for (var j = 0; j < geometry[i].width; j++)
		{
			if (extents[tile.x + j] < (tile.y + tile.height))
				extents[tile.x + j] = (tile.y + tile.height);
		}
	}
	var shortest = 0;
	var length = 100000;
	for (i = 0; i < extents.length; i++)
	{
		if (extents[i] < length)
		{
			shortest = i;
			length = extents[i];
		}
	}

	return { x: shortest, y: length };
};

Pages.prototype.addInterstitials = function()
{
	$('.js-add-trigger').popover('hide');
	$('.js-insert-target').popover('dispose');
	$('.insert-bottom-target').popover('dispose');
	var addButtons = $('#add_buttons');
	addButtons.find('li').tooltip('hide');
	addButtons.remove();
	$('.grid-stack').append('<div id="add_buttons"></div>');
	addButtons = $('#add_buttons');
	addButtons.html('<div id="popover-container"></div>');

	var geometry = this.getGeometry();
	var cellHeight = this.grid.cellHeight() + this.verticalMargin;
	var hMargin = this.horizontalMargin / 2;

	var extents = [];
	var maxExtents = [];
	for (var i = 0; i < this.columnCount; i++)
	{
		extents.push([]);
		maxExtents.push(0);
	}


	// noTileHelper is a class that forces the column-bottom interstitials to be visible when
	// there are no tiles at all on the page as a prompt that the user can start adding content
	var noTileHelper = '';
	if (geometry.length === 0)
		noTileHelper = ' no-tile-helper';

	for (i = 0; i < geometry.length; i++)
	{
		var tile = geometry[i];
		for (var j = 0; j < geometry[i].width; j++)
		{
			var absX = tile.x + j;

			extents[absX].push({min: tile.y, max: tile.y + tile.height});

			if (maxExtents[absX] < (tile.y + tile.height))
				maxExtents[absX] = (tile.y + tile.height);

		}
	}

	var sortFunc = function (a, b)
	{
		if (a.min < b.min)
			return -1;
		if (a.min > b.min)
			return 1;
		return 0;
	};

	// Sort columns
	for (i = 0; i < this.columnCount; i++)
	{
		extents[i] = extents[i].sort(sortFunc);
	}

	// Add links at the top of each column
	for (i = 0; i < this.columnCount; i++)
	{
		var y1 = -22;
		var y2 = 0;
		if (typeof extents[i][0] !== 'undefined')
			y2 = (extents[i][0].min * cellHeight);
		var x = i * (100 / this.columnCount);
		var height = y2 - y1;
		if (height > (cellHeight * 4))
			height = cellHeight * 4;
		var icon_style = '';
		if (height > 30) {
			icon_style = 'add-pages-component-inner-large" style="line-height: ' + height + 'px;';
        }

		// Add add area above each component
		addButtons.append(
			'<div ng-controller="tileLibraryController as ctrl" class="add-pages-component insert-target-' + this.columnCount + ' js-insert-target" style="left: ' + x + '%; top: ' + y1 + 'px; height: ' + height + 'px;" data-x="' + i + '" data-y="0">' +
				'<tile-insert-location store="ctrl.insertLocation"></tile-insert-location>' +
				'<clang-popover autoclose="true" class="js-popover" container="#popover-container" placement="vertical" shown-callback="ctrl.onOpenPopover(e)">' +
					'<a class="js-trigger add-pages-component-link js-add-trigger">' +
						'<div class="h-100" style="margin-left: ' + hMargin + 'px; margin-right: ' + hMargin + 'px;">' +
							'<span class="inner add-pages-component-inner ' + icon_style + '">' +
                            '<ion-icon class="add-pages-component-icon" name="add-circle"></ion-icon>' +
                            '</span>' +
						'</div>' +
					'</a>' +
					'<div class="js-content"><tile-library tiles="ctrl.tiles" categories="ctrl.categories" control="ctrl.controlToken"></tile-library></div>' +
				'</clang-popover>' +
			'</div>');
	}

	// Add links between tiles vertically
	for (i = 0; i < this.columnCount; i++)
	{
		for (j = 0; j < (extents[i].length - 1); j++)
		{
			y1 = ((extents[i][j].max) * cellHeight) - this.verticalMargin;
			y2 = (extents[i][j + 1].min * cellHeight);
			x = i * (100 / this.columnCount);
			height = y2 - y1;
            var marginTop = ""
            if (height > (cellHeight * 4)) {
                height = (cellHeight * 4) - 20;
                marginTop = 'add-page-component-margin-top';
            }
			icon_style = '';
			if (height > 30) {
				icon_style = 'add-pages-component-inner-large" style="line-height: ' + height + 'px;';
            }

			// Add add area above each component
			addButtons.append(
			 '<div ng-controller="tileLibraryController as ctrl" class="add-pages-component insert-target-' + this.columnCount + ' js-insert-target" style="left: ' + x + '%; top: ' + y1 + 'px; height: ' + height + 'px;" data-x="' + i + '" data-y="' + extents[i][j].max + '">' +
				 '<tile-insert-location store="ctrl.insertLocation"></tile-insert-location>' +
			     '<clang-popover autoclose="true" class="js-popover" container="#popover-container" placement="vertical" shown-callback="ctrl.onOpenPopover(e)">' +
					 '<a class="js-trigger add-pages-component-link js-add-trigger">' +
			            '<div class="h-100 ' + marginTop + '" style="margin-left: ' + hMargin + 'px; margin-right: ' + hMargin + 'px;">' +
			                '<span class="inner add-pages-component-inner ' + icon_style + '">' +
                            '<ion-icon class="add-pages-component-icon" name="add-circle"></ion-icon>' +
                            '</span>' +
			            '</div>' +
			        '</a>' +
			        '<div class="js-content"><tile-library tiles="ctrl.tiles" categories="ctrl.categories" control="ctrl.controlToken"></tile-library></div>' +
			     '</clang-popover>' +
			 '</div>');
		}
	}

	// Add links at the bottom of each column
	for (i = 0; i < this.columnCount; i++)
	{
		y1 = (maxExtents[i] * cellHeight) - this.verticalMargin;
		y2 = y1 + (cellHeight * 4);
        x = i * (100 / this.columnCount);
        height = (y2 - y1) - 20;


		// Add add area above each component
		addButtons.append(
			'<div ng-controller="tileLibraryController as ctrl" class="add-pages-component insert-target-' + this.columnCount + ' js-insert-target" style="left: ' + x + '%; top: ' + y1 + 'px; height: ' + height + 'px;" data-x="' + i + '" data-y="' + maxExtents[i] + '">' +
				'<tile-insert-location store="ctrl.insertLocation"></tile-insert-location>' +
				'<clang-popover autoclose="true" class="js-popover" container="#popover-container" placement="vertical" shown-callback="ctrl.onOpenPopover(e)">' +
					'<a class="js-trigger add-pages-component-link js-add-trigger">' +
						'<div class="h-100 add-page-component-margin-top" style="margin-left: ' + hMargin + 'px; margin-right: ' + hMargin + 'px;">' +
							'<span class="inner add-pages-component-inner add-pages-component-inner-large'+ noTileHelper +'" style="line-height: ' + height + 'px;">' +
                            '<ion-icon class="add-pages-component-icon" name="add-circle"></ion-icon>' +
                            '</span>' +
						'</div>' +
					'</a>' +
					'<div class="js-content"><tile-library tiles="ctrl.tiles" categories="ctrl.categories" control="ctrl.controlToken"></tile-library></div>' +
				'</clang-popover>' +
			'</div>');
	}

	angular.bootstrap(addButtons, ['tile_library', 'tile_settings']);
};

Pages.prototype.saveLayout = function(successFunc)
{
	var geometry = this.getGeometry();
	var geom2 = [];
	for (var i = 0; i < geometry.length; i++)
	{
		if ((geometry[i] !== null) &&
			(geometry[i].tile_id > 0))
		{
			geom2.push(geometry[i]);
		}
	}

	this.changed = true;

	$.ajax({
		url: this.base_url + this.layout_id + '/geometry',
		data: JSON.stringify({geometry: geom2}),
		method: 'PUT',
		contentType: 'application/json',
		success: function($data)
		{
			if (!$data['success'])
			{
				cla.showMessage($data['message'], '', true);
				return;
			}
			if (typeof successFunc === 'function')
				successFunc();

		},
		error: function(xhr)
		{
			var response = $.parseJSON(xhr.responseText);

			// Show a formatted error if possible
			if (typeof response.error === 'boolean' && response.error)
				cla.showMessage(response.message, '', true);
			else
				cla.showMessage(xhr.responseText, '', true);

			// Is it a "page not found" exception
			if (typeof response.code === 'number' && response.code === 1)
				cla.pages.invalidatePage();
		}
	});
};

Pages.prototype.getGeometry = function()
{
	return _.map($('.grid-stack .grid-stack-item:visible').not('.grid-stack-placeholder'), function (el) {
		el = $(el);
		var node = el.data('_gridstack_node');
		if (typeof node === 'undefined')
			return {};
		return {
			tile_id: el.data('tile_id'),
			x: node.x,
			y: node.y,
			width: node.width,
			height: node.height
		};
	});
};

Pages.prototype.showTileButtons = function(element)
{
	var root;
	if (typeof element !== 'undefined')
		root = $(element);
	else
		root = $('.grid-stack');

	var moveHandle = root.find('.js-move-handle');
	moveHandle.show();
	moveHandle.addClass('drag tile-buttons');

	var settingsButton = root.find('.js-settings-button');
	settingsButton.show();
	settingsButton.addClass('settings tile-buttons');

	var deleteButton = root.find('.js-delete-button');
	deleteButton.show();
	deleteButton.addClass('delete tile-buttons');

	root.find('.tile-settings-title').show();

	$('.ui-resizable-handle').html('<ion-icon name="resize-outline"></ion-icon>');
};

Pages.prototype.showOptionsPopover = function(element)
{
	var root;
	if (typeof element !== 'undefined')
		root = $(element);
	else
		return;

	var settingsButton = root.find('.js-settings-button');
	settingsButton.click();
};

Pages.prototype.hideTileButtons = function()
{
	var root = $('.grid-stack');

	var moveHandle = root.find('.js-move-handle');
	moveHandle.hide();
	moveHandle.removeClass('drag tile-buttons');

	var settingsButton = root.find('.js-settings-button');
	settingsButton.hide();
	settingsButton.removeClass('settings tile-buttons');

	var settingsButton = root.find('.js-no-settings');
	settingsButton.hide();
	settingsButton.removeClass('settings tile-buttons');

	var deleteButton = root.find('.js-delete-button');
	deleteButton.hide();
	deleteButton.removeClass('delete tile-buttons');

	root.find('.tile-settings-title').hide();

	$('.ui-resizable-handle').html('');
};

Pages.prototype._onDeleteTile = function(event) {
	event.preventDefault();

	var self = event.data.self;
	var gsItem = $(event.target).closest('.grid-stack-item')[0];

	// Notify the component first so it can access the element before we remove it
	self._notifyTileDeleted($(gsItem));

	self.grid.removeWidget(gsItem);

	self.addInterstitials();
	self.saveLayout(function()
	{
	    cla.showMessage(lmsg('pages.edit.tile_deleted'));
	});
};

Pages.prototype._onAddTile = function(event)
{
	var tile = event.tile;
	var self = event.data.self;

	var divisor = 12 / self.columnCount;

	var width = 1;
	var height = 4;
	if (typeof tile.size_constraints.min_width !== 'undefined')
		width = Math.ceil(tile.size_constraints.min_width / divisor);
	if (typeof tile.size_constraints.min_height !== 'undefined')
		height = Math.ceil(tile.size_constraints.min_height / 3);

	var componentName = tile.title;
	var template = self.blankComponent.replace('[Title]', componentName)
		.replace('grid-stack-item js-tile', 'grid-stack-item js-tile grid-stack-item-' + self.columnCount);

	var coords = self.getInsertCoordinates();
	var newTile = self.grid.addWidget(template, coords.x, coords.y, width, height, false);

	self.grid.movable(newTile, true);
	self.grid.resizable(newTile, true);

	var data =
	{
		component: tile.key,
		x: coords.x,
		y: coords.y,
		width : width,
		height: height,
		options: [],
		return: ['status', 'info', 'tile', 'options'],
        isSafeMode: self.isSafeMode
	};

	$.ajax(
		{
			url: '/api/pages/v0/' + self.layout_id + '/tiles/',
			method: 'POST',
			data: JSON.stringify(data),
			contentType: 'application/json',
			success: function(result)
			{
				if (result.status.success)
				{
					// Add the tile to our internal list
					var tileData = angular.extend(result.info);
					tileData.type = tileData.component;
					tileData.layout_id = self.layout_id;
					self.tileCollection.addtile(tileData);

					// Update gridstack
					cla.showMessage(result.status.message);
					self.grid.removeWidget(newTile);
					newTile = self.grid.addWidget(result.tile, coords.x, coords.y, width, height, false);
					self.grid.movable(newTile, true);
					self.grid.resizable(newTile, true);
					newTile.data('tile_id', result.info.id);

					var library_scripts = [];
					var library_modules = [];
					for (var key in result.info.options)
					{
						if (result.info.options.hasOwnProperty(key))
						{
							var option = result.info.options[key];
							if (typeof option['library_scripts'] !== 'undefined')
							{
								library_scripts.push.apply(library_scripts, option['library_scripts']);
							}
							if (typeof option['library_modules'] !== 'undefined')
							{
								library_modules.push.apply(library_modules, option['library_modules']);
							}
						}
					}

					self.showTileButtons(newTile);
					requirejs(library_scripts, function()
					{
						self.initTileSettings(newTile, library_modules);

                        self.addInterstitials();
                        self.saveLayout();
                        self._notifyTileAdded(newTile);

						// Short delay for Angular to bootstrap the settings popover
						setTimeout(function()
						{
							newTile.find('.js-settings_app').addClass('settings-open');
							self.showOptionsPopover(newTile);
							newTile.find('.js-settings_app').removeClass('settings-open');
						}, 50);
					});
				}
			},
			error: function(xhr)
			{
				self.grid.removeWidget(newTile);
				var response = $.parseJSON(xhr.responseText);

				// Show a formatted error if possible
				if (typeof response.error === 'boolean' && response.error)
					cla.showMessage(response.message, '', true);
				else
					cla.showMessage(xhr.responseText, '', true);

				// Is it a "page not found" exception
				if (typeof response.code === 'number' && response.code === 1)
					cla.pages.invalidatePage();
			}
		});

	self.insertLocation = { x: null, y: null };
};

Pages.prototype.enableTileMoving = function()
{
	if (this.grid === null)
		return;

	this.grid.enableMove(true);
};

Pages.prototype.disableTileMoving = function()
{
	if (this.grid === null)
		return;

	this.grid.enableMove(false);
};

Pages.prototype.invalidatePage = function()
{
	// Force popovers to close
	$('.js-add-trigger, .js-delete-trigger, .js-settings-button').each(function ()
	{
		(($(this).popover('hide').data('bs.popover')||{}).inState||{}).click = false;
	});

	// Disable the options/delete buttons and tile titles
	this.hideTileButtons();

	// If we were moving a tile, stop the auto-scrolling
	this.currentlyDragging = false;

	// Don't allow the tile library to be opened
	$('#add_buttons').hide();

	// Disable the main page buttons
	$('.js-edit-publish-btn').prop('disabled', 'disabled');
	$('.js-edit-pre-cancel-btn').prop('disabled', 'disabled');

	$('.ui-resizable-handle').hide();

	$('.grid-stack').addClass('edit-locked');

	// Can't disable grid to stop dragging straight away for some reason?
	setTimeout(function()
	{
		cla.pages.grid.disable();
	}, 300);

	// Show the warning message
	$('#page_editcopy-missing').removeClass('d-none');

	// Go to the top of the page to make the message obvious
	$('body').scrollTop(0)
};

return Pages;
});
