// 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 drg"></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.gsub(/\/images\//, imageBase));
        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 +
        '</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 +
        '</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>'),

    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);
        if (typeof VKI_attach == 'function') {
            VKI_attach(this.textarea);
        }
        this.textarea.addClassName('pagetext');
        this.div.addClassName('text');
        if (setupData) {
            var text = setupData.content ? setupData.content : this._default;
            text = text.unescapeHTML();
            this.textarea.value = text;
            if (setupData.type == 'text_title') {
                this.originalTitle = text;
            }
            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 {
            // @todo update title on page change only, not here
            // @hack if virtual keyboard in use, disable this capability...
            if (typeof VKI_attach != 'function') {
                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');
        this.binding = $$('#binding_top, #binding_btm');
        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>' + transport.responseText);
                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).replace(/ /, '<br/>'));
        $('page-right').update(editor.getPageNum(this._curPage ? this._curPage+2 : this._curPage+1).replace(/ /, '<br/>'));
        this._loadPages();
        this.updatePager();
        this.binding.invoke((cspMode || loadData.type != 2) ? 'hide' : 'show');
        if (this._curPage) {
            this.instructions.hide();
            this.left.removeClassName('disabled');
            if (this._curPage == pagingController.numPages()-1) {
                this.right.addClassName('disabled');
                this.hint_r.hide();
                this.binding[1].hide();
                $('page-right').innerHTML = ''
            } else {
                this.right.removeClassName('disabled');
            }
        } else {
            this.left.addClassName('disabled');
            this.hint_l.hide();
            this.binding[0].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 {
            if (loadData.type != 2 || cspMode || which == this.left || this._curPage == 0) {
                var wasLeft = (this.pmLastAt == this.left);
                this.pmLastAt = which;
                if (this.lastPMEffect) this.lastPMEffect.cancel();
                if (which == this.selected) {
                    this._showMenu();
                } else {
                    this._pageMenu.hide();
                    this.lastPMEffect = new Effect.Move(this._pageMenu, {
                        x: wasLeft ? this.PMWIDTH : -this.PMWIDTH,
                        duration: 0.75,
                        afterFinish: this._showMenu.bind(this)
                    });
                }
            } else {
                this._pageMenu.hide();
            }
        }
        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;
        var message = $('global_message');
        if (message.visible()) sy -= message.getHeight();
        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;
            // try to fit height
            if (y > sy) {
                y = sy;
                x = y / a;
            }
        }

        // if zoom is ~1, check sizing
        if (Math.abs(this._zoomFactor-1) < 0.05) {
            var limit =  390;
            // change limit for calendars
            if (loadData.type == 2) limit = 250;
            if (y < limit) {
                y = limit;
                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.setStyle({'left': (params.left + x - params.width) + 'px', 'top': (params.top + y - 5) + 'px', 'width': params.width + 'px'});
        this.binding[0].setStyle({'left': (params.left + x - params.width) + 'px', 'top': (params.top + y - 9) + 'px', '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.setStyle({'left': (params.left) + 'px', 'top': (params.top + y - 5) + 'px', 'width': params.width + 'px' });
        this.binding[1].setStyle({'left': (params.left) + 'px', 'top': (params.top - 9) + 'px', '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'});
        img.updateData();

        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');
            // if not calendar, right page clicked when 1 and 2 are showing --> title change
            if (loadData.type != 2 && 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.parentNode) 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;
                        var text = $F(target).stripScripts().escapeHTML();
                        d.content = text;
                    }
                    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.editor.setStyle({ width: 'auto', height: 'auto' });
        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(); });
        if (typeof VKI_attach == 'function') {
            VKI_attach($('new_title'));
        }
    },

    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);
    },

    setupEditorMode: function() {
        if (loadData.type == 2) {
            $$('#pm_addpage, #pm_delpage, #pm_addvideo, #pc_hint').invoke('hide');
            hintsAreOn = false;
            pageEditor.enableHints(hintsAreOn);
            if (!cspMode) {
                $$('#pm_layout, #pm_addtext').invoke('hide');
            }
            $$('.pagenum').invoke('addClassName', 'smaller');
        }
    },

    getPageNum: function(n) {
        if (n == 0) return '';
        if (n == (pagingController ? pagingController._elements.length : loadData.pages.length)) return 'back';
        if (loadData.type == 2) {
            // calendar
            var t = ['cover', 'Jan Top', 'Jan Bottom', 'Feb Top', 'Feb Bottom', 'Mar Top', 'Mar Bottom', 'Apr Top', 'Apr Bottom', 'May Top', 'May Bottom', 'Jun Top', 'Jun Bottom', 'Jul Top', 'Jul Bottom', 'Aug Top', 'Aug Bottom', 'Sep Top', 'Sep Bottom', 'Oct Top', 'Oct Bottom', 'Nov Top', 'Nov Bottom', 'Dec Top', 'Dec Bottom'];
            if (n <= t.length) return t[n-1];
            return '' + n;
        } else {
        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;
    },

    showGlobalMessage: function(msg) {
        if (msg) {
            $('global_message').update(msg).show();
        } else {
            $('global_message').hide();
        }
        pageEditor.resizeContainer();
    }
};

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;
var cspMode = false;

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);
    if (cspMode || loadData.type != 2) {
    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); });
        }
    });

    editor.setupEditorMode();
};

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

