// Prototype DOM extensions (extensiont to prototype's DOM extensions)
Element.addMethods({
    setClass: function(el, cls, on) {
    	if (on) return $(el).addClassName(cls);
    	return $(el).removeClassName(cls);
    }
});

PageObject = Class.create();
PageObject.prototype = {
	controlsStr:
		'<div class="ul drg"></div>' +
		'<div class="br drg"></div>' +
		'<div class="st drg"></div>' +
		'<div class="sb drg"></div>' +
		'<div class="sl drg"></div>' +
		'<div class="sr drg"></div>' +
		'<div class="bar"><div class="drag"></div><div class="delete"></div><div class="edit"></div><div class="lock"></div></div>',
	ie6Mode: (document.all && !window.XMLHttpRequest),
	lastEffects: null,
	removeEdit: false,
    removeLock: true,

	doInit: function(pg, divContent, setupData, setHeight) {
		this._forceFocus = false;
		this.page = pg.selected;
		if (setupData) {
			this.setupData = setupData;
		} else {
			this.setupData = $H({ lock: 0 });
		}
		this.moved = false;
		this.div = $(document.createElement('div'));
		this.id = Util.getId();
		this.div.id = this.id;
		this.div.addClassName('positionable');
		this.div.update(divContent);
		if (setupData) {
			this.reposition();
		} else {
			Util.p(this.div, this.ph()/loadData.height,
				this.pw()/loadData.width,
				this.ph()/3,
				this.pw() * ((loadData.width - 2)/loadData.width));
			this.updateData();
		}
		this.page.appendChild(this.div);

		this._hover = this.hover.bindAsEventListener(this);
		this._unhover = this.unhover.bindAsEventListener(this)

		Util.defer(this, this.hideQuickly);
		Util.defer(this, this.fixIE);
	},

	forceFocus: function(bool) { this._forceFocus = bool; },

	fixIE: function() {
		var c = this.div.down('.controls');
		if (!c) return;
		Util.fixNodeForIE6(c);
	},

	reposition: function() {
		var w = this.pw();
		var h = this.ph();
		Util.p(this.div, this.setupData.top * h, this.setupData.left * w, this.setupData.height * h, this.setupData.width * w);
	},

	updateData: function() {
		var w = this.pw();
		var h = this.ph();
		this.setupData.top = parseFloat(this.div.style.top) / h;
		this.setupData.left = parseFloat(this.div.style.left) / w;
		this.setupData.height = parseFloat(this.div.style.height) / h;
		this.setupData.width = parseFloat(this.div.style.width) / w;
		this.moved = true;
	},

	initialSetup: function() {
		if (this.ie6Mode) {
			this.div.style.borderWidth = '0px';
		} else {
			this.div.style.borderColor = 'transparent';
		}
		var e;
        if (!cspMode) {
            if (this.setupData.lock & 1) {
	        	e = this.div.down('.delete');
	        	if (e) e.hide();
	        }
	        if (this.setupData.lock & 4) {
	            e = this.div.down('.drag');
	            if (e) e.hide();
	            this.div.getElementsBySelector('.drg').each(function(el) {
	            	el.parentNode.removeChild(el);
	            });
	        }
	        // if image set it, leave it set
	        this.removeEdit = this.removeEdit  || (this.setupData.lock & 8);
	    }
		if (this.removeEdit) {
            e = this.div.down('.edit');
			if (e) e.parentNode.removeChild(e);
		}
        if (this.removeLock) {
            e = this.div.down('.lock');
            if (e) e.parentNode.removeChild(e);
        }
	},

	hideQuickly: function() {
		this.initialSetup();
		var c = this.div.down('.controls');
		this.controls = c ? $A(c.getElementsByTagName('div')) : $A();
		this.controls.map(function(e) { $(e).hide(); });
	},

	hideInitially: function() {
		this.initialSetup();
		var c = this.div.down('.controls');
		this.controls = c ? $A(c.getElementsByTagName('div')) : $A();
		this._unhover();
	},

	_cancelEffects: function() {
		if (!this.lastEffects) return;
		if (!this.ie6Mode) this.lastEffects.invoke('cancel');
	},

	hover: function(evt) {
		if (evt) Event.stop(evt);
        this._cancelEffects();
		try {
			var current = this;
			pageEditor._items.each(function(e) {
				if (e[1] !== current) {
					e[1].forceFocus(false);
	    			e[1].unhover(null, true, false);
				}
			});
		} catch (e) {}
		if (this.controls) this.controls.map(function(e) { $(e).show(); });
		this.div.style.borderWidth = '1px';
		this.div.addClassName('hovered');
	},

	unhover: function(evt, force, dontRelease) {
		if (evt) Event.stop(evt);
		if (this.focused && !force) {
			return;
		}
		if (!dontRelease && this.releaseFocus) this.releaseFocus();
		pageEditor.lastHovered = null;
		this._cancelEffects();
		var textarea = this.div.down('textarea');
		if (textarea) {
			if (!dontRelease) textarea.blur();
		    this.focused = false;
		}
        if (this.controls) this.controls.map(function(e) { $(e).hide(); });
		if (this.ie6Mode) {
			this.div.style.borderWidth = '0px';
		} else {
			this.div.style.borderColor = 'transparent';
		}
        this.div.removeClassName('hovered');
	},

	cleanup: function() {
		//Event.stopObserving(this.div, 'mouseover', this._hover);
		//Event.stopObserving(this.div, 'mouseout', this._unhover);
	},

	ph: function() { return this.page.getHeight(); },

	pw: function() { return this.page.getWidth(); }
};

PageImage = Class.create();
Object.extend(PageImage.prototype, PageObject.prototype);
Object.extend(PageImage.prototype, {
	type: 'image',
	imageFile: null,
	_template: new Template(
		'<div class="controls">' +
		PageObject.prototype.controlsStr.gsub(/\/images\//, imageBase) +
		'</div>' +
		'<img class="pageimg" src="#{img}" />'),

	initialize: function(iconImageFile, pg, id, setupData) {
		this.removeEdit = true;
		if (cspMode) this.removeLock = false;
		this.imageFile = iconImageFile;
		this.doInit(pg, this._template.evaluate({base: imageBase, img: this.imageFile, ext: Prototype.Browser.IE ? 'gif' : 'png'}), setupData, false);
		this.id = id + '__' + this.id; // make a GUID
		this.div.id = this.id;
		if (!setupData) {
			var h = this.div.getHeight();
			var w = Math.floor(h * library.getAspect(id));
			var pw = pg.selected.getWidth();
			if (w > pw) {
				h = Math.floor(pw / library.getAspect(id));
				w = pw;
			}
			this.div.style.width = w + 'px';
			this.div.style.height = h + 'px';
			this.div.style.left = (pw - w)/2 + 'px';
			this.updateData();
		}
		if (cspMode) Util.defer(this, this.bindLock, 500);
		if (cspMode || (!(this.setupData.lock & 4))) Event.observe(this.div, 'click', this.hover.bindAsEventListener(this));
	},

	bindLock: function() {
		this.lock = this.div.down('.lock');
		this._toggleLock = this.toggleLock.bindAsEventListener(this);
		if (this.lock) {
			Event.observe(this.lock, 'click', this._toggleLock);
			this.updateLock();
		}
	},

	updateLock: function() {
        if (this.setupData.lock) {
            this.lock.className = 'lock';
        } else {
            this.lock.className = 'nolock';
        }
	},

	toggleLock: function(evt) {
		Event.stop(evt);
        if (!this.setupData.lock) this.setupData.lock = 0;
        this.setupData.lock = (this.setupData.lock) ? 0 : 0xffff;
        this.updateLock();
	},

	cleanup: function() {
		Event.stopObserving(this.div, 'click', this._hover);
		if (this._toggleLock) Event.stopObserving(this.lock, 'click', this._toggleLock);
	}
});

ImagePlaceholder = Class.create();
Object.extend(ImagePlaceholder.prototype, PageObject.prototype);
Object.extend(ImagePlaceholder.prototype, {
	type: 'place_image',
	_template:
		'<div class="controls">' +
		PageObject.prototype.controlsStr.gsub(/\/images\//, imageBase) +
		'</div>' +
		'<div class="image_ph">click to add a picture</div>',

	initialize: function(pg, setupData) {
		this.hideInitially = function() {};
		this.removeEdit = true;
		this.doInit(pg, this._template, setupData, false);
		this.id = Util.getId();
		this.div.id = this.id;
		this.div.addClassName('placeholder');
		this._openLibrary = this._openLibrary.bindAsEventListener(this);
        Event.observe(this.div, 'click', this._hover);
		Util.defer(this, this.fixImage, 250);
	},

	fixImage: function() {
		this.placeHolder = this.div.down('.image_ph');
		try {
			Event.observe(this.placeHolder, 'click', this._openLibrary);
		} catch (except) {}
		Util.fixNodeForIE6(this.div);
	},

	_openLibrary: function(evt) {
		var el = Event.element(evt);
		if (el.hasClassName('placeholder') || el.hasClassName('image_ph')) {
			library.show(this);
		}
	},

	cleanup: function() {
		Event.stopObserving(this.placeHolder, 'click', this._openLibrary);
	}
});

PageText = Class.create();
Object.extend(PageText.prototype, PageObject.prototype);
Object.extend(PageText.prototype, {
	type: '',
	_default: 'Click here to edit text',
	_template: (
		'<div class="controls">' +
		PageObject.prototype.controlsStr +
		'</div>').gsub(/\/images\//, imageBase),

	initialize: function(pg, setupData) {
		this.type = setupData ? setupData.type : 'text';
		this.doInit(pg, this._template, setupData, true);
		this.textarea = $(document.createElement('textarea'));
		this.div.appendChild(this.textarea);
		this.textarea.addClassName('pagetext');
		this.div.addClassName('text');
		if (setupData) {
			this.textarea.value = setupData.content ? setupData.content : this._default;
            if (setupData.type == 'text_title') {
                this.originalTitle = setupData.content;
            }
	        if (!cspMode && (setupData.lock & 2)) {
                this.textarea.readOnly = true;
	        }
			$(this.textarea).setStyle({
				fontSize: (this.ph() * setupData.fontSize) + 'px',
				color: (setupData.color || '#000000'),
				fontWeight: (setupData.fontWeight || 'normal'),
				fontStyle: (setupData.fontStyle || 'normal'),
				textAlign: (setupData.textAlign || 'left')
				}, true);
		} else {
			this.textarea.value = this._default;
			pageEditor.editor.applyOldState(this, this.textarea);
			//this.setupData.fontSize = parseFloat(this.textarea.style.fontSize / this.ph());
		}
		if (Prototype.Browser.IE) {
			this.textarea.style.height = this.div.getHeight() + 'px';
		}
		Event.observe(this.textarea, 'focus', this.lockFocus.bindAsEventListener(this));
		Event.observe(this.textarea, 'blur', this.releaseFocus.bindAsEventListener(this));
	},

	reposition: function() {
		PageObject.prototype.reposition.apply(this);
		if (this.textarea) {
			this.textarea.style.fontSize = (this.setupData.fontSize * this.ph()) + 'px';
		}
	},

	updateFontSize: function(sz) {
		this.setupData.fontSize = sz;
		this.reposition();
	},

	hasText: function() {
		var t = $F(this.textarea);
		return t && t != this._default;
	},

	lockFocus: function(evt) {
		this.focused = true;
		if (this.hover) this.hover();
		if ((!this.hasText()) && evt) {
			this.textarea.value = '';
		}
	},

	releaseFocus: function(evt) {
		if (this._forceFocus) {
			Event.stop(evt);
			return;
        }
		var doUnhover = !(evt && this.focused);
		this.focused = false;
		if (!$F(this.textarea)) {
			this.textarea.value = this._default;
		} else {
			if ((this.setupData.type == 'text_title') && (this.textarea.value != this.originalTitle)) {
				this.originalTitle = this.textarea.value;
				pageEditor.updateTitle(this.textarea.value);
			}
		}
        this.textarea.blur();
		if (doUnhover) this.unhover(null, false, true);
	},

	isFocusLocked: function() {
		return this.focused;
	}
});

PageLockedText = Class.create();
Object.extend(PageLockedText.prototype, PageObject.prototype);
Object.extend(PageLockedText.prototype, {
	type: 'locked_text',
	_template: '',
	content: null,

	initialize: function(pg, setupData) {
		this.doInit(pg, this._template, setupData, true);
		this.div.addClassName('locked_text');
		this.content = this.setupData.content || '';
		this.div.update(this.content.replace('\n', '<br/>'));
		this.div.setStyle({
			fontSize: (this.ph() * this.setupData.fontSize) + 'px',
			color: (this.setupData.color || '#000000'),
			fontWeight: (this.setupData.fontWeight || 'normal'),
			fontStyle: (this.setupData.fontStyle || 'normal'),
			textAlign: (this.setupData.textAlign || 'left')
			}, true);
	},

	reposition: function() {
		PageObject.prototype.reposition.apply(this);
		this.div.style.fontSize = (this.setupData.fontSize * this.ph()) + 'px';
	},

	hideQuickly: function() {
		this.controls = $A();
	},

	hideInitially: function() {
		this.controls = $A();
	}
});

BgProps = Class.create();
BgProps.prototype = {
	_target: null,
	_lastColor: 'rgb(255,255,255)',

	initialize: function() {
		this.element = $('bgedit');
		Util.wrapDialog(this.element);
		this.element.hide();
		Event.observe(this.element, 'click', this._handleClick.bindAsEventListener(this));
	},

	_handleClick: function(evt) {
		if (!this._target) return;

		var el = Event.element(evt);
		if (el.nodeName.toLowerCase() == 'span') el = el.parentNode;

		if (!el) return;
		switch (el.id) {
			case 'bg_save':
				this.save();
				break;
			case 'bg_cancel':
				this.cancel();
				break;
			case 'bg_last':
				this.setLast();
				break;
			default:
				if (el.hasClassName('box')) {
					this.recolor(el.style.backgroundColor);
				}
				break;
		}
		Event.stop(evt);
	},

	show: function(target) {
		this._target = target;
		this.element.style.left = (parseInt(target.style.left) + 20) + 'px';
		this.element.style.top = (parseInt(target.style.top) + 20) + 'px';
		this.oldColor = target.style.backgroundColor;
		if (!this.oldColor) this.oldColor = 'rgb(255,255,255)';
		this.element.show();
	},

	recolor: function(color) {
		if (this._target) this._target.style.backgroundColor = color;
	},

	save: function() {
		this._lastColor = this._target.style.backgroundColor;
		this._target = null;
		this.element.hide();
	},

	cancel: function() {
		this._target.style.backgroundColor = this.oldColor;
		this._target = null;
		this.element.hide();
	},

	setLast: function() {
		this._target.style.backgroundColor = this._lastColor;
	},

	getLastColor: function() {
		return this._lastColor;
	}
};

PageEditor = Class.create();
PageEditor.prototype = {
	_items: $H(),
	_lastEdit: null,
	selected: null,
	_disallowDrag: false,
	_curPage: 0, // after zero, this is only odd numbers since 2 pages are shown
	_nextEn: true,
	_prevEn: true,
	_zoomFactor: 1,
	_offset: { x: 0, y: 0 },
	_size: { w: 0, h: 0 },
	bgEditor: null,
	_snapBackSave: null,
	_hintsEnabled: true,
	_sizeWarned: false,
    PMWIDTH: 50,

	initialize: function(leftPageId, rightPageId, containerId, pageSize) {
		this.left = $(leftPageId);
		this.right = $(rightPageId)
		this.instructions = $('instructions');
		if ($('instrHelp')) Event.observe('instrHelp', 'click', function() { editor.showHelp() });
		this._pageMenu = $('pagemenu');
		Event.observe(this._pageMenu, 'mouseover', function(evt) {
			var el = Event.element(evt);
			if (!el.hasClassName('icon')) el = el.up('.icon');
			if (!el) return;
			el = el.down('span');
			if (!/_d\.png/.test(el.style.backgroundImage)) {
				el.style.backgroundImage = el.style.backgroundImage.replace('_n.png', '_o.png');
			}
		});
		this.pageContainer = $(containerId);
		this.pageDragger = this.pageContainer.down('.dragarea');
		this.prev = $('editor_prev');
		this.next = $('editor_next');
		this.hint_l = $('hint_l');
		this.hint_r = $('hint_r');
        if (cspMode) {
            Event.observe(this.hint_l, 'click', this.editHint.bindAsEventListener(this, this.hint_l));
            Event.observe(this.hint_r, 'click', this.editHint.bindAsEventListener(this, this.hint_r));
        }

		this.editor = new TextProps2();
		this.bgEditor = new BgProps();
		this.aspect = pageSize[1] / pageSize[0];

		this.resizeContainer();
		this.setSelected(this.right);
		this.attachUI();
		this.updatePager();
		this.left.addClassName('disabled');
		pagingController.setSelected(0, true);

		Event.observe(this.left , 'focus', this.changeFocus);
		Event.observe(this.right, 'focus', this.changeFocus);
		Event.observe(window, 'resize', this.resizeContainer.bindAsEventListener(this));
	},

	editHint: function(evt, w) {
		Event.stop(evt);
	    var text = w.down('.content').innerHTML;
	    text = prompt('Enter hint:', text);
	    if (text != null) {
	    	w.down('.content').update(text);
	    }
	},

	changeFocus: function(evt) {
		var e = Event.element(evt);
		if (e.nodeName.toLowerCase() != 'textarea') {
			e.blur();
		}
	},

	getSelectedPageNumber: function() {
		var p = this._curPage;
		if (this._curPage && this.selected == this.right) p++;
		return p;
	},

	enableHints: function(enable) {
		if (arguments.length == 0 || enable == this._hintsEnabled) return;
		this._hintsEnabled = enable;
		var icon = $('pc_hint');
		icon.setClass('on', enable);
		if (enable) {
			this.save();
			this.focusPage(this._curPage, true);
		} else {
			this.hint_l.hide();
			this.hint_r.hide();
		}
		this.resizeContainer();
	},

	showBgEditor: function(index) {
		this.focusPage(index);
		this.bgEditor.show(this.selected);
	},

	attachUI: function() {
		this.left.makePositioned().makeClipping();
		this.right.makePositioned().makeClipping();
		$('editorspace').makePositioned();

		Event.observe(this.pageContainer, 'mousedown', this._dragStart.bindAsEventListener(this));
		Event.observe(this.pageContainer, 'mouseup', this._dragStop.bindAsEventListener(this));
		Event.observe(this.pageContainer, 'mousemove', this._dragUpdate.bindAsEventListener(this));
		Event.observe(this.pageContainer, 'click', this.releaseAllItems);
        //Event.observe(window, 'focus', this.releaseAllItems);
		var tHandler = this._topAction.bindAsEventListener(this);
		Event.observe($('tleft'), 'click', tHandler);
		Event.observe($('tright'), 'click', tHandler);
		Event.observe(this._pageMenu, 'click', this._pmAction.bindAsEventListener(this));
		Event.observe(this.prev, 'click', this.previousPages.bindAsEventListener(this));
		Event.observe(this.next, 'click', this.nextPages.bindAsEventListener(this));
		Event.observe($('rlogin'), 'click', this.remoteLogin.bindAsEventListener(this));
		Event.observe($('rlogin_cancel'), 'click', function() {
			$('loginOrReg').hide();
			Messaging.unlockUI();
		});
        Event.observe($('r_join'), 'click', this.remoteRegister.bindAsEventListener(this));
		Event.observe(this.pageContainer, 'contextmenu', function(evt) {
			if (Event.element(evt).nodeName.toLowerCase() == 'textarea') return;
			Event.stop(evt);
		});
	},

	releaseAllItems: function(evt) {
		// for IE, we need to force release if the click is outside a positonable
		// because of incorrect event ordering
		var force = false;
		if (Prototype.Browser.IE) {
    		var el = Event.element(evt);
	   	    force = !(el.hasClassName('.positionable') || el.up('.positionable'));
		}
		pageEditor._items.each(function(e) { e[1].forceFocus(false); if (e[1].unhover) e[1].unhover(null, force, !force); });
	},

	_pmAction: function(evt) {
		var el = Event.element(evt);
		while (el && !el.id) el = $(el.parentNode);
		if (!el || !el.id.startsWith('pm_')) return;

		switch (el.id) {
			case 'pm_addtext'    : this.addText(); break;
			case 'pm_addimage'   : library.show(); break;
			case 'pm_addimageph' : this.addImagePlaceholder(); break;
			case 'pm_bgcolor'    : pagingController.menuChangeBg(); break;
			case 'pm_layout'     : pageLayoutEditor.show(); break;
			case 'pm_addpage'    : pagingController.addPage(); break;
			case 'pm_delpage'    : pagingController.menuDelete(); break;
		}

		if (!Event.isLeftClick(evt)) Event.stop(evt);
	},

	_routeMenuActions: function(id) {
        switch (id) {
			case 'pc_save':
				this.saveToServer();
				break;
            case 'pc_preview':
                Messaging.unlockUI();
                openWindow(previewURL + '?wait=1&title=' + loadData['book'], true);
                                break;
            case 'pc_publish':
                editor.showPublish();
				break;
            case 'pc_share':
                editor.showShare();
                new Ajax.Request(shareURL, {
                    'method': 'get',
                    'parameters': { 'message': 'print', 'title': loadData['book'], 'type': 'print' }
                });
				break;
            case 'pc_help':
                Messaging.lockUI();
                editor.showHelp();
                break;
            case 'pc_hint':
                hintsAreOn = !hintsAreOn;
                pageEditor.enableHints(hintsAreOn);
                break;
			case 'pc_exit'  : exit(); break;
		}
	},

	_saveNeededCallback: function(id, res) {
        if (!res) return;
        this.saveToServer(this._routeMenuActions.bind(this, id));
	},

	_topAction: function(evt) {
		var el = Event.element(evt);
		while (el && !el.id) el = $(el.parentNode);
		if (!el || !el.id.startsWith('pc_')) return;
		this.save(); // need to save before checking
		// check if we need to save a first time (needbook) or need to save before use (checksave)
        if ((el.hasClassName('needbook') && !loadData['book']) ||
            (el.hasClassName('checksave') && (loadData['starter'] || !Util.deepCompare(oldState, loadData)))) {
            Messaging.confirm('<h1>Please save your book first</h1>'
                + 'Please use the save button to save the book to the studio to continue.',
                this._saveNeededCallback.bind(this, el.id), 'save', 'cancel');
            return;
				}
        this._routeMenuActions(el.id);
		if (!Event.isLeftClick(evt)) Event.stop(evt);
	},

	saveSuccess: function(json) {
		if (loadData.cspInit) {
			loadData.cspInit = false;
		}
		if (json['name']) {
			loadData['starter'] = '';
			loadData['book'] = json['name'];
			returnURL = json['returnto'];
			forceReload = true;
		}
		saveFlag = true;
		if (pageEditor.saveCallback) {
			pageEditor.saveCallback();
			pageEditor.saveCallback = null;
		} else {
			oldState =  Util.deepCopy(loadData);
			Messaging.message('<h1>Save successful</h1><div>You may close the window to return to the Tikatok site if you want.</div>');
		}
	},

	saveFailure: function(text) {
		Messaging.message('<h1>Save Failed</h1><div>We\'re very sorry for the inconvenience, but your changes couldn\'t be saved.<br/><br/></div><div>Error message:<br/><div class="scrollable">' + text + '</div></div>');
	},

	remoteLogin: function() {
		$('loginOrReg').hide();
		new Ajax.Request(remoteLoginURL, {
			method: 'post',
			parameters: { 'nickname': $F('rlogin_nick'), 'password': $F('rlogin_pass') },

			onSuccess: function(transport) {
				var text = transport.responseText;
				var jsonIndex = text.indexOf('@@{');
				if (jsonIndex < 0) {
					Messaging.message('<h1>Error during Login</h1>' + text);
				} else {
					var json = text.substr(jsonIndex + 2).evalJSON();
					if (json.success) {
						library.forceReload();
						pageEditor.saveToServer(pageEditor.saveCallback);
					} else {
						Messaging.message('<h1>Could not log in</h1>' + json.message + '<br/><br/>CAUTION: your book has not been saved!');
                        pageEditor.saveCallback = null;
					}
				}
			},

			onFailure: function(transport) {
				Messaging.message('<h1>Error during Login</h1>' + text);
				pageEditor.saveCallback = null;
			}
		});
	},

	remoteRegister: function(evt) {
		Event.stop(evt);
		this.save();
		loadData.pages = pagingController._pages;
        editor.overrideExitWarning(true);
		$('save_data').value = $H(loadData).toJSON();

		setTimeout(function() { $('r_register').submit(); }, 100);
	},

	saveToServer: function(callback) {
		pageEditor.saveCallback = callback;
		Messaging.lockUI();

		this.save();
		loadData.pages = pagingController._pages;

		new Ajax.Request(saveURL, {
			method: 'post',
			parameters: { 'data' : $H(loadData).toJSON() },

			onSuccess: function(transport) {
				var text = transport.responseText;
				var jsonIndex = text.indexOf('@@{');
				if (jsonIndex < 0) {
					pageEditor.saveFailure(text);
				} else {
					var json = text.substr(jsonIndex + 2).evalJSON();
					if (!json.success) {
						pageEditor.saveFailure(json.message || text);
					} else {
						switch (json.success) {
							case 'done':
	                            pageEditor.saveSuccess(json);
	                            break;
	                        case 'nologin':
	                        	Util.wrapDialog($('loginOrReg'));
	                        	$('loginOrReg').show();
	                            break;
	                        case 'error':
	                        default:
	                            pageEditor.saveCallback = null;
	                            pageEditor.saveFailure(json.message || text);
	                            break;
						}
					}
				}
			},

			onFailure: function(transport) {
				pageEditor.saveFailure(transport.responseText);
			}
		});
	},

	updateTitle: function(s) {
		this.save();
        var oldTitle = Util.trim(loadData.title);
		loadData.title = s;
		$A(loadData.pages[0].elements).each(function(e) {
		    if (e.type == 'text_title' || (e.type == 'text' && e.content == oldTitle)) {
		        e.content = s;
		        e.type = 'text_title';
		    }
		});
        $A(loadData.pages[2].elements).each(function(e) {
            if (e.type == 'locked_text') {
                e.content = s + e.content.substr(e.content.indexOf('<br'));
            }
        });
        this.refocus();
        pagingController.updatePage(0, loadData.pages[0]);
        pagingController.updatePage(2, loadData.pages[2]);
	},

	save: function() {
		if (this._curPage == 0) {
			pagingController.updatePage(0, this.prepForJSON(this.right, 0));
		} else {
			pagingController.updatePage(this._curPage, this.prepForJSON(this.left, this._curPage));
			if (!this.right.hasClassName('disabled'))
				pagingController.updatePage(this._curPage+1, this.prepForJSON(this.right, this._curPage + 1));
		}
	},

	refocus: function() {
		while (this._curPage >= pagingController.numPages()) this._curPage--;
		this.focusPage(this._curPage, true);
	},

	focusPage: function(page, loadOnly) {
		this.editor.cancel();
		if (loadOnly !== true) this.save();
		this._curPage = page;
		if (this._curPage && page % 2 == 0) this._curPage--;
		$('page-left').update(editor.getPageNum(this._curPage ? this._curPage+1 : 0));
		$('page-right').update(editor.getPageNum(this._curPage ? this._curPage+2 : this._curPage+1));
		this._loadPages();
		this.updatePager();
		if (this._curPage) {
			this.instructions.hide();
			this.left.removeClassName('disabled');
			if (this._curPage == pagingController.numPages()-1) {
				this.right.addClassName('disabled');
				this.hint_r.hide();
				$('page-right').innerHTML = ''
			} else {
				this.right.removeClassName('disabled');
			}
		} else {
			this.left.addClassName('disabled');
			this.hint_l.hide();
			this.instructions.show();
			this.right.removeClassName('disabled');
		}
		this.setSelected(this._curPage == 0 ? this.right : this._curPage == page ? this.left : this.right);
	},

	_addCenteringGrid: function(el) {
		var d = $(document.createElement('div')).addClassName('vertgrid');
		el.appendChild(d);
		d = $(document.createElement('div')).addClassName('horizgrid');
		el.appendChild(d);
	},

	_loadPages: function() {
		Util.clearNode(this.left);
		Util.clearNode(this.right);

		if (this._curPage == 0) {
			this._addCenteringGrid(this.right);
			this._load(this.right, pagingController.getData(0), 'r');
		} else {
			this._addCenteringGrid(this.left);
			this._load(this.left, pagingController.getData(this._curPage), 'l');
			if (this._curPage != pagingController.numPages() - 1) {
			  this._addCenteringGrid(this.right);
				this._load(this.right, pagingController.getData(this._curPage+1), 'r');
			}
		}
	},

	_load: function(element, data, side) {
		this.selected = element;
		element.style.backgroundColor = data.bg || 'rgb(255,255,255)';
		if (cspMode || (data.hint && this._hintsEnabled)) {
			$('hint_' + side).show();
			$('hint_txt_' + side).update(data.hint);
		} else {
			$('hint_' + side).hide();
            $('hint_txt_' + side).update('');
		}
		$A(data.elements).each(function(item) {
			if (item.type == 'image') {
				pageEditor.addImage(item.file, item.rid, item);
			} else if (item.type == 'text' || item.type == 'text_title' || item.type == 'text_author') {
				pageEditor.addText(item);
			} else  if (item.type == 'place_image') {
				pageEditor.addImagePlaceholder(item);
			} else if (item.type == 'locked_text') {
				pageEditor.addLockedText(item);
			}
		});
	},

	previousPages: function() {
		if (!this._curPage) return;
		var p = this._curPage - 2;
		if (p < 0) p = 0;
		this.focusPage(p);
	},

	nextPages: function() {
		var n = pagingController.numPages()-1;
		if (n % 2 == 0) n--;
		if (this._curPage >= n) return;
		var p = this._curPage + ((this._curPage) ? 2 : 1);
		this.focusPage(p);
	},

	updatePager: function() {
		this.prev.setStyle({display: this._curPage ? 'block' : 'none'});
		this.next.setStyle({display: this._curPage + (this._curPage ? 2 : 1) >= pagingController.numPages() ? 'none' : 'block'});
	},

	setSelected: function(which) {
		var leftPage = (which == this.left);
		var pg = leftPage ? this._curPage : (this._curPage == 0 ? 0 : this._curPage+1);
		pagingController.setSelected(pg);
		var data = pagingController.getData(pg);
		this._pageMenu.setClass('locked', !data.moveable);
		if (!this.pmInit) {
			Position.clone(which, this._pageMenu, {offsetLeft: leftPage ? -this.PMWIDTH : this.left.getWidth(), setWidth: false, setHeight: false});
			this.pmInit = true;
			this.pmLastAt = which;
		} else {
			var wasLeft = (this.pmLastAt == this.left);
			this.pmLastAt = which;
			if (this.lastPMEffect) this.lastPMEffect.cancel();
			if (which == this.selected) {
				this._showMenu();
			} else {
				this.lastPMEffect = new Effect.Move(this._pageMenu, {
					x: wasLeft ? this.PMWIDTH : -this.PMWIDTH,
					duration: 0.75,
					afterFinish: this._showMenu.bind(this)
				});
			}
		}
		this.selected = which;
	},

	_showMenu: function() {
		var leftPage = (this.selected == this.left);
		var actualPage = (this._curPage == 0) ? 0 : (this._curPage + (leftPage ? 0 : 1));
		var data = pagingController.getData(actualPage);
		if (!data.editable) {
			this._pageMenu.hide();
			return;
		} else {
			this._pageMenu.show();
		}
		Position.clone(this.selected, this._pageMenu, {offsetLeft: leftPage ? 0 : (this.right.getWidth() - this.PMWIDTH), setWidth: false, setHeight: false});
		this._pageMenu.setClass('right', !leftPage);
		this.lastPMEffect = new Effect.Move(this._pageMenu, {x: leftPage ? -this.PMWIDTH : this.PMWIDTH, duration: 0.75});
	},

	resizeContainer: function() {
		this.editor.cancel();
		var vs = Util.getViewportSize();
		var sy = vs[1]-124;
		this.pageContainer.setStyle({height: sy + 'px'}, true);

		sy -= 60; // remove margins + space for horiz scroll
		if (pagingController.hasHints && this._hintsEnabled) sy -= 20;
		// subtract arrow space, page menu space, and menu space
		sx = this.pageContainer.getWidth() - 44*2 - 50*2 - 50*2;

		var a = this.aspect / 2; // because we show 2-page spreads most of the time
		var x, y;
		if (a <= 1) {
			y = sy;
			x = y / a;
			// try to fit the width
			if (x > sx) {
				x = sx;
				y = x * a;
			}
		} else {
			x = sx;
			y = x * a;
		}

		if (Math.abs(this._zoomFactor-1) < 0.05 && y < 390) {
			y = 390;
			x = y/a;
			if (!this._sizeWarned) {
				this._sizeWarned = true;
				Messaging.message('<h1>The browser window is too small</h1>To allow you to edit the book more comfortably, we have magnified the page.<br/><br/><em>Please right-click on the page to view different parts of it.</em>')
			}
		}

		x = this._zoomFactor * x * 0.5; // go back to page sizes now that sizing is done
		y = this._zoomFactor * y;

		this._size.w = x;
		this._size.h = y;

		var cw = this.pageContainer.getWidth() / 2;
		var factor = x/this.left.getWidth();
		var params = {width: x, height: y, left: cw - x - 5, top: (sy - y + 30)/2 };
		Util.p(this.left, params);
		Util.p(this.instructions, params);


		this.hint_l.style.left = (params.left + x - params.width) + 'px';
		this.hint_l.style.top = (params.top + y - 5) + 'px';
		this.hint_l.style.width = params.width + 'px';

		params.left -= 34 + 20 + this.PMWIDTH;
		params.width = 34;
		Util.p(this.prev, params);

		var t = $('page-left');
		t.style.left =  (params.left + this.PMWIDTH - 150) + 'px';
		t.style.top = (params.top + y - 40) + 'px';

		params.left = cw + x + 6 + 10 + this.PMWIDTH;
		Util.p(this.next, params);

		t = $('page-right');
		t.style.left=  params.left + 'px';
		t.style.top = (params.top + y - 40) + 'px';

		params.left = cw + 5;
		params.width = x;
		Util.p(this.right, params);

		this.hint_r.style.left = (params.left) + 'px';
		this.hint_r.style.top = (params.top + y - 5) + 'px';
        this.hint_r.style.width = params.width + 'px';

		this._zoomSizeUpdate(factor);

		this._offset.x *= factor;
		this._offset.y *= factor;
		this._updatePosition();
		if (this.selected) this.setSelected(this.selected);
	},

	dragBy: function(dx, dy) {
		this._offset.x -= dx;
		this._offset.y -= dy;
		this._snapBackSave = [dx, dy];
		this._updatePosition();
	},

	snapBack: function() {
		if (this._snapBackSave != null) {
			this._offset.x += this._snapBackSave[0];
			this._offset.y += this._snapBackSave[1];
			this._snapBackSave = null;
			this._updatePosition();
		}
	},

	_updatePosition: function() {
		this.pageDragger.style.left = this._offset.x + 'px';
		this.pageDragger.style.top = this._offset.y + 'px';
	},

	_zoomSizeUpdate: function(factor) {
		$H(this._items).each(function(pair) {
			pair.value.reposition();
		});
	},

	addLockedText: function(setupData) {
		this.editor.cancel();
		var lt = new PageLockedText(this, setupData);
		this._items[lt.id] = lt;
	},

	addImagePlaceholder: function(setupData) {
		this.editor.cancel();
		var iph = new ImagePlaceholder(this, setupData);
		this._items[iph.id] = iph;
	},

	addImage: function(iconFile, id, setupData) {
		this.editor.cancel();

		var img = new PageImage(iconFile, this, id, setupData);
		this._items[img.id] = img;
	},

	replacePlaceholder: function(placeholder, iconFile, id) {
		this.editor.cancel();

		var p = placeholder.div;

		this.setSelected(placeholder.page);
		var img = new PageImage(iconFile, this, id);
		this._items[img.id] = img;

		var w = parseFloat(p.style.width);
		var h = parseFloat(p.style.height);
		var left = parseFloat(p.style.left);
		var top  = parseFloat(p.style.top);
		var imageAspect = library.getAspect(id);
		var pAspect = w/h;
		if (imageAspect > pAspect) {
			// image is wider... set the height
			//top += (h - w / imageAspect) / 2;
			h = w / imageAspect;
		} else {
			// placeholder is wider... set the width
			//left += (w - h * imageAspect) / 2;
			w = h * imageAspect;
		}

		img.div.setStyle({'top': top + 'px', 'left': left + 'px', 'width': w + 'px', 'height': h + 'px'});

        var r = this._items.remove(p.id);
        if (r) r.cleanup();
		try { p.remove(); } catch (ex) { /* in case it's not attached yet */ }
	},

	addText: function(setupData) {
		this.editor.cancel();

		var txt = new PageText(this, setupData);
		this._items[txt.id] = txt;
	},

	getLastBg: function() {
		return this.bgEditor.getLastColor();
	},

	_dragStart: function(evt) {
		var el = $(Event.element(evt));
	    if (Event.isLeftClick(evt)) {
	        var page = el.hasClassName('editpage') ? el : el.up('.editpage');
            // right page clicked when 1 and 2 are showing --> title change
            if (page && page == this.right && this._curPage == 1) editor.showTitleChange();
	        if (page && page != this.selected && !page.hasClassName('disabled')) this.setSelected(page, true);
	    }

		if (el.nodeName.toLowerCase() == 'textarea') return;

		var p = el.hasClassName('positionable') ? el : $(el.up('.positionable'));
		if (!p) {
			if (!Event.isLeftClick(evt)) {
				this.lastx = Event.pointerX(evt);
				this.lasty = Event.pointerY(evt);
				this.drag = true;
				this.dragPage = true;
				this._snapBackSave = null;
				Event.stop(evt);
			}
			return;
		}

		if (!Event.isLeftClick(evt) || this.lastObject || p.hasClassName('locked_text')) {
			Event.stop(evt);
			return;
		}

		var pobj = this._items[p.id];
		if (!cspMode && pobj && (pobj.setupData.lock & 4)) { // locked
            Event.stop(evt);
            return;
		}

		this._isTextItem = p.hasClassName('text');
		if (this._isTextItem) {
			this.textBox = p.down('textarea');
		}
        if (pobj.forceFocus) pobj.forceFocus(true);

		this.target = p;
		var cls = el.className.replace(' drg', '');

		if (cls == 'ul') {
			this.mode = 1;
		} else if (cls == 'br') {
			this.mode = 2;
		} else if (cls == 'st') {
			this.mode = 3;
		} else if (cls == 'sb') {
			this.mode = 4;
		} else if (cls == 'sl') {
			this.mode = 5;
		} else if (cls == 'sr') {
			this.mode = 6;
		} else if (this._isTextItem) {
			this.mode = 8;
		} else {
			this.mode = 7;
		}
		this.lastx = Event.pointerX(evt);
		this.lasty = Event.pointerY(evt);

		this.drag = true;
		Event.stop(evt);
	},

	_getObject: function(positionable) {
		if (!positionable.hasClassName('positionable')) return;
		return this._items[positionable.id];
	},

	_dragStop: function(evt) {
        var el = $(Event.element(evt));
        var p = el.up('.positionable');

        if (p) {
	        var obj = this._getObject(p);
	        if (obj) obj.forceFocus(false);
        }

        if (!this.lastObject && el.hasClassName('delete')) {
            // if not in editor, we can delete it if delete is clicked
            // as long as it's not already being removed...
            if (this._items[p.id] && (cspMode || ((this._items[p.id].setupData.lock & 1) == 0))) {
                Messaging.confirm('<h1>Remove this item?</h1><div>The only way to restore a removed item is to recreate it or revert the entire book to the last saved version by exiting the editor without saving.</div>', function(result) {
                    if (result) {
                        $H(this._items).remove(p.id).cleanup();
                        Effect.Fade(p, { duration: 1, afterFinish: function(e) { if (e.element) e.element.remove(); } });
                    }
                }.bind(this),
                'remove', 'cancel');
            }
        } else if (el.hasClassName('edit')) {
            // if this is a text object, we can edit it
            this.lastObject = obj;
            if (this.lastObject) this.lastObject.lockFocus();
            // First blur the text field to prevent any issues there
            this.lastObject.textarea.blur();
            this.editor.edit(p);
        }

		if (!this.drag) return;

		this.drag = false;
		if (this.dragPage) {
			this.dragPage = false;
			Event.stop(evt);
			return false;
		}
		this._disallowDrag = (this.mode == 8);

		// clicking outside editor or the area itself
		if (!p && !this.editor.inEditor(el)) {
			this.editor.cancel();
		}
	},

	_pg: function(key) {
		return parseFloat(this.target.style[key]);
	},

	_dragUpdate: function(evt) {
		if (!this.drag) return;
		var x = Event.pointerX(evt);
		var y = Event.pointerY(evt);

		var dx = (x - this.lastx);
		var dy = (y - this.lasty);
		var moving = false;
		if (dx || dy) {
			if (this.dragPage) {
				this._offset.x += dx;
				this._offset.y += dy;
				this._updatePosition();
			} else {
				var s = [
					parseFloat(this.target.style.left),
					parseFloat(this.target.style.top),
					parseFloat(this.target.style.width),
					parseFloat(this.target.style.height)];

	 			switch (this.mode) {
					case 1:
						var aspect = s[3]/s[2];
						var lw = s[3];
						s[2] -= dx;
						s[3] = (aspect * s[2]);
						s[0] += dx;
						s[1] += lw - s[3];
						break;
					case 2:
						var aspect = s[3]/s[2];
						s[2] += dx;
						s[3] = (aspect * s[2]);
						break;
					case 3:
						s[1] += dy;
						s[3] -= dy;
						break;
					case 4:
						s[3] += dy;
						break;
					case 5:
						s[0] += dx;
						s[2] -= dx;
						break;
					case 6:
						s[2] += dx;
						break;
					case 7:
					case 8:
						moving = true;
						s[0] += dx;
						s[1] += dy;

						var p = this.target.up('li');
						if (!p) break;
						if (p == this.left) {
							if (!this._in(s, this.left))  {
								var t = s.clone();
								t[0] -= this._size.w + 10;
								if (this._in(t, this.right)) {
									s = t;
									this.right.appendChild(this.target);
								}
							}
						} else {
							if (!this._in(s, this.right)) {
								var t = s.clone();
								t[0] += this._size.w + 10;
								if (this._in(t, this.left)) {
									s = t;
									this.left.appendChild(this.target);
								}
							}
						}
						break;
				}

				if (this._isTextItem) {
					var margin = Math.floor(0.75 * this._size.w / loadData.width);
					if (s[0] + s[2] > this._size.w - margin ) {
						if (moving) {
							s[0] = this._size.w - margin - s[2];
							if (s[0] < margin) {
								s[2] -= margin - s[0];
								s[0] = margin;
							}
						} else {
							s[2] = this._size.w - margin - s[0];
						}
					}
					if (s[1] + s[3] > this._size.h - margin ) {
						if (moving) {
							s[1] = this._size.h - margin - s[3];
							if (s[1] < margin) {
								s[3] -= margin - s[1];
								s[1] = margin;
							}
						} else {
							s[3] = this._size.h - margin - s[1];
						}
					}
					if (s[0] < margin) {
						//s[2] -= (margin - s[0]);
						s[0] = margin;
					}
					if (s[1] < margin) {
						//s[3] -= (margin - s[1]);
						s[1] = margin;
					}
				}
				if (s[3] > 25 && s[2] > 25) {
					Util.p(this.target, Math.round(s[1]), Math.round(s[0]), Math.round(s[3]), Math.round(s[2]));
					if (Prototype.Browser.IE && this._isTextItem) {
						this.textBox.style.width = (Math.round(s[2]) - 2) + 'px';
						this.textBox.style.height = (Math.round(s[3]) - 4) + 'px';
					}
					var obj = this._getObject(this.target);
					if (obj) obj.updateData();
				}
			}
		}

		this.lastx = x;
		this.lasty = y;
		Event.stop(evt);
	},

	_in: function(a, p) {
		return (!p.hasClassName('disabled'))
			&& a[0] < this._size.w
			&& a[0]+a[2] >= 0
			&& a[1] < this._size.h
			&& a[1]+a[3] >= 0 ;
	},

	prepForJSON: function(element, pagePosition) {
		var w = element.getWidth();
		var h = element.getHeight();
		var array = $A();
		var textProps = ['color', 'fontWeight', 'fontStyle', 'textAlign'];

		this._items.each(function(arg) {
			var obj = arg[1];

			// check upstream to see if this item is part of the root we want
			if (!Util.ancestor(obj.div, element)) return;

            // reuse dimensions from setupData
			var d = {};
            d.width = obj.setupData.width;
			d.height = obj.setupData.height;
			d.left = obj.setupData.left;
			d.top = obj.setupData.top;

			// object is completely outside the element... ignore
			if (obj.moved) {
			    if (d.left > 1 || d.left + d.width < 0 || d.top > 1 || d.top + d.height < 0) return;
			}

            d.type = obj.type;
			switch (obj.type) {
				case 'text':
				case 'locked_text':
				case 'text_author':
				case 'text_title':
					d.lock = obj.setupData.lock || 0;
					d.hint = obj.setupData.hint || '';
					var target = null;
					if (obj.type.indexOf('locked') >= 0) {
						target = obj.div;
						d.content = obj.content;
                    } else {
						target = obj.textarea;
						d.content = $F(target).stripScripts().escapeHTML();
					}
					d.color = target.getStyle('color');
					d.fontSize = parseFloat(target.getStyle('fontSize')) / h;
					d.fontStyle = target.getStyle('fontStyle');
					if (!d.fontStyle) d.fontStyle = 'normal';
					d.fontWeight = target.getStyle('fontWeight');
					if (!d.fontWeight) d.fontWeight = 'normal';
					switch (d.fontWeight) { // if we have to use computed values, the names need to be inferred from spec values
						case '400':
							d.fontWeight = 'normal';
							break;
						case '700':
							d.fontWeight = 'bold';
					}
					d.textAlign = target.getStyle('textAlign');
					if (!d.textAlign) d.textAlign = 'left';
					break;
				case 'place_image':
					break;
				case 'image':
                    d.lock = obj.setupData.lock || 0;
					d.file = obj.imageFile;
					d.rid = arg[0].sub(/__.*$/, '');
					break;
			}
			array.push(d);
		});

		var page = pagingController.getData(pagePosition);
		page.bg = element.style.backgroundColor;
		page.elements = array;
        if (cspMode) {
    		page.hint = $('hint_txt_' + (pagePosition % 2 == 0 ? 'r' : 'l')).innerHTML;
        }

		return page;
	},

	getJSON: function(doLeft) {
		return this.prepForJSON(doLeft).toJSON();
	}
};

ScrollArea = Class.create();
ScrollArea.prototype = {
	_items: $A(),
	_pos: 0,
	_width: 10,
	_pad: 20,
	_lastEffect: null,
	_lock: false,


	doInit: function(id) {
		this.element = $(id);
		this.scroller = this.element.down('.scroller');
		this.container = this.scroller.down('ul');
		this.prev = this.element.down('.prev');
		this.next = this.prev.next('.next');

		Event.observe(this.prev, 'click', this.moveLeft.bindAsEventListener(this));
		Event.observe(this.next, 'click', this.moveRight.bindAsEventListener(this));
		Event.observe(this.container, 'mouseup', this._select.bindAsEventListener(this));
		Event.observe(window, 'resize', this.resize.bindAsEventListener(this));

		this.resize();
		this.fixWidth();
	},

	release: function() { this._lock = false; },

	_select: function(evt) {
		if (this._lock) return;
		this._lock = true;
		this.select(evt);
		Util.defer(this, this.release, 200);
	},

	checkLoad: function() {
		if ('function' == typeof this.loadMore &&
			this.container.getWidth() - this._pos < this.scroller.getWidth() * 1.25) {
			this.loadMore();
		}
	},

	resize: function() {
		this.scroller.style.width = Math.max(0, this.element.getWidth() - 68 - 30 - 36 - 10 - (Prototype.Browser.IE ? 20 : 0)) + 'px';
		this.checkLoad();
	},

	fixWidth: function() {
		this.container.style.width = ((this._width + this._pad) * (this._items.size()+1) + this._pad * 2) + 'px';
	},

	_m: function() {
		if (this._lastEffect) this._lastEffect.cancel();
		this._lastEffect = new Effect.Move(this.container,{ x: -this._pos, y: 0, mode: 'absolute', duration: 0.5});
	},

	moveRight: function() {
		var w = this.scroller.getWidth();
		if (this._pos + w/2 >= (this._width + this._pad) * this._items.size()) {
			return;
		}
		this._pos += w / 4;
		this._m();
		this.checkLoad();
	},

	moveLeft: function() {
		if (!this._pos) return;
		this._pos -= Math.floor(this.scroller.getWidth() / 4);
		if (this._pos < 0) this._pos = 0;
		this._m();
	},

	scrollToEnd: function() {
		this._pos = (this._width + this._pad) * this._elements.size() - Math.floor(this.scroller.getWidth() / 2);
		if (this._pos < 0) this._pos = 0;
		this._m();
	}
};

PagingController = Class.create();
Object.extend(PagingController.prototype, ScrollArea.prototype);
Object.extend(PagingController.prototype, {
	_elements: $A(),
	_selected: $A(),
	_pages: $A(),
	_selectEnable: true,
	_menuTarget: null,
	_numCoverAtEnd: 0,
	_menuHideHandler: null,
	hasHints: false,

	initialize: function(containerId, pageSize, pageData) {
		this._aspect = pageSize[0]/pageSize[1];
		this._width = Math.round(pageSize[0] * 82 / pageSize[1]);

		if (pageData) {
			this._pages = $A(pageData);
		} else {
			this.addPage();
		}

		this._items = this._pages;

		this.doInit(containerId);
		this.buildPages();

		//var addBtn = this.element.down('.extra');
		//Event.observe(addBtn, 'click', this.addPage.bindAsEventListener(this));

		var n = this._pages.size() - 1;
		while (n != this._numCoverAtEnd && this._pages[n-this._numCoverAtEnd].moveable == false) {
			this._numCoverAtEnd++;
		};

		for (var i = 0; i <= n; i++) if (this._pages[i].hint) { this.hasHints = true; break; };
	},

	show: function() { this.element.show(); this.resize(); },

	hide: function() { this.element.hide(); },

	numPages: function() { return this._pages.size(); },

	_findChildPosition: function(el) {
		for (var i = 0, n = this._elements.size(); i < n; i++) {
			if (this._elements[i] == el) {
				return i;
			}
		}
		return null;
	},

	menuDelete: function(el) {
		if (pageEditor._pageMenu.hasClassName('locked')) return;
		if (this.numPages() == 1) {
			Messaging.message('<h1>Cannot remove the last page</h1><div>You must have at least one page in the book. To remove this page, add another page first.</div>');
			return;
		}
		Messaging.confirm('<h1>Confirm Removal</h1><div>Are you sure you want to remove this page?</div>',
			this._finishDelete.bind(this, this._selected[0], 1),
			'remove', 'cancel');
	},

	menuDeleteFacing: function() {
		if (this.numPages() == 2) {
			Messaging.message('<h1>Cannot remove last 2 pages</h1><div>You must have at least one page in the book. To remove these pages, add another page first.</div>');
			return;
		}
		Messaging.confirm('<h1>Confirm Removal</h1><div>Are you sure you want to remove these pages?</div>',
			this._finishDelete.bind(this, this._selected[0], 2),
			'remove', 'cancel');
	},

	menuChangeBg: function() {
		var i = this._findChildPosition(this._selected[0]);
		pageEditor.showBgEditor(i);
	},

	_finishDelete: function(target, howMany, conf) {
		if (!conf) return;
		pageEditor.save();

		var i = this._findChildPosition(target);

		if (howMany > 2 || howMany < 1) return;
		if (howMany == 2) {
			if (i == 0) { // first page... no facing spread
				howMany--;
			} else {
				if (i % 2 == 0) i--; // if right page selected, move to left and delete 2
				if (this._pages.size() == i+1) howMany--; // unless it's an orphaned last page
			}
		}

		this._pages.splice(i, howMany);
		var parent = target.parentNode;
		for (var k = 0; k < howMany; k++)
			parent.removeChild(this._elements[i + k]);
		this._elements.splice(i, howMany);
		this.updatePageNum();
		pageEditor.refocus();
	},

	updatePageNum: function() {
		for (var i = 0; i < this._elements.length; i++) {
			this._elements[i].down('.number').update(editor.getPageNum(i + 1));
            this._elements[i].setClass('left', i % 2 != 0);
            this._elements[i].setClass('right', i % 2 == 0);
		}
	},

	select: function(evt) {
		if (!this._selectEnable || !pageEditor) return;

		var el = Event.element(evt);
		if (el.nodeName.toLowerCase() == 'img' && el.hasClassName('menu')) {
			this.showContextMenu(el);
			return;
		}

		el = el.up('li');
		var i = this._findChildPosition(el);
		if (i != null) pageEditor.focusPage(i);
	},

	setSelected: function(page, supressNext) {
		var el;
		while (el = this._selected.pop()) el.removeClassName('selected');
		var last = page + 1;//(supressNext ? 1 : 2);
		for (var which = page; which < last; which++) {
			if (which < 0 || which >= this._pages.size()) {
				continue;
			}
			el = $(this._elements[which]);
			el.addClassName('selected');
			this._selected.push(el);
		};
		pageLayoutEditor.setCurrent(page);
	},

	getData: function(which) {
		if (which < 0 || which >= this._pages.size()) return $A();
		return this._pages[which];
	},

	_makePage: function() {
		var pg = $(document.createElement('li'));
		var div = $(document.createElement('div'));
		div.addClassName('pagebody');
		var number = $(document.createElement('div'));
		div.makeClipping();
		pg.style.width = this._width + 'px';
		pg.appendChild(div);

		number.className = 'number';
		pg.appendChild(number);
		pg.addClassName((this._elements.size() % 2 == 0) ? 'right' : 'left');
		number.update(editor.getPageNum(this._elements.size() + 1));

		/*var menuMark = $(document.createElement('img'));
		menuMark.src = imageBase + 'editor/pagemenu.png';
		menuMark.addClassName('menu');
		pg.appendChild(menuMark);
		tooltips.addHelp(menuMark, 'Click here to change background', 'T');*/

		if (arguments.length > 0) {
			var pos = arguments[1];
			if (pos >= this._elements.length) {
		        this.container.appendChild(pg);
			} else {
				this.container.insertBefore(pg, this._elements[pos]);
			}
			this._elements.splice(pos, 0, pg);
			Util.defer(this, this.updatePageNum, 500);
		} else {
			this.container.appendChild(pg);
			this._elements.push(pg);
		}
		this.fixWidth();
		return div;
	},

	buildPages: function() {
		this._pages.each(function(page) {
			var div = this._makePage();
			this.updatePage(div, page);
		}.bind(this));
	},

	addPage: function() {
		if (pageEditor._pageMenu.hasClassName('locked')) return;
		pageEditor.save();
		var position = pageEditor.getSelectedPageNumber() + 1;
		if (position > this._pages.size() - this._numCoverAtEnd) {
			position = this._pages.size() - this._numCoverAtEnd;
		} else if (position < 2) {
			position = 2;
		}

		this._makePage(true, position);
		// !! magic type 3 = normal page
		var pg = $H({'bg' : pageEditor.getLastBg(), 'elements' : $A(), 'editable': true, 'moveable': true, 'type': 3, 'pos': -1, 'hint': ''});
		this._pages.splice(position, 0, pg);
		pageEditor.focusPage(position, true);
	},

	updatePage: function(which, pageData) {
		if ('number' == typeof which) {
			if (which < 0 || which >= this._elements.size()) return;
			var el = $(this._elements[which]).down('div');
			this._pages[which] = pageData;
		} else {
			el = $(which);
		}

		Util.clearNode(el);
		var h = el.getHeight();

		el.style.backgroundColor = pageData.bg || 'rgb(255,255,255)';

		$A(pageData.elements).each(function(a) {
			switch (a.type) {
				case 'image':
					var e = $(document.createElement('img'));
					e.src = a.file;//.sub(/\/([^\/]+)$/, function(match) { return '/small-' + match[1]; } );
					e.setStyle({left: (a.left*100) + '%', top: (a.top*100) + '%', width: (a.width*100) + '%', height: (a.height*100) + '%', position: 'absolute'}, true);
					el.appendChild(e);
					break;
				case 'text':
				case 'text_title':
				case 'text_author':
				case 'locked_text':
					var e = $(document.createElement('div'));
					if (!a.content) return;
					e.update(a.content.gsub('\n', '<br/>'));
					e.addClassName('text');
					e.setStyle({
						left: (a.left*100) + '%', top: (a.top*100) + '%', width: (a.width*100) + '%', height: (a.height*100) + '%', position: 'absolute',
						fontSize: (a.fontSize * h) + 'px',
						color: (a.color || '#000000'),
						fontWeight: (a.fontWeight || 'normal'),
						fontStyle: (a.fontStyle || 'normal'),
						textAlign: (a.textAlign || 'left'),
						overflow: 'hidden'
						}, true);
					el.appendChild(e);
					break;
			}
		});
		var cover = $(document.createElement('div'));
		cover.addClassName('cover');
		cover.update('&nbsp;');
		el.appendChild(cover);
	},

	moveElement: function(st, en) {
		if (en == st) return;
		this._pages.splice(en, 0, this._pages[st]);
		this._elements.splice(en, 0, this._elements[st]);
		if (en < st) {
			st++;
		}
		this._pages.splice(st, 1);
		this._elements.splice(st, 1);
		this.updatePageNum();
	},

	allowSelect: function(v) {
		this._selectEnable = v;
	},

	enablePageReorder: function() {
		sorter = new SortController(this.container, 'li', this.moveElement.bind(this), this.allowSelect.bind(this));
	}
});

SortController = Class.create();
SortController.prototype = {

	initialize: function(container, tag, moveCallback, allowSelectCallback) {
		this.container = $(container);
		this.tag = tag.toLowerCase();
		this.move = moveCallback || Prototype.emptyFunction;
		this.allowSelect = allowSelectCallback || Prototype.emptyFunction;
		container.cleanWhitespace();

		this.marker = $(document.createElement(this.tag));
		this.marker.id = 'marker';

		// Prevent text selection and dragging, which allows us to do
		// drags in IE without the images and such themselves getting
		// dragged, which in turn freezes the move because there are
		// no available drop targets... <sigh>
		this.container.ondrag = function () { return false; };
		this.container.onselectstart = function () { return false; };

		this.boundDrag = this.drag.bindAsEventListener(this);
		Event.observe(container, 'mousedown', this.start.bindAsEventListener(this));
		Event.observe(document, 'mouseup',   this.stop.bindAsEventListener(this));
	},

	findChildElement: function(el) {
		while (el && (el.nodeName.toLowerCase() != this.tag || el.parentNode != this.container)) {
			el = el.parentNode;
		}

		return el;
	},

	initIndex: function() {
		this.offsets = [];
		this.children = [];
		$A(this.container.childNodes).each(function(e) {
			if (e.nodeName.toLowerCase() == this.tag) {
				this.children.push(e);
				var t = [e.offsetLeft, e.offsetLeft + e.clientWidth];
				t[2] = (t[0] + t[1]) / 2;
				this.offsets.push(t);
			}
		}.bind(this));
	},

	start: function(evt) {
		if (!Event.isLeftClick(evt)) return;
		if (this.stated) {
			this.stop(evt); // prevent double-start
			return;
		}

		var el = this.findChildElement(Event.element(evt));
		if (!el) return;

		Event.stop(evt);
		//pageEditor.save();

		var i = 0 ;
		while (i < this.container.childNodes.length && this.container.childNodes[i] != el) i++;
		if (!pagingController.getData(i).moveable) return;
		this.startPosition = i;
		this.target = $(el);
		this.target.style.left = '0px';
		this.lx = Event.pointerX(evt);
		this.containerOffset = Position.page(this.container);
		this.finalPosition = null;
		this.initIndex();

		this.started = true;
		Event.observe(this.container, 'mousemove', this.boundDrag);
	},

	stop: function(evt) {
		this.allowSelect(true);
		if (!this.started) return;
		this.started = false;
		Event.stopObserving(this.container, 'mousemove', this.boundDrag);

		if (this.finalPosition !== null) {
			// snap it into position
			$(this.target).setStyle({opacity: 1, zIndex: 0}, true);
			this.target.style.left = '0px';
			this.container.removeChild(this.marker);

			if (this.finalPosition == this.startPosition || this.finalPosition+1 == this.startPosition) return;
			this.finalPosition++;
			if (this.finalPosition == this.children.length) {
				this.container.appendChild(this.target);
			} else {
				this.container.insertBefore(this.target, this.children[this.finalPosition]);
			}

			this.move(this.startPosition, this.finalPosition);
			pageEditor.focusPage(this.finalPosition < this.startPosition ? this.finalPosition : this.finalPosition - 1, true);
		} else {
			// snap it into position
			$(this.target).setStyle({opacity: 1, zIndex: 0}, true);
			this.target.style.left = '0px';
			if (this.marker.parentNode == this.container) {
				this.container.removeChild(this.marker);
			}
		}
		Event.stop(evt);
	},

	drag: function(evt) {
		if (!this.started) return;

		var cx = Event.pointerX(evt);
		var px = parseInt(this.target.style.left) + (cx - this.lx);
		this.lx = cx;
		cx -= this.containerOffset[0];
		this.target.style.left = px + 'px';
		for (var i = 0; i < this.offsets.length; i++) {
			if (cx >= this.offsets[i][0] && cx <= this.offsets[i][1]) {
				if (this.finalPosition === null) {
					pageEditor.save();
					this.allowSelect(false);
					this.target.setStyle({opacity: 0.5, zIndex: 5000, left: 0}, true);
					this.container.appendChild(this.marker);
				}

				this.finalPosition = (cx < this.offsets[i][2]) ? i-1 : i;
				if (this.finalPosition <= 0 || this.finalPosition >= this.children.length-1) break;
				this.marker.style.left = ((this.finalPosition < 0) ? 0 : (this.offsets[this.finalPosition][1])) + 'px';
				break;
			}
		}
	}
};

Messaging = {
	callback: null,
	_initDone: false,
	_locked: true,
	_lockFx: null,
	_dialog: null,
	_contentBox: null,

	show: function(message, callback) {
		Messaging.callback = callback || Prototype.emptyFunction;
		if (!Messaging._initDone) {
			Messaging._initDone = true;
			dlg = $(document.createElement('div'));
			this._dialog = dlg;

			dlg.id = 'dialog';
			dlg.addClassName('dialog');
			var container = $(document.createElement('div')).addClassName('content');
			container.appendChild($(document.createElement('div')).addClassName('t'));
			Messaging._contentBox = $(document.createElement('div'));
			container.appendChild(this._contentBox);
			dlg.appendChild(container);
			var t = document.createElement('div');
			t.style.clear = 'both';
			container.appendChild(t);
			var bottom = $(document.createElement('div')).addClassName('b');
			bottom.appendChild(document.createElement('div'));
			dlg.appendChild(bottom);

			document.body.appendChild(dlg);
			dlg.style.zIndex = 20000;
		}
		Messaging._dialog.show();
		Messaging._contentBox.update(message);
		Messaging.lockUI();
	},

	close: function() {
		Messaging.callback(arguments);
		Messaging.callback = Prototype.emptyFunction();
		var el = $('dialog');
		if (el) el.hide();
		Messaging.unlockUI();
	},

	message: function(message, callback) {
		message += '<div class="buttons">' +
				'<a href="#" class="dlgbutton" onclick="return Messaging.close()"><span>ok</span></a>' +
				'</div>';
		Messaging.show(message, callback);
	},

	finishConfirm: function(s) {
		Messaging.callback(s);
		Messaging._dialog.hide();
		Messaging.unlockUI();
		return false;
	},

	confirm: function(message, resultCallback, yesChoice, noChoice) {
		if (!yesChoice) yesChoice = 'ok';
		if (!noChoice)  noChoice  = 'cancel';
		message += '<div class="buttons">' +
				'<a href="#" class="dlgbutton" onclick="return Messaging.finishConfirm(false)"><span>' + noChoice + '</span></a>' +
				'<a href="#" class="dlgbutton" onclick="return Messaging.finishConfirm(true)"><span>' + yesChoice + '</span></a>' +
				'</div>';
		Messaging.show(message, resultCallback);
	},

	lockUI: function () {
		if (Messaging._locked) return;
		Messaging._locked = true;
		if (Messaging.lockFx) Messaging._lockFx.cancel();
		Messaging.lockFx = new Effect.Appear($('overlay'), {duration: 0.5, from: 0, to: 0.5});
	},

	unlockUI: function () {
		if (Messaging._lockFx) Messaging._lockFx.cancel();
		Messaging._lockFx = new Effect.Fade($('overlay'), {duration: 0.5, from: 0.5, to: 0 });
		Messaging._locked = false;
	}
};

BubbleHelp = Class.create();
BubbleHelp.prototype = {
	ids: $H(),
	curId: null,
	timer: null,
	curRealObj: null,

	initialize: function() {
		this.mouseOver = this._over.bindAsEventListener(this);
		this.mouseOut  = this._out.bindAsEventListener(this);
		this.element = $(document.createElement('div'));
		this.element.id = 'tooltip';
		document.body.appendChild(this.element);
		this.element.hide();
		this.element.setStyle({ opacity: 0.75 }, true);
	},

	addHelp: function(id, help, dir, bindContainer) {
		var el = $(id);
		if (!el) return;
		if (!el.id) {
			id = 'tid' + Util.getId();
			el.id = id;
		}
		this.ids[id] =  { 'help': help, 'dir': dir.toUpperCase() };
		if (!bindContainer) bindContainer = el;
		Event.observe(bindContainer, 'mouseover', this.mouseOver);
		Event.observe(bindContainer, 'mouseout', this.mouseOut);
		$A(bindContainer.childNodes).each(function(e) {
			Event.observe(e, 'mouseout', this.mouseOut);
		}.bind(this));
	},

	_over: function(evt) {
		var el = Event.element(evt);
		this.curRealObj = el;
		if (el && !(el.id && this.ids[el.id])) el = el.parentNode;
		if (!el) return;
		if (el.id && this.ids[el.id]) {
			this.curId = el.id;
			this.element.innerHTML = this.ids[el.id].help;
			var p = Position.page(el);
			var d = $(el).getDimensions();
			var showX = p[0] + d.width + 5;
			var showY = p[1] + d.height/2;
			switch (this.ids[el.id].dir) {
				case 'L':
					showX = p[0] - this.element.getWidth()  - 5;
					break;
				case 'T':
					showY = p[1] - this.element.getHeight() - 5;
					break;
				case 'B':
					showY = p[1] + d.height + 5;
					break;
			}
			this.element.style.left = showX + 'px';
			this.element.style.top  = showY + 'px';
			this.element.show();
		}
	},

	_out: function(evt) {
		var el = Event.element(evt);
		if (el == this.curRealObj || el.id != this.curId) { this.element.hide(); }
	}
};

Uploader = Class.create();
Object.extend(Uploader.prototype, {
	uploaded: true,
	flashHandle: null,
	idToBar: $H(),
	isStepMode: false,
	uploadedImages: false,
	uploadURL: '',
	uploadErrorURL: '',

	initialize: function(id, nameStem) {
		this.proxyNameStem = nameStem;
		this.element = $(id);
		Util.wrapDialog(this.element);
		var t = $('noUpload');
		if (t) {
			Util.wrapDialog(t);
			Event.observe('noupload_cancel', 'click', function(evt) {
				Event.stop(evt);
				t.hide();
				Messaging.unlockUI();
			});
			Event.observe('noupload_save', 'click', function(evt) {
				Event.stop(evt);
				t.hide();
				pageEditor.saveToServer();
			});
		}
		this.insertSWF();
	},

    insertSWF: function() {
        try {
	        var so = new SWFObject(uploaderURL, "flashProxyEmbed", "300", "270", "8", "#eaeaea");
	        so.addParam("align", "middle");
	        so.addParam("allowScriptAccess", "sameDomain");
	        so.write("uploadswf");
        } catch (x) {
            window.setTimeout(this.insertSWF.bind(this), 1000);
        }
    },

	show: function(stepMode) {
		this.isStepMode = stepMode ? true : false;
		//this.element.show();
		if (!this.uploadURL) {
			new Ajax.Request(tokenURL, {
				onSuccess: this._receivedToken.bindAsEventListener(this),
				onFailure: function() { uploader._showError('Unable to get upload security token. Please try to upload again.'); }
			});
		} else {
			this._init(0);
		}
		return true;
	},

	_showError: function(msg) {
		Messaging.message('<h1>Error Uploading Image</h1>' +
			'We are very sorry for the inconvenience.<br/>Error message: ' +
			msg +
			'<br/><br/>If the problem persists, do not hesistate to contact us from the help area of the site. You can also try to upload the image from the studio (under drawings).');
		this.cancel(true);
	},

	_receivedToken: function(transport) {
		if (transport.responseText == 'nologin') {
			Util.wrapDialog($('noUpload'));
			$('noUpload').show();
			return;
		}
		if (!(new RegExp('[0-9a-f]{32}', 'i').test(transport.responseText))) {
			this._showError('Invalid upload token returned by server. Please try to upload again.');
			return;
		}
		this.uploadURL = uploadURL + '/' + transport.responseText;
		this.uploadErrorURL = uploadErrorURL + transport.responseText;
		window.setTimeout(this._init.bind(this, 0), 500);
	},

	_init: function(count) {
		var flashObjectHandle = $(this.proxyNameStem + "Object");
		var flashEmbedHandle = $(this.proxyNameStem + "Embed");
		this.flashHandle = flashEmbedHandle ? flashEmbedHandle : flashObjectHandle;
		/*try {
			this.flashHandle.addItems();
		} catch (e) {
		*/
		if (!this.flashHandle) {
			if (count < 8) {
				window.setTimeout(this._init.bind(this, count+1), 500);
				return;
			}
			this._showError('The editor could not load the Adobe Flash Player plugin.');
		}
		this.element.show();
	},

	cancel: function(errored) {
		this.element.hide();
		if (!errored) {
			if (this.isStepMode) {
				Messaging.unlockUI();
			} else {
				library.uploadComplete(false);
			}
		}
	},

	flashError: function(msg) {
		if (msg.toLowerCase().indexOf('http error') >= 0) {
			new Ajax.Request(this.uploadErrorURL, {
				onComplete: function(transport) {
					uploader._showError(transport.responseText + '<br/>Please check the file and try to upload again.');
				}
			});
		} else {
			this._showError(msg);
		}
	},

	getUploadUrl: function() {
		return this.uploadURL;
	},

	sendComplete: function(n) {
		this.element.hide();
		this.uploadedImages = n > 0;
		library.uploadComplete(this.uploadedImages);
	}
});

Library2 = Class.create();
Object.extend(Library2.prototype, {
	_template: new Template('<div id="#{id}"><b class="overlay"></b><img class="icon" src="#{img}" /></div>'),
	_lastBuild: 0,
	_aspects: $H(),
	_callbackPlaceholder: null,
	_firstRun: true,
	_offsets: [],
	_selected: -1,
	_tabs: [],
	containers: null,

	initialize: function(id, tabcontainer) {
		this.element = $(id);
		Util.wrapDialog(id);
		this._items = $A();
		this.prev = $('lib_up');
		this.next = $('lib_down');
		this._hasMore = $A();

		var boundHandler = this._handleClick.bindAsEventListener(this);
		Event.observe($('lib_controls'), 'click', boundHandler);
		Event.observe($('lib_up'), 'click', boundHandler);
		Event.observe($('lib_down'), 'click', boundHandler);
		Event.observe($('libselect'), 'click', boundHandler);
		Event.observe($('lib_search'), 'click', this._doSearch.bind(this));
		Event.observe($('lib_reset'), 'click', this._resetSearch.bind(this));

        boundHandler = this.select.bindAsEventListener(this);
        var boxholder = this.element.down('.libcontainers');
        this._tabs = $$('#' + tabcontainer + ' li');
        this.containers = [];
		for (var i = 0; i < this._tabs.length; i++) {
            this.containers[i] = $(document.createElement('div')).addClassName('holder');
            boxholder.appendChild(this.containers[i]);
            Event.observe(this.containers[i], 'click', boundHandler);
			Event.observe(this._tabs[i], 'click', this._setTab.bind(this, i));
			this._offsets[i] = 0;
            this._hasMore[i] = false;
		}

        // in order of preference, spark-specific, set-specific, shared, and your own is default
    	this._setTab(this._tabs.length - 1);

		this.element.hide();
		$('lib_loader').hide();
	},

	forceReload: function() { this._setTab(this._selected, true); },

	_setTab: function(which, force) {
		if (!force && this._selected == which) return;
		this._selected = which;
		this.container = this.containers[which];
		this.container.show();
		for (var i = 0; i < this._tabs.length; i++) {
			if (i == which) {
				this._tabs[i].addClassName('selected');
			} else {
				this.containers[i].hide();
				this._tabs[i].removeClassName('selected');
			}
		}
		this.prev.setClass('disable', this._offsets[this._selected] == 0);
		this.next.setClass('disable', !this._hasMore[this._selected]);

        // if have class search, enable that
		if (this._tabs[which].hasClassName('search')) {
			$('lib_searchbox').show();
		} else {
			$('lib_searchbox').hide();
		}

		// if the container has nothing, force reload...
		if (force || !this.container.down('div')) this.build();
	},

	_handleClick: function(evt) {
		var el = Event.element(evt);
		while (el && !el.id) el = el.parentNode;
		if (!el) return;
		switch (el.id) {
			case 'lib_up':		this._scroll(-102); break;
			case 'lib_down':	this._scroll(102);	break;
			case 'lib_add':		this._upload();		break;
		 	case 'lib_cancel':	this.hide();		break;
			case 'lib_refresh':	this._doUpdate();	break;
		}
		Event.stop(evt);
	},

	uploadComplete: function(reload) {
		this.element.show();
		this.container.show();
		if (reload) {
			this._setTab(0, true);
		} else {
			this._setTab(0);
		}
	},

	_upload: function() {
		this.element.hide();
		uploader.show();
	},

	_scroll: function(dist) {
		if (this._offsets[this._selected] == 0 && dist < 0) return;
		if (this._items.length != 15 && dist > 0) return;
		this._offsets[this._selected] += (dist > 0) ? 15 : -15;
		if (this._offsets[this._selected] < 0) this._offsets[this._selected] = 0;
		if (this._offsets[this._selected] == 0) {
			this.prev.addClassName('disable');
		} else {
			this.prev.removeClassName('disable');
		}
		this.build();
	},

	_doUpdate: function() {
		this.build();
	},

	_finishUpdate: function(transport) {
		$('lib_loader').hide();
		eval('var data = ' + transport.responseText + ';');
		this._items = data;

        if (this._offsets[this._selected] == 0) {
            this.prev.addClassName('disable');
        } else {
            this.prev.removeClassName('disable');
        }
        if (this._items.length < 15) {
            this._hasMore[this._selected] = false;
            this.next.addClassName('disable');
        } else {
        	this._hasMore[this._selected] = true;
            this.next.removeClassName('disable');
        }
		this._doBuild(this.containers[this._selected], this._items);
	},

	_doBuild: function(container, data) {
		container.scrollTop = 0;
		var param = { base : imageBase };
		var s = '';
		for (var i = 0; i < data.length; i++) {
			var img = data[i];
			param.id = img[0];
			param.img = img[1];
			param.img = param.img.replace(/\/s_([^.]+).(jpg|png)$/, '/t_$1.jpg');
			s += this._template.evaluate(param);
			this._aspects[img[0]] = img[2];
		}
		if (data.length) {
			this.element.down('.noimages').hide();
		} else {
			this.element.down('.noimages').show();
		}

		container.update(s);
	},

	build: function() {
		this.containers[this._selected].show();
		for (var i = 0; i < this.containers.length; i++) {
			if (this._selected != i) this.containers[i].hide();
		}
		this.prev.addClassName('disable');
		this.next.addClassName('disable');
		$('lib_loader').show();
		new Ajax.Request(libUpdateURL, {
			method: 'get',
			parameters: { which: this._tabs[this._selected].id.replace('tab_', ''), offset: this._offsets[this._selected], ssid: loadData.ssid, sid: sparkGalleryId },
			onSuccess: this._finishUpdate.bindAsEventListener(this)
		});
	},

	_resetSearch: function() {
		$('lib_search_input').value = '';
		this._doSearch();
	},

	_doSearch: function() {
		$('lib_loader').show();
		new Ajax.Request(searchURL, {
			method: 'post',
			parameters: { 'keywords' : $('lib_search_input').value },

			onSuccess: function(transport, json) {
				$('lib_loader').hide();
				var json = ('' + transport.responseText).evalJSON();
				if (!json) {
					this._searchError('Incoming data was corrupt.');
					return;
				}
				this._comItems = json['data'];
				this._doBuild(this.comContainer, this._comItems);
				if (json['count'] == 0) {
					this.comContainer.update('<br/><br/><br/>Sorry. No matching drawing were found. Click reset to see all images.');
				}
			}.bind(this),

			onFailure: function(transport) {
				this._searchError(transport.responseText);
			}.bind(this)
		});
	},

	_searchError: function(msg) {
		$('lib_loader').hide();
		this._callbackPlaceholder = null;
		Messaging.message('<h1>Search Failed</h1><div>We\'re very sorry for the inconvenience, but there was a problem while conducting the search.<br/><br/></div><div>Error message:<br/>' + msg + '</div>');
	},

	getAspect: function(id) { return this._aspects[id] || 1; },

	show: function(callbackPlaceholder) {
		Messaging.lockUI();
		this._callbackPlaceholder = callbackPlaceholder ? callbackPlaceholder : null;
		this.element.show();
		if (this._firstRun) {
			this._firstRun = false;
			//Position.clone(this.container, this.element.down('.scroll'), { setLeft: false, setWidth: false });
			//this.build();
			//this._setTab(0);
		}
	},

	hide: function() {
		this.element.hide();
		this._callbackPlaceholder = null;
		Messaging.unlockUI();
	},

	select: function(evt) {
		var el = $(Event.element(evt));

		if (el.hasClassName('overlay')) {
			el = el.next('.icon') || el;
		}
		if (el.hasClassName('icon')) {
			var rid = el.up('div').id;
			var imageFile = '';
			for (var i = 0; i < this._items.length; i++) {
				if (this._items[i][0] == rid) {
					imageFile = this._items[i][1];
					break;
				}
			}
			if (this._callbackPlaceholder) {
				pageEditor.replacePlaceholder(this._callbackPlaceholder, imageFile, rid);
			} else {
				pageEditor.addImage(imageFile, rid);
			}
			this.hide();
		}
	},

	loadMore: function() {
	}
});

HelpDialog = Class.create();
HelpDialog.prototype = {
	shown: false,

	initialize: function() {
		this.element = $('starterHelp');
        Util.wrapDialog(this.element);
        Event.observe(this.element, 'click', this.handleClick.bindAsEventListener(this));
	},

	show: function() {
		Messaging.lockUI();
		this.element.show();
	},

	handleClick: function(evt) {
		this.element.hide();
		Messaging.unlockUI();
	}
};

TextProps2 = Class.create();
TextProps2.prototype = {
	storedState: $H({
		fontSize: 30/(72 * loadData.height),
		color: 'rgb(0,0,0)',
		fontWeight: 'normal',
		fontStyle: 'normal',
		textAlign: 'left'
	}),
	oldValues: $H(),
	previousValues: $H(),
	dragger: null,
	trackWidth: 108, // thumb size
	object: null,
	minSz: 16, maxSz: 108,
	snaps: 0,

	initialize: function() {
		this.editor = $('textprops');
		Util.wrapDialog(this.editor);
		Position.absolutize(this.editor);
		this.dragger = new Draggable('size_thumb', {
			snap: this.snapSize.bind(this)
		});

		Event.observe(this.editor, 'click', this._handleClick.bindAsEventListener(this));
		this.editor.hide();
	},

	snapSize: function(x, y) {
		y = 0;
		if (x < 0) x = 0;
		if (x > this.trackWidth - 8) x = this.trackWidth - 8;

        if (this.snaps > 0) {
        	var d = (this.trackWidth - 8) / (this.snaps - 1);
        	x = Math.round(x/d) * d;
        }

		this._changeSize(this.minSz + Math.round((x / this.trackWidth) * this.maxSz));
		return [x,y];
	},

	chooseSize: function(evt) {
		var e = Event.element(evt);
		if (!e.className) return;
		var w = e.className.substr(1, 1) * 1 - 1;
		this._changeSize(this.minSz + Math.round((w / 12) * this.maxSz));
		w = (w/12)*(this.trackWidth - 8);
		$('size_thumb').setStyle({'left': w + 'px'});
	},

	_handleClick: function(evt) {
		if (!this.target) return;

		var el = Event.element(evt);
		while (el.nodeName.toLowerCase() == 'span') el = el.parentNode;
		switch (el.id) {
			case 'size_chooser':
			    this.chooseSize(evt);
		        break;
			case 'text_save':
				this.save();
				break;
			case 'text_cancel':
				this.cancel();
				break;
			case 'text_match':
				this.matchLastStyle();
				break;
			case 'font_bold':
				this._toggleStyle('fontWeight', 'bold', 'font_bold');
				break;
			case 'text_up':
				this._updateSize(true);
				break;
			case 'text_down':
				this._updateSize(false);
				break;
			case 'font_italic':
				this._toggleStyle('fontStyle', 'italic', 'font_italic');
				break;
			case 'fmt_align_left':
			case 'fmt_align_center':
			case 'fmt_align_right':
				this._align(el.id.replace('fmt_align_', ''));
				break;
			default:
				if (el.hasClassName('box')) {
					this.recolor(el.style.backgroundColor);
				} else {
					return;
				}
				break;
		}
		Event.stop(evt);
	},

	matchLastStyle: function() {
		if (!this.previousValues.textAlign) return;

		this.previousValues.each(function(pair) {
			this.oldValues[pair.key] = this.storedState[pair.key] = this.previousValues[pair.key];
		}.bind(this));

		this.applyOldState(this.object, this.text);
		this.save()
	},

	_updateSize: function(up) {
		var thumb = $('size_thumb');
		var pos = parseInt(thumb.getStyle('left'));
		var d = 1;
		if (this.snaps > 1) {
            d = (this.trackWidth) / (this.snaps - 1);
		}
        pos += (up ? 1 : -1) * d;
		var np = this.snapSize(pos, 0);
		thumb.style.left = np[0] + 'px';
	},

	_toggleStyle: function(style, onState, buttonId) {
		if (this.text.style[style] == onState) {
			this.storedState[style] = this.text.style[style] = 'normal';
			$(buttonId).removeClassName('selected');
		} else {
			this.storedState[style] = this.text.style[style] = onState;
			$(buttonId).addClassName('selected');
		}
	},

	_align: function(alignment) {
		var b = $('fmt_align_' + this.text.style.textAlign);
		if (!b) b = $('fmt_align_left'); // if nothing set
		b.removeClassName('selected');
		this.storedState['textAlign'] = this.text.style.textAlign = alignment;
		b = $('fmt_align_' + alignment);
		if (b) b.addClassName('selected');
	},

	recolor: function(color) {
		if (this.target) {
			this.storedState['color'] = this.text.style.color = color;
		}
	},

	_changeSize: function(sizeInPoints) {
		if (!this.target) return;
		var szInPercent = sizeInPoints / (72 * loadData.height);
		this.object.updateFontSize(szInPercent);
		this.storedState.fontSize = szInPercent;
	},

	_saveOld: function() {
		this.storedState.each(function(pair) {
			if (pair && pair.key != 'fontSize') {
				this.storedState[pair.key] = this.oldValues[pair.key] = this.text.getStyle(pair.key);
			}
		}.bind(this));
		this.storedState.fontSize = this.oldValues.fontSize = this.object.setupData.fontSize;
	},

	_updateUI: function() {
		var buttons = $A(this.editor.getElementsByTagName('a')).each(function(e) {
			$(e).removeClassName('selected');
		});

		var btn = $('fmt_align_' + this.oldValues['textAlign']);
		if (btn) btn.addClassName('selected');
		if (this.oldValues['fontWeight'] == 'bold' || this.oldValues['fontWeight'] == 700) {
			$('font_bold').addClassName('selected');
		}
		if (this.oldValues['fontStyle'] == 'italic') {
			$('font_italic').addClassName('selected');
		}
		var pos = (Math.round(this.object.setupData.fontSize * 72 * loadData.height) - this.minSz) / this.maxSz;
		pos = 10 + (this.trackWidth-10) * pos;
		var newPos = this.snapSize(pos, 0);
		$('size_thumb').setStyle({left: newPos[0] + 'px', top: newPos[1] + 'px'});
		if (cspMode) {
            var lock = this.object.setupData.lock;
            $('lock_del').checked = lock & 1;
            $('lock_edit').checked = lock & 2;
            $('lock_move').checked = lock & 4;
            $('lock_style').checked = lock & 8;
            $('element_hint_txt').value = this.object.setupData.hint || '';
		}
	},

	ensureVisible: function() {
		var size = this.editor.getDimensions();
		var es = $('editorspace').getDimensions();
		var da = this.editor.up('.dragarea');
		var t = this.editor;
		var offx = this.editor.offsetLeft + da.offsetLeft + size.width;
		var offy = this.editor.offsetTop  + da.offsetTop  + size.height;
		offx = (offx > es.width ) ? offx - es.width  : 0;
		offy = (offy > es.height) ? offy - es.height : 0;
		pageEditor.dragBy(offx, offy);
	},

	edit: function(p, cnt) {
		this.text = p.down('textarea');
        if (!this.text) { return; }
		this.target = p;
		this.object = pageEditor._getObject(p);
		this._saveOld();
		this.editor.show();
		Position.clone(p, this.editor, {setWidth: false, setHeight: false, offsetTop: (parseInt(p.style.height) + 10), offsetLeft: 5 });
		this.sizeValue = parseInt(this.oldValues['fontSize']);
		this.recolor(this.oldValues['color']);
		this._updateUI();
		this.ensureVisible();
	},

	_hide: function() {
		pageEditor.snapBack();
		this.editor.hide();
		if (pageEditor.lastObject) pageEditor.lastObject.releaseFocus();
		pageEditor.lastObject = null;
		this.target = null;
	},

	cancel: function() {
		if (!this.target) return;
		if (!this.text) {
			this._hide();
			return;
		}
		$H(this.oldValues).each(function(pair) {
			if (pair.key != 'fontSize') {
				this.text.style[pair.key] = this.oldValues[pair.key];
			}
			this.storedState[pair.key] = this.oldValues[pair.key];
		}.bind(this));
		this.object.updateFontSize(this.oldValues.fontSize);
		this._hide();
	},

	getLockState: function() {
		var lock = 0;
        if ($('lock_del').checked)   lock |=  1;
        if ($('lock_edit').checked)  lock |=  2;
        if ($('lock_move').checked)  lock |=  4;
        if ($('lock_style').checked) lock |=  8;
        return lock;
	},

	save: function() {
		this.storedState.each(function(pair) {
			this.previousValues[pair.key] = this.storedState[pair.key];
		}.bind(this));
		if (cspMode) {
			this.object.setupData.lock = this.getLockState();
			this.object.setupData.hint = $('element_hint_txt').value;
		}
		this._hide();
	},

	inEditor: function(el) {
		return (el == this.editor) || $(el).up('#propedit');
	},

	applyOldState: function(object, target) {
		this.storedState.each(function(pair) {
			if (pair.key != 'fontSize') target.style[pair.key] = this.storedState[pair.key];
		}.bind(this));
		object.updateFontSize(this.storedState.fontSize);
	}
};

PageLayoutEditor = Class.create();
Object.extend(PageLayoutEditor.prototype, {
	formats: {
		'pl_txt':   { t: [{left: 0.1, width: 0.8, top: 0.1, height: 0.8}], i: [] },
		'pl_img_f':  { t: [], i: [{fill:true}] },
		'pl_img_n':  { t: [], i: [{fill:false, width: 0.8, height: 0.8, cx: 0.5, cy: 0.5 }] },
		'pl_mix_ob': { t: [{left: 0.1, width: 0.8, top: 0.65, height: 0.25}], i: [{fill:true}] },
		'pl_mix_nb': { t: [{left: 0.1, width: 0.8, top: 0.65, height: 0.25}], i: [{fill:false, width: 0.8, height: 0.5, cx: 0.5, cy: 0.35 }] }
	},
	currentPage: 0,

	initialize: function(element, pe) {
		this.pe = pe;
		this.element = $(element);
		Util.wrapDialog(this.element);
		this.buttons = this.element.down('.holder');
		this.message = this.element.down('.message');
		this.message.hide();
		Event.observe(this.buttons, 'click', this.handleButton.bindAsEventListener(this));
		Event.observe($('pl_cancel'), 'click', this.close.bind(this));
	},

	close: function() { this.element.hide(); },
	show: function() {
		if (pageEditor._pageMenu.hasClassName('locked')) return;
		this.element.show();
	},

	setCurrent: function(n) {
		this.currentPage = n;
		var data = pagingController.getData(n);
		if (!data || !data.moveable) {
			this.message.show();
			this.buttons.hide();
		} else {
			this.message.hide();
			this.buttons.show();
		}
	},

	handleButton: function(evt) {
		var el = Event.element(evt);
		Event.stop(evt);
		if (!this.currentPage) return;
		if (!el.id) el = el.up('div');
		if (!this.formats[el.id]) return;
		this.applyFormat(this.formats[el.id]);
	},

	applyFormat: function(fmt) {
		pageEditor.save();
		var tc = 0, ic = 0;
		var data = pagingController.getData(this.currentPage);
		var images = 0, text = 0, realText = 0, realImages = 0;
		$A(data.elements).each(function(e) {
			if (e.type == 'text') {
				text++;
				if (e.content && PageText.prototype._default != e.content) realText++;
			}
			if (/image/.test(e.type)) {
				if (!(/place/.test(e.type))) realImages++;
				images++;
			}
		});
		if ((realText > fmt.t.length || realImages > fmt.i.length)
			&& !confirm('Words or pictures that don\'t fit into the new layout will be left in place. Continue?')) return;

		while (text < fmt.t.length) {
			pageEditor.addText();
			text++;
		}
		while (images < fmt.i.length) {
			pageEditor.addImagePlaceholder();
			images++;
		}
		pageEditor.save();
		var data = pagingController.getData(this.currentPage);

		var aspect = loadData.width / loadData.height;
		var els = $A();
		$A(data.elements).each(function(e) {
			if (e.type == 'text') {
				if (text > fmt.t.length && PageText.prototype._default == e.content) { text--; return; }
				if (tc >= fmt.t.length) { els.push(e); return; }
				e.left = fmt.t[tc].left;
				e.top = fmt.t[tc].top;
				e.width = fmt.t[tc].width;
				e.height = fmt.t[tc].height;
			} else if (/image/.test(e.type)) {
				var isPlaceholder = /place/.test(e.type);
				if (images > fmt.i.length && isPlaceholder) { images--; return; }
				if (ic >= fmt.i.length) { els.push(e); return; }
				var imageAspect = e.width / e.height;
				if (fmt.i[ic].fill) {
					if (isPlaceholder) {
						e.width = e.height = 1;
					} else {
						if (imageAspect > aspect) {
							// wider than page
							e.height = 1;
							e.width = imageAspect;
						} else {
							e.width = 1;
							e.height = 1/imageAspect;
						}
					}
					e.left = 0.5 - e.width/2;
					e.top = 0.5 - e.height/2;
				} else {
					var curFormat = fmt.i[ic];
					var wantedAspect = curFormat.width / curFormat.height;
					if (isPlaceholder) {
						e.width = curFormat.width;
						e.height = curFormat.height;
					} else {
						if (imageAspect < wantedAspect) {
							// image is narrower than needed, so set height
							e.height = curFormat.height;
							e.width = imageAspect * e.height;
						} else {
							// otherwise, set width
							e.width = curFormat.width;
							e.height = e.width/imageAspect;
						}
					}
					e.left = curFormat.cx - e.width/2;
					e.top = curFormat.cy - e.height/2;
				}
				ic++;
			}
			els.push(e);
		});
		data.elements = els;
		pagingController.updatePage(this.currentPage, data);
		pageEditor.focusPage(this.currentPage, true);
		this.close();
	}
});

PublishDialog = Class.create();
PublishDialog.prototype = {
	initialize: function() {
        this.dlg = $('publishDlg');
        Util.wrapDialog(this.dlg);
        Event.observe(this.dlg, 'click', this.handleClick.bindAsEventListener(this));
	},

    _doUpdate: function(data) {
        this.data = data;
        var rt = new Template('<tr id="row_#{id}" class="#{cls}"><td>#{name}</td><td class="qty">#{qty}</td><td><b class="plus"></b><b class="minus"></b></td><td>&#215;</td><td class="price">$ #{price} ea.</td><td>=</td><td class="tot">$ #{tot}</td></tr>');
        var html = '';
        $A(data).each(function(p, i) {
    	    p.qty = (i == 0) ? 1 : 0;
    	    p.tot = (p.qty * p.price).toFixed(2);
    	    p.cls = (p.limit) ? 'limit' : '';
    	    html += rt.evaluate(p);
	    });
        html += '<tr id="tot_row"><td colspan="6">Total</td><td class="tot" id="tot_amt">$ ' + data[0].price + '</td></tr>';
        $('publishTableDiv').update('<table border="0" cellspacing="0" cellpadding="0">' + html + '</table>');
        this.dlg.show();
    },

    show: function() {
    	Messaging.lockUI();
        new Ajax.Request(priceURL + '?title=' + loadData['book'], {
            method: 'post',

            onSuccess: function(transport) {
                var text = transport.responseText;
                var jsonIndex = text.indexOf('@@[');
                if (jsonIndex < 0) {
                    Messaging.message('<h1>Error</h1>Please save your book and reload the editor!');
                } else {
                    var json = text.substr(jsonIndex + 2).evalJSON();
                    this._doUpdate(json);
                }
            }.bind(this),

            onFailure: function(transport) {
                Messaging.message('<h1>Error</h1>Please save your book and reload the editor!');
            }
        });
    },

    _updateTotal: function() {
    	var t = 0;
    	this.data.each(function(e) { t += parseFloat(e.price) * parseInt(e.qty); });
    	$('tot_amt').update('$ ' + t.toFixed(2));
    },

    _updateAmt: function(e, up) {
        var id = e.up('tr').id.replace('row_', '');
        this.data.each(function(item) {
        	if (item.id != id) return;
            item.qty += up ? 1 : -1;
            if (item.qty < 0) item.qty = 0;
            if (item.limit && item.qty > item.limit) item.qty = item.limit;
            e.up('tr').down('.qty').update(item.qty);
            e.up('tr').down('.tot').update('$ ' + (item.qty * item.price).toFixed(2));
            this._updateTotal();
        }.bind(this));
    },

    _doAdd: function() {
        editor.overrideExitWarning(true);
        var t = $A();
        this.data.each(function(item) { if (item.qty) t.push({'id': item.id, 'qty': item.qty}); });
        if (!t.length) {
        	this.dlg.hide();
        	Messaging.message('<h1>Nothing in Cart!</h1>You didn\'t add anything to the cart. Please add some items in order to publish your book.');
        	return;
        }
        var form = $('publishForm');
        form.down('input[name=title]').value = loadData['book'];
        form.down('input[name=order]').value = t.toJSON();
        form.submit();
    },

    handleClick: function(evt) {
    	Event.stop(evt);
    	var e = Event.element(evt);
    	if (e.hasClassName('plus')) {
    		this._updateAmt(e, true);
    	} else if (e.hasClassName('minus')) {
    		this._updateAmt(e, false);
    	} else {
    		while (e && !e.id) e = e.up();
    		if (!e) return;
    		switch (e.id) {
    			case 'pub_cancel':
    			    this.dlg.hide();
    			    Messaging.unlockUI();
    		        break;
		        case 'pub_add':
		            this._doAdd();
		            break;
    		}
    	}
    }
};

ShareDialog = Class.create();
ShareDialog.prototype = {
	initialize: function() {
	    this.dlg = $('shareDlg');
	    Util.wrapDialog(this.dlg);
        Event.observe(this.dlg, 'click', this.handleClick.bindAsEventListener(this));
	},

	requestPrint: function() {
		new Ajax.Request(shareURL, {
		    'method': 'get',
		    'parameters': { 'message': 'print', 'title': loadData['book'], 'type': 'print' }
	    });
        Messaging.message('<h1>Congratulations!</h1>You&rsquo;re on your way to becoming a published author!<br/><br/>An email has been sent to your parents asking them to buy your book.');
	},

	requestGift: function() {
		var p = { 'message': 'gift', 'title': loadData['book'] };
		var txt = $('share_msg').value;
		if (txt == 'Include a message?') txt = '';
		p.text = txt;
        new Ajax.Request(shareURL, {
            'method': 'post',
            'parameters': p
        });
		Messaging.message('<h1>Great!</h1>We have sent your gift.');
	},

	show: function() {
		Messaging.lockUI();
		this.dlg.show();
	},

	_clearPress: function() { $$('#' + this.dlg.id + ' .radio.pressed').invoke('removeClassName', 'pressed'); },

	handleClick: function(evt) {
		Event.stop(evt);
		var e = Event.element(evt);
		while (e && !e.id) e = e.up();
		if (!e) return;
		switch (e.id) {
			case 'share_msg':
				if (e.value == 'Include a message?') e.value = '';
				e.style.color = 'black';
				break;
			case 'share_buy':
				this._clearPress();
				e.down('.radio').addClassName('pressed');
				$('share_msg').hide();
				break;
			case 'share_gift':
				this._clearPress();
				e.down('.radio').addClassName('pressed');
				$('share_msg').show();
				break;
			case 'share_cancel':
				this.dlg.hide();
				Messaging.unlockUI();
				break;
			case 'share_go':
			    this.requestPrint();
			    this.dlg.hide();
			    break;
		}
	}
};

TitleDialog = Class.create();
TitleDialog.prototype = {
	initialize: function() {
		this.dlg = Util.wrapDialog('titleDlg');
		Event.observe(this.dlg, 'click', this.handleClick.bindAsEventListener(this));
        Event.observe('new_title', 'change', function() { if ($F('new_title')) $('new_title_error').hide(); else $('new_title_error').show(); });
	},

	show: function() {
	    $('new_title').value = loadData.title;
	    Messaging.lockUI();
        this.dlg.show();
        $('new_title').focus();
    },

    hide: function() { this.dlg.hide(); Messaging.unlockUI(); },

    handleClick: function(evt) {
        var e = Event.element(evt);
        while (e && !e.id) e = e.up();
        if (!e) return;
    	switch (e.id) {
    		case 'title_cancel': this.hide(); break;
    		case 'title_go':
                var title = Util.trim($('new_title').value);
                if (!title) {
                	$('new_title_error').show();
                	return;
                }
                this.hide();
    		    pageEditor.updateTitle(title);
    		    break;
    	}
    }
};

Util = {
    _id: 1,

    trim: function(s) {
        return s.replace(/^\s+|\s+$/g, '');
    },

    /**
     * returns true in a is an ancestor of c
     */
	ancestor: function(c, a) {
	    return $(c).ancestors().member(a);
	},

	getId: function() { return 'pageitem' + (++Util._id); },

	defer: function(obj, fcn, delay) {
	    window.setTimeout(fcn.bind(obj), delay || 10);
	},

	/**
	 * Sets the position of e to values in p. Values are set as px.
	 * Don's pass $A() or $H() objects to this.
	 */
	p: function(e, t, l, h, w) {
	    if ('object' != typeof t)  t = { 'top': t, 'left': l, 'width': w, 'height': h };
	    for (s in t) e.style[s] = t[s] + 'px';
	},

	getViewportSize: function() {
	    var res = [0, 0];
	    if ('undefined' != typeof window.innerWidth) {
	        // standards compliant browsers
	        res[0] = window.innerWidth;
	        res[1] = window.innerHeight;
	    } else {
	        // IE6 uses either documentElement or document's body
	        var d = document.documentElement;
	        if ('undefined' == typeof document.documentElement) {
	            d = document.getElementsByTagName('body')[0];
	        }
	        res[0] = d.clientWidth;
	        res[1] = d.clientHeight;
	    }

	    return res;
	},

	clearNode: function(el) {
	    $(el).immediateDescendants().each(function(child) { el.removeChild(child); });
	    $(el).setStyle({background: ''});
	},

	fixNodeForIE6: function(e) {
	    if (document.all && !window.XMLHttpRequest) {
	        $A(e.getElementsByTagName('img')).each(function(i) {
	            if (!i.src.match(/\.png$/)) return;
	            var s = i.src;
	            i.src = imageBase + 'blank.gif';
	            i.runtimeStyle.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + s + '\', sizingMethod=\'scale\')';
	        });
	    }
	},

	deepCopy: function(o) {
	    var objectClone = new Object();
	    for (var property in o)
	        if (typeof o[property] == 'object')
	            objectClone[property] = Util.deepCopy(o[property]);
	        else
	            objectClone[property] = o[property];
	    return objectClone;
	},

	deepCompare: function(a, b) {
	    try {
	        var keys = {};
	        for (var prop in b) if (typeof(b[prop]) != 'function') keys[prop] = true;
	        for (var prop in a) {
	            var type = typeof(a[prop]);
	            if (type != typeof(b[prop])) return false;
	            if (type == 'function') {
	               continue;
	            } else if (type == 'number') {
	                if (Math.abs(a[prop] - b[prop]) > 0.01) return false;
	            } else if (type == 'object') {
	                if (!Util.deepCompare(a[prop], b[prop])) return false;
	            } else if (a[prop] != b[prop]) {
	                if (a[prop].indexOf && a[prop].indexOf('rgb') >= 0) {
	                    if (a[prop].gsub(/ /, '') != b[prop].gsub(/ /, '')) return false;
	                } else return false;
	            }
	            if (keys[prop]) keys[prop] = false;
	        }
	        for (var prop in keys) if (keys[prop] === true) return false;
	    } catch (e) { return false; }
	    return true;
	},

	wrapDialog: function(dlg) {
	    dlg = $(dlg);
	    if (dlg.hasClassName('dlg_wrapped')) return dlg;
	    dlg.addClassName('dialog').addClassName('dlg_wrapped');
	    var container = $(document.createElement('div')).addClassName('content');
	    container.appendChild($(document.createElement('div')).addClassName('t'));
	    while (dlg.firstChild) container.appendChild(dlg.removeChild(dlg.firstChild));
	    dlg.appendChild(container);
	    var t = $(document.createElement('div')).addClassName('clear');
	    container.appendChild(t);
	    var bottom = $(document.createElement('div')).addClassName('b');
	    bottom.appendChild(document.createElement('div'));
	    dlg.appendChild(bottom);
	    return dlg;
	},

	centerDialog: function(dlg) {
	    var vs = Util.getViewportSize();
	    dlg = $(dlg);
	    var dim = dlg.getDimensions();
	    dlg.setStyle({left: (vs[0] - dim.width)/2 + 'px', top: (vs[1] - dim.height)/2 + 'px'});
	}
};

Editor = Class.create();
Editor.prototype = {
    initialize: function() {
        this._overrideExitWarning = false;
        window.onbeforeunload = this.exitHandler.bind(this);
    },

    getPageNum: function(n) {
        if (n == 0) return '';
        if (n == (pagingController ? pagingController._elements.length : loadData.pages.length)) return 'back';
        var t = [ 'front', '', 'title', 'copyright' ];
        if (n <= t.length) return t[n-1];
        return n-4;
    },

    showPublish: function() {
    	//if (!this.publishDlg) this.publishDlg = new PublishDialog();
    	//this.publishDlg.show();
    	this.overrideExitWarning(true);
    	document.location.href = publishURL + '/' + loadData['book'];
	},

    showShare: function() {
    	if (!this.shareDlg) this.shareDlg = new ShareDialog();
    	this.shareDlg.show();
	},

	showTitleChange: function() {
		if (!this.titleDlg) this.titleDlg = new TitleDialog();
		this.titleDlg.show();
	},

    showHelp: function() {
        if (!this.helpDlg) this.helpDlg = new HelpDialog();
        this.helpDlg.show();
    },

    overrideExitWarning: function(permanent) {
    	if (permanent) window.onbeforeunload = function() {};
    	this._overrideExitWarning = true;
	},

    exitHandler: function() {
	    pageEditor.save();
	    if (!this._overrideExitWarning && !Util.deepCompare(oldState, loadData))
	        return 'Your book has unsaved changes. These changes will be lost if you navigate away from this page.';
	    this._overrideExitWarning = false;
    }
};

var editor = new Editor();
var pageEditor = null;
var library = null;
var sortable = null;
var pagingController = null;
var pageSize = [loadData.width, loadData.height];
var lockFx = null;
var tooltips = null;
var uploader = null;
var hintsAreOn = true;
var saveFlag = false;
var forceReload = false;
var oldState = null;
var pageLayoutEditor = null;

function bindUI() {
    cspMode = $('__csp');
    if (cspMode) loadData.cspMode = true;
	tooltips = new BubbleHelp();
	pageLayoutEditor = new PageLayoutEditor('pleditor');
	pagingController = new PagingController('pagelist', pageSize, loadData.pages);
	library = new Library2('library2', 'libselect', 'libcontainers');
	pageEditor = new PageEditor('leftpage', 'rightpage', 'editorspace', pageSize);
	pageEditor.focusPage(0, true);
	pagingController.enablePageReorder();
	oldState = Util.deepCopy(loadData);

	tooltips.addHelp('pc_save', 'Save your changes', 'R');
	tooltips.addHelp('pc_publish', 'Purchase printed copies of this book', 'R');
    tooltips.addHelp('pc_share', 'Ask your parent to buy this book', 'R');
	tooltips.addHelp('pc_preview', 'Show how this book will look in the print version', 'R');
	tooltips.addHelp('pc_exit', 'Exit the editor', 'L');
    tooltips.addHelp('pc_hint', 'Show or hide the writing hints below each page', 'L');
    tooltips.addHelp('pc_help', 'Get help using the editor', 'L');

	tooltips.addHelp('pm_layout', 'Pick a layout for this page', 'R');
	tooltips.addHelp('pm_bgcolor', 'Change the color of this page', 'R');
	tooltips.addHelp('pm_addtext', 'Add an area for entering words', 'R');
	tooltips.addHelp('pm_addimage', 'Add a picture from your gallery', 'R');
	tooltips.addHelp('pm_addpage', 'Add a page after this one', 'R');
	tooltips.addHelp('pm_delpage', 'Remove this page', 'R');

	$$('form').each(function(form) {
		if (!form.action) {
			Event.observe(form, 'submit', function(evt) { Event.stop(evt); });
		}
	});
};

function openWindow(url, noSize) {
	if (!noSize) {
	var t = { w: 400, h: 700 };
	var params  = 'width=' + t.w;
	params += ',height=' + t.h;
	params += ',top=50,left=50';
	params += ',resizable=yes';
	} else {
		var params = 'top=50,left=50,resizable=yes';
	}
	try { newwin = window.open(url, 'windowname' + Math.floor(Math.random()*10000), params);
	   if (newwin && newwin.focus) newwin.focus(); } catch (ex) {}
	return false;
}

function showCopyrightHelp() {
	var htmlBase = imageBase.replace('images/', '') + 'help';
	Messaging.message(
		'<h1>What is Copyright?</h1>'
		+ 'A copyright is a legal statement about who owns a creative work, like this book and the illustrations inside it, and what other people can do with it. Authors use copyrights to protect their work. It is a way of saying, "I wrote this and you should respect the work I put into it." Authors should respect the work of other authors, which means only copying something if you have the author\'s permission, and giving them credit when you do.'
		+ '<br/><br/>'
		+ 'You hold the copyright for everything you create on Tikatok, and you grant Tikatok the right to share your creation through the website and to print and sell books in our store. '
		+ 'For more information about your agreement with Tikatok, view our <a target="_blank" href="' + htmlBase + '/userAgreement.html">Terms of Service</a>.');
	return false;
}

function showBioHelp() {
	var htmlBase = imageBase.replace('images/', '') + 'help';
	Messaging.message(
		'<h1>Protecting Your Privacy</h1>'
		+ 'The back cover is only for printed books that your parents order. It is invisible on the site, except in the book editor, so you can share your book online without sharing your personal information and photo. Even though we make it invisible, you should still be careful about the information you put here. Never put your home address or phone number in your "about the author." Remember, authors need to protect their privacy!'
		+ '<br/><br/>'
		+ 'For more information about protecting your privacy, view our <a target="_blank" href="' + htmlBase + '/privacy.html">Privacy Policy</a>.');
	return false;
}

function completeBind() {
	uploader = new Uploader('uploader', 'flashProxy');
	$('preloader').remove();
	Messaging.unlockUI();
};

function exit(evt, force) {
	if (!force) {
		pageEditor.save();
		if (!Util.deepCompare(oldState, loadData)) {
			var dlg = $('exitConfirm');
			if (!dlg.hasClassName('dialog')) {
				Util.wrapDialog(dlg);
				Event.observe('exit_no', 'click', function() { exit(null, true); });
				Event.observe('exit_cancel', 'click', function() {
					Messaging.unlockUI();
					dlg.hide();
				});
				Event.observe('exit_yes', 'click', function() {
					dlg.hide();
					pageEditor.saveToServer(function() { exit(null, true); });
				});
			}
			Messaging.lockUI();
			dlg.show();
			return;
		}
	}

	editor.overrideExitWarning();
	var doneClosing = false;
	try {
		if (!forceReload && window.parent && 'function' == typeof(window.parent.closeEditor)) {
			window.parent.closeEditor(saveFlag);
			doneClosing = true;
		}
	} catch (_exception1) {}

	if (!doneClosing) {
		var url = returnURL;
		if (uploader.uploadedImages) {
			url += '/uploaded/true';
		}
		try {
			if (window.parent && window.parent.document) {
				window.parent.document.location = url;
				doneClosing = true;
			}
		} catch (_exception2) {}

		if (!doneClosing) {
			document.location = url;
		}
	}
};

Event.observe(window, 'load', completeBind); // we wait for the images and binaries to load
Event.onDOMReady(bindUI); // but try to bind everything soon as the HTML is loaded
