/***
    Pandora
***/

var pandora = new Ox.App({
    apiURL: '/api/',
    config: '/site.json',
    init: 'hello',
}).launch(function(data) {

    Ox.print('data', data);

	var app = {
			$ui: {
				body: $('body'),
				document: $(document),
				window: $(window)
			},
			config: data.config,
			requests: {},
			ui: {
				infoRatio: 4 / 3,
				sectionElement: 'buttons',
		        selectedMovies: []
			},
			user: data.user
		};

    if (app.user.group == 'guest') {
        app.user = data.config.user;
        $.browser.safari && Ox.theme('modern');
    }


    // $.each(pandora.afterLaunch, function(i, f) { f(); });
    

	function load() {

        $(function() {
            var $body = $('body');
    	    Ox.Request.requests() && app.$ui.loadingIcon.start();
    	    $body.bind('requestStart', function() {
    	        Ox.print('requestStart')
    	        app.$ui.loadingIcon.start();
    	    });
    	    $body.bind('requestStop', function() {
    	        Ox.print('requestStop')
    	        app.$ui.loadingIcon.stop();
    	    });
        });

		//Query.fromString(location.hash.substr(1));

	    URL.parse();
	    window.onpopstate = function() {
	        URL.update();
	    };

		app.$ui.appPanel = ui.appPanel();
		app.$ui.appPanel.display();	    

        app.ui.sectionButtonsWidth = app.$ui.sectionButtons.width() + 8;

        app.$ui.window.resize(function() {
            if (app.user.ui.item == '') {
                app.$ui.list.size();
                resizeGroups(app.$ui.rightPanel.width());
                if (app.user.ui.listView == 'map') {
                    app.$ui.map.triggerResize();
                }
            } else {
                Ox.print('app.$ui.window.resize');
                app.$ui.browser.scrollToSelection();
                app.user.ui.itemView == 'timeline' && app.$ui.editor.options({
					height: app.$ui.document.height() -
					        20 - 24 - app.$ui.contentPanel.size(0) - 1 - 16,
                    width: app.$ui.document.width() -
                            app.$ui.mainPanel.size(0) - app.$ui.item.size(1) - 2
                });
            }
        });

        pandora.app = app; // remove later

	}

    function login(data) {
        app.user = data.user;
        //Ox.Event.unbindAll();
        app.$ui.appPanel.reload();
    }

    function logout(data) {
        app.user = data.user;
        //Ox.Event.unbindAll();
        app.$ui.appPanel.reload();
    }

	var ui = {
	    accountDialog: function(action) {
	        var that = new Ox.Dialog($.extend({
                    height: 256,
                    id: 'accountDialog',
                    minHeight: 256,
                    minWidth: 384,
                    width: 384
    	        }, ui.accountDialogOptions(action)))
    	        .bindEvent({
    	            resize: function(event, data) {
                        var width = data.width - 32;
                        app.$ui.accountForm.items.forEach(function(item) {
                            item.options({width: width});
                        });
                    }
    	        });
	        return that;
	    },
	    accountDialogOptions: function(action, value) {
	        Ox.print('ACTION', action)
            app.$ui.accountForm && app.$ui.accountForm.remove();
	        var buttons = {
	                login: ['register', 'reset'],
	                register: ['login'],
	                reset: ['login'],
	                resetAndLogin: []
                },
                buttonTitle = {
                    login: 'Login',
                    register: 'Register',
                    reset: 'Reset Password',
                    resetAndLogin: 'Reset Password and Login'
                },
                dialogText = {
                    login: 'To login to your account, please enter your username and password.',
                    register: 'To create a new account, please choose a username and password, and enter your e-mail address.',
                    reset: 'To reset your password, please enter either your username or your e-mail address.',
                    resetAndLogin: 'To login to your account, please choose a new password, and enter the code that we have just e-mailed to you.'
                },
                dialogTitle = {
                    login: 'Login',
                    register: 'Register',
                    reset: 'Reset Password',
                    resetAndLogin: 'Reset Password'
                };
            function button(type) {
                if (type == 'cancel') {
    	            return new Ox.Button({
                        id: 'cancel' + Ox.toTitleCase(action),
                        title: 'Cancel'
                    }).bindEvent('click', function() {
                        app.$ui.accountDialog.close();
                    });
    	        } else if (type == 'submit') {
    	            return new Ox.Button({
                        disabled: true,
                        id: 'submit' + Ox.toTitleCase(action),
                        title: buttonTitle[action]
                    }).bindEvent('click', function() {
                        app.$ui.accountForm.submit();
                    });
    	        } else {
    	            return new Ox.Button({
    	                id: type,
    	                title: buttonTitle[type] + '...'
    	            }).bindEvent('click', function() {
    	                Ox.print('CLICK EVENT', type)
                        app.$ui.accountDialog.options(ui.accountDialogOptions(type));
                    });
    	        }
            }
            return {
                buttons: [
    	            $.map(buttons[action], function(type) {
                        return button(type);
                    }),
                    [button('cancel'), button('submit')]
                ],
                content: new Ox.Element('div')
                    .append(
                        new Ox.Element('div')
                            .addClass('OxText')
                            .html(dialogText[action] + '<br/><br/>')
                    )
                    .append(
                        app.$ui.accountForm = ui.accountForm(action, value)
                    ),
                keys: {
    	            enter: 'submit' + Ox.toTitleCase(action),
    	            escape: 'cancel' + Ox.toTitleCase(action)
    	        },
                title: dialogTitle[action]
            };
	    },
	    accountForm: function(action, value) {
	        if (app.$ui.accountForm) {
	            app.$ui.accountForm.items.forEach(function(item) {
	                if (item.options('id') == 'usernameOrEmail') {
	                    Ox.print('REMOVING')
	                    //Ox.Event.unbind('usernameOrEmailSelect')
	                    //Ox.Event.unbind('usernameOrEmailSelectMenu')
	                    //Ox.Event.unbind('usernameOrEmailInput')
	                }
	                Ox.print('REMOVING ITEM', item.options('id'));
	                item.remove();
	            });
	        }
	        var items = {
	                'login': ['username', 'password'],
	                'register': ['newUsername', 'password', 'email'],
	                'reset': ['usernameOrEmail'],
	                'resetAndLogin': ['oldUsername', 'newPassword', 'code']
	            },
	            $items = $.map(items[action], function(v) {
                    return item(v, value);
                }),
	            that = new Ox.Form({
                    id: 'accountForm' + Ox.toTitleCase(action),
                    items: $items,
                    submit: function(data, callback) {
                        if (action == 'login') {
                            pandora.api.signin(data, function(result) {
                                if (!result.data.errors) {
                                    app.$ui.accountDialog.close();
                                    login(result.data);
                                } else {
                                    callback([{id: 'password', message: 'Incorrect password'}]);
                                }
                            });
                        } else if (action == 'register') {
                            pandora.api.signup(data, function(result) {
                                if (!result.data.errors) {
                                    app.$ui.accountDialog.close();
                                    login(result.data);
                                    ui.accountWelcomeDialog().open();
                                } else {
                                    callback([{id: 'password', message: result.data.errors.toString()}]); // fixme
                                }
                            });
                        } else if (action == 'reset') {
                            var usernameOrEmail = data.usernameOrEmail,
                                key = usernameOrEmail[0].id;
                            data = {};
                            data[key] = usernameOrEmail[1];
                            pandora.api.requestToken(data, function(result) {
                                if (!result.data.errors) {
                                    app.$ui.accountDialog.options(ui.accountDialogOptions('resetAndLogin', result.data.username));
                                } else {
                                    callback([{id: 'usernameOrEmail', message: 'Unknown ' + (key == 'username' ? 'username' : 'e-mail address')}])
                                }
                            });
                        } else if (action == 'resetAndLogin') {
                            pandora.api.resetPassword(data, function(result) {
                                if (!result.data.errors) {
                                    app.$ui.accountDialog.close();
                                    login(result.data);
                                } else {
                                    callback([{id: 'code', message: 'Incorrect code'}]);
                                }
                            })
                        }
                    }
                }).bindEvent({
                    submit: function(event, data) {
                
                    },
                    validate: function(event, data) {
                        Ox.print('FORM VALIDATE', data)
                        app.$ui.accountDialog[
                            (data.valid ? 'enable' : 'disable') + 'Button'
                        ]('submit' + Ox.toTitleCase(action));
                    }
                });
            that.items = $items;
            function item(type, value) {
                if (type == 'code') {
                    return new Ox.Input({
                        autovalidate: autovalidateCode,
                        id: 'code',
                        label: 'Code',
                        labelWidth: 120,
                        validate: function(value, callback) {
                            callback({
                                message: 'Missing code',
                                valid: !!value.length
                            });
                        },
                        width: 352
                    });
                } else if (type == 'email') {
                    return new Ox.Input({
                        autovalidate: autovalidateEmail,
                        id: 'email',
                        label: 'E-Mail Address',
                        labelWidth: 120,
                        type: 'email',
                        validate: validateUser('email'),
                        width: 352
                    });
                } else if (type == 'newPassword') {
                    return new Ox.Input({
                        autovalidate: /.+/,
                        id: 'password',
                        label: 'New Password',
                        labelWidth: 120,
                        type: 'password',
                        validate: function(value, callback) {
                            callback({
                                message: 'Missing password',
                                valid: value.length > 0
                            });
                        },
                        width: 352
                    });
                } else if (type == 'newUsername') {
                    return new Ox.Input({
                        autovalidate: autovalidateUsername,
                        id: 'username',
                        label: 'Username',
                        labelWidth: 120,
                        validate: validateUser('username'),
                        width: 352
                    });
                } else if (type == 'oldUsername') {
                    return new Ox.Input({
                        disabled: true,
                        id: 'username',
                        label: 'Username',
                        labelWidth: 120,
                        value: value,
                        width: 352
                    });
                } else if (type == 'password') {
                    return new Ox.Input({
        	            autovalidate: /.+/,
                        id: 'password',
                        label: 'Password',
                        labelWidth: 120,
                        type: 'password',
                        validate: function(value, callback) {
                            callback({
                                message: 'Missing Password',
                                valid: value.length > 0
                            });
                        },
                        width: 352
                    });
                } else if (type == 'username') {
                    return new Ox.Input({
                        autovalidate: autovalidateUsername,
                        id: 'username',
                        label: 'Username',
                        labelWidth: 120,
                        validate: validateUser('username', true),
                        width: 352
                    });    
                } else if (type == 'usernameOrEmail') {
                    return new Ox.FormElementGroup({
                        id: 'usernameOrEmail',
                        elements: [
                            app.$ui.usernameOrEmailSelect = new Ox.Select({
                                    id: 'usernameOrEmailSelect',
                                    items: [
                                        {id: 'username', title: 'Username'},
                                        {id: 'email', title: 'E-Mail Address'},
                                    ],
                                    overlap: 'right',
                                    width: 120
                                })
                                .bindEvent({
                                    change: function(event, data) {
                                        var selected = data.selected[0].id;
                                        app.$ui.usernameOrEmailInput.options({
                                            autovalidate: selected == 'username' ? autovalidateUsername : autovalidateEmail,
                                            validate: validateUser(selected, true),
                                            value: ''
                                        }).focus();
                                    }
                                }),
                            app.$ui.usernameOrEmailInput = new Ox.Input({
                                autovalidate: autovalidateUsername,
                                id: 'usernameOrEmailInput',
                                validate: validateUser('username', true),
                                width: 232
                            })
                        ],
                        separators: [
                            {title: '', width: 0}
                        ]
                    });
                }
            }
            return that;	        
	    },
        accountLogoutDialog: function() {
            var that = new Ox.Dialog({
                    buttons: [
                        new Ox.Button({
                            id: 'cancel',
                            title: 'Cancel'
                        }).bindEvent('click', function() {
                            that.close();
                            app.$ui.mainMenu.getItem('loginlogout').toggleTitle();
                        }),
                        new Ox.Button({
                            id: 'logout',
                            title: 'Logout'
                        }).bindEvent('click', function() {
                            that.close();
                            pandora.api.signout({}, function(result) {
                                logout(result.data);
                            });
                        })
                    ],
                    content: new Ox.Element('div').html('Are you sure you want to logout?'),
                    height: 160,
                    keys: {enter: 'logout', escape: 'cancel'},
                    title: 'Logout',
                    width: 300
                });
            return that;
        },
        accountWelcomeDialog: function() {
            var that = new Ox.Dialog({
                    buttons: [
                        [
                            new Ox.Button({
                                id: 'preferences',
                                title: 'Preferences...'
                            }).bindEvent('click', function() {
                                that.close();
                            })
                        ],
                        [
                            new Ox.Button({
                                id: 'close',
                                title: 'Close'
                            }).bindEvent('click', function() {
                                that.close();
                            })
                        ]
                    ],
                    content: new Ox.Element('div').html('Welcome, ' + app.user.username + '!<br/><br/>Your account has been created.'),
                    height: 160,
                    keys: {enter: 'close', escape: 'close'},
                    title: 'Welcome to ' + app.config.site.name,
                    width: 300
                });
            return that;
        },
		annotations: function() {
			var that = new Ox.Element({
			        id: 'annotations'
			    })
			    .bindEvent({
			        resize: function(event, data) {
			            app.user.ui.annotationsSize = data;
			        },
			        toggle: function(event, data) {
			            app.user.ui.showAnnotations = !data.collapsed;
			        }
			    }),
				$bins = [];
		    $.each(app.config.layers, function(i, layer) {
		        var $bin = new Ox.CollapsePanel({
		            id: layer.id,
		            size: 16,
		            title: layer.title
		        });
		        $bins.push($bin);
		        $bin.$content.append(
		            $('<div>').css({ height: '20px' }).append(
		                $('<div>').css({ float: 'left', width: '16px', height: '16px', margin: '1px'}).append(
		                    $('<img>').attr({ src: 'static/oxjs/build/png/ox.ui.modern/iconFind.png' }).css({ width: '16px', height: '16px', border: 0, background: 'rgb(64, 64, 64)', WebkitBorderRadius: '2px' })
		                )
		            ).append(
		                $('<div>').css({ float: 'left', width: '122px', height: '14px', margin: '2px' }).html('Foo')
		            ).append(
		                $('<div>').css({ float: 'left', width: '40px', height: '14px', margin: '2px', textAlign: 'right' }).html('23')
		            )
		        );
		    });
		    $.each($bins, function(i, bin) {
		        that.append(bin);
		    });
			return that;
		},
		appPanel: function() {
			var that = new Ox.SplitPanel({
			        elements: [
			            {
			                element: app.$ui.mainMenu = ui.mainMenu(),
			                size: 20
			            },
			            {
			                element: app.$ui.mainPanel = ui.mainPanel()
						}
					],
			        orientation: 'vertical'
			    });
			that.display = function() {
				app.$ui.body.css({opacity: 0});
			    that.appendTo(app.$ui.body);
			    app.$ui.body.animate({opacity: 1}, 1000);
				return that;
			}
			that.reload = function() {
			    app.$ui.appPanel.remove();
			    app.$ui.appPanel = ui.appPanel().appendTo(app.$ui.body);
			    return that;
			}
			return that;
		},
        backButton: function() {
            var that = Ox.Button({
                title: 'Back to Movies',
                width: 96
            }).css({
                float: 'left',
                margin: '4px'
            })
            .bindEvent({
                click: function(event, data) {
                    URL.set(Query.toString());
                }
            });
            return that;
        },
		browser: function() {
		    var that;
			if (!app.user.ui.item) {
				app.$ui.groups = ui.groups();
				that = new Ox.SplitPanel({
	                elements: [
	                    {
	                        element: app.$ui.groups[0],
	                        size: app.ui.groups[0].size
	                    },
	                    {
	                        element: app.$ui.groupsInnerPanel = ui.groupsInnerPanel()
	                    },
	                    {
	                        element: app.$ui.groups[4],
	                        size: app.ui.groups[4].size
	                    },
	                ],
	                id: 'browser',
	                orientation: 'horizontal'
	            })
	            .bindEvent({
	                resize: function(event, data) {
    	                app.user.ui.groupsSize = data;
    	                $.each(app.$ui.groups, function(i, list) {
    	                    list.size();
    	                });
    	            },
    	            toggle: function(event, data) {
    	                app.user.ui.showGroups = !data.collapsed;
    	                data.collapsed && app.$ui.list.gainFocus();
    	            }
	            });
			} else {
		        var that = new Ox.IconList({
		            centered: true,
		            id: 'list',
		            item: function(data, sort, size) {
		                var ratio = data.poster.width / data.poster.height;
		                size = size || 64;
		                return {
		                    height: ratio <= 1 ? size : size / ratio,
		                    id: data['id'],
		                    info: data[['title', 'director'].indexOf(sort[0].key) > -1 ? 'year' : sort[0].key],
		                    title: data.title + (data.director ? ' (' + data.director + ')' : ''),
		                    url: data.poster.url.replace(/jpg/, size + '.jpg'),
		                    width: ratio >= 1 ? size : size * ratio
		                };
		            },
		            keys: ['director', 'id', 'poster', 'title', 'year'],
		            max: 1,
		            min: 1,
		            orientation: 'horizontal',
		            request: function(data, callback) {
		                Ox.print('data, Query.toObject', data, Query.toObject())
		                pandora.api.find($.extend(data, {
		                    query: Query.toObject()
		                }), callback);
		            },
		            selected: [app.user.ui.item],
		            size: 64,
		            sort: app.user.ui.sort,
		            unique: 'id'
		        })
		        .bindEvent({
		            open: function(event, data) {
		                that.scrollToSelection();
		            },
		            select: function(event, data) {
		                URL.set(data.ids[0]);
		            },
		            toggle: function(event, data) {
		                app.user.ui.showMovies = !data.collapsed;
		                if (data.collapsed) {
		                    if (app.user.ui.itemView == 'timeline') {
		                        app.$ui.editor.gainFocus();
		                    }
		                }
		            }
		        });
			}
			that.update = function() {
			    app.$ui.contentPanel.replace(0, app.$ui.browser = ui.browser());
			}
			return that;
		},
		contentPanel: function() {
			var that = new Ox.SplitPanel({
		        elements: app.user.ui.item == '' ? [
		            {
		                collapsed: !app.user.ui.showGroups,
		                collapsible: true,
		                element: app.$ui.browser = ui.browser()
			                .bindEvent('resize', function(event, data) {
			                    $.each(app.$ui.groups, function(i, list) {
			                        list.size();
			                    });
			                }),
		                resizable: true,
		                resize: [96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256],
		                size: app.user.ui.groupsSize
		            },
		            {
		                element: app.$ui.list = ui.list(app.user.ui.listView)
		            }
		        ] : [
    	            {
    	                collapsed: !app.user.ui.showMovies,
    	                collapsible: true,
    	                element: app.$ui.browser = ui.browser(),
    	                size: 112 + ($.browser.mozilla ? 16 : 12) // fixme: should be app.ui.scrollbarSize
    	            },
    	            {
    	                element: app.$ui.item = ui.item(app.user.ui.item, app.user.ui.itemView)
    	            }
		        ],
		        orientation: 'vertical'
		    })
			return that;
		},
		findElement: function() {
			var that = new Ox.FormElementGroup({
                    elements: [
                        app.$ui.findSelect = new Ox.Select({
                                id: 'select',
                                items: $.map(app.config.findKeys, function(key, i) {
                                    return {
                                        id: key.id,
                                        title: 'Find: ' + key.title
                                    };
                                }),
                                overlap: 'right',
                                width: 112
                            })
                            .bindEvent('change', function(event, data) {
                                var key = data.selected[0].id;
                                if (!app.user.ui.findQuery.conditions.length) {
                                    app.user.ui.findQuery.conditions = [{key: key, value: '', operator: ''}];
                                } else {
                                    app.user.ui.findQuery.conditions[0].key = key;
                                }
                                app.$ui.mainMenu.checkItem('findMenu_find_' + key);
                                app.$ui.findInput.options({
                                    autocomplete: autocompleteFunction()
                                }).focus();
                                Ox.print(app.$ui.findInput.options('autocomplete').toString())
                            }),
                        app.$ui.findInput = new Ox.Input({
                            autocomplete: autocompleteFunction(),
                            autocompleteSelect: true,
                            autocompleteSelectHighlight: true,
                            autocompleteSelectSubmit: true,
                            clear: true,
                            id: 'input',
                            width: 192
                        })
                        .bindEvent('submit', function(event, data) {
                            var key = app.user.ui.findQuery.conditions.length ?
                                    app.user.ui.findQuery.conditions[0].key : '',
                                query;
                            Ox.print('key', key);
                            app.user.ui.findQuery.conditions = [
                                {
                                    key: key == 'all' ? '' : key,
                                    value: data.value,
                                    operator: ''
                                }
                            ];
                            $.each(app.ui.groups, function(i, group) {
                                group.query.conditions = [];
                                app.$ui.groups[i].options({
                                    request: function(data, callback) {
                                        delete data.keys;
                                        return pandora.api.find($.extend(data, {
                                            group: group.id,
                                            query: Query.toObject(group.id)
                                        }), callback);
                                    }
                                });
                            });
                            app.$ui.list.options({
                                request: function(data, callback) {
                                    return pandora.api.find($.extend(data, {
                                        query: query = Query.toObject()
                                    }), callback);
                                }
                            });
                            history.pushState({}, '', '/#' + Query.toString(query));
                        })
                    ],
                    id: 'findElement'
                })
                .css({
                    float: 'right',
                    margin: '4px'
                });
            function autocompleteFunction() {
                return app.user.ui.findQuery.conditions.length ? function(value, callback) {
                    var key = app.user.ui.findQuery.conditions[0].key,
                        findKey = Ox.getObjectById(app.config.findKeys, key);
                    Ox.print('autocomplete', key, value);
                    value === '' && Ox.print('Warning: autocomplete function should never be called with empty value');
                    if ('autocomplete' in findKey && findKey.autocomplete) {
                        pandora.api.autocomplete({
                            key: key,
                            range: [0, 20],
                            value: value
                        }, function(result) {
                            //alert(JSON.stringify(result))
                            callback(result.data.items);
                        });
                        /*
                        pandora.api.find({
                            keys: [key],
                            query: {
                                conditions: [
                                    {
                                        key: key,
                                        value: value,
                                        operator: ''
                                    }
                                ],
                                operator: ''
                            },
                            sort: [
                                {
                                    key: key,
                                    operator: ''
                                }
                            ],
                            range: [0, 10]
                        }, function(result) {
                            callback($.map(result.data.items, function(v) {
                                return v[key];
                            }));
                        });
                        */
                    } else {
                        callback();                            
                    }
                } : null;
            }
			return that;
		},
        group: function(id, query) {
            Ox.print('group', id, query);
            /*
            query && query.conditions.length && alert($.map(query.conditions, function(v) {
                return v.value;
            }));
            */
            //alert(id + ' ' + JSON.stringify(Query.toObject(id)))
            var i = app.user.ui.groups.indexOf(id),
                panelWidth = app.$ui.document.width() - (app.user.ui.showSidebar * app.user.ui.sidebarSize) - 1,
                title = Ox.getObjectById(app.config.groups, id).title,
                width = getGroupWidth(i, panelWidth),
                that = new Ox.TextList({
                    columns: [
                        {
                            align: 'left',
                            id: 'name',
                            operator: id == 'year' ? '-' : '+',
                            title: title,
                            unique: true,
                            visible: true,
                            width: width.column
                        },
                        {
                            align: 'right',
                            id: 'items',
                            operator: '-',
                            title: '#',
                            visible: true,
                            width: 40
                        }
                    ],
                    columnsVisible: true,
                    id: 'group_' + id,
                    request: function(data, callback) {
                        Ox.print('sending request', data)
                        delete data.keys;
                        //alert(id + " Query.toObject " + JSON.stringify(Query.toObject(id)) + ' ' + JSON.stringify(data))
                        return pandora.api.find($.extend(data, {
                            group: id,
                            query: Query.toObject(id)
                        }), callback);
                    },
                    selected: query ? $.map(query.conditions, function(v) {
                        return v.value;
                    }) : [],
                    sort: [
                        {
                            key: id == 'year' ? 'name' : 'items',
                            operator: '-'
                        }
                    ]
                })
                .bindEvent('select', function(event, data) {
                    var group = app.ui.groups[i],
                        query;
                    app.ui.groups[i].query.conditions = $.map(data.ids, function(v) {
                        return {
                            key: id,
                            value: v,
                            operator: '='
                        };
                    });
                    reloadGroups(i);
                });
            new Ox.Select({
                    items: $.map(app.config.groups, function(v) {
                        return {
                            checked: v.id == id,
                            id: v.id,
                            title: v.title
                        }
                    }),
                    max: 1,
                    min: 1,
                    type: 'image'
                })
                .bindEvent('change', function(event, data) {
                    var id_ = data.selected[0].id,
                        i_ = app.user.ui.groups.indexOf(id_);
                    if (i_ == -1) {
                        // new group was not part of old group set
                        if (app.ui.groups[i].query.conditions.length) {
                            // if group with selection gets replaced, reload
                            app.ui.groups[i].query.conditions = [];
                            reloadGroups(i);
                        }
                        app.ui.groups[i] = getGroupObject(id_);
                        app.user.ui.groups[i] = id_;
                        replaceGroup(i, id_);
                    } else {
                        // swap two existing groups
                        var group = $.extend({}, app.ui.groups[i]);
                        app.ui.groups[i] = app.ui.groups[i_];
                        app.ui.groups[i_] = group;
                        app.user.ui.groups[i] = id_;
                        app.user.ui.groups[i_] = id;
                        replaceGroup(i, id_, app.ui.groups[i].query);
                        replaceGroup(i_, id, app.ui.groups[i_].query);
                    }
                    function replaceGroup(i, id, query) {
                        // if query is passed, selected items will be derived from it
                        var isOuter = i % 4 == 0;
                        //foo = app.$ui.groups[i];
                        app.$ui[isOuter ? 'browser' : 'groupsInnerPanel'].replace(
                            isOuter ? i / 2 : i - 1,
                            app.$ui.groups[i] = ui.group(id, query)
                        );
                        //foo.remove();
                    }
                })
                .appendTo(that.$bar.$element);
            if (!query) {
                // if query is set, group object has already been taken care of
                app.ui.groups[i] = getGroupObject(id);
            }
            function getGroupObject(id) {
                var i = app.user.ui.groups.indexOf(id),
                    title = Ox.getObjectById(app.config.groups, id).title,
                    width = getGroupWidth(i, panelWidth);
                return {
                    id: id,
                    element: that,
                    query: {
                        conditions: [],
    	                operator: '|'
                    },
                    size: width.list,
                    title: title
                };
            }
            return that;
        },
		groups: function() {
			var $groups = [];
			app.ui.groups = [];
            app.user.ui.groups.forEach(function(id, i) {
                $groups[i] = ui.group(id);
            });
		    return $groups;
		},
		groupsInnerPanel: function() {
			var that = new Ox.SplitPanel({
                elements: [
                    {
                        element: app.$ui.groups[1],
                        size: app.ui.groups[1].size
                    },
                    {
                        element: app.$ui.groups[2],
                    },
                    {
                        element: app.$ui.groups[3],
                        size: app.ui.groups[3].size
                    }
                ],
                orientation: 'horizontal'
            });
			return that;
		},
		info: function() {
			var that = new Ox.Element()
		        .append(
		            app.$ui.infoStill = new Ox.Element('img')
		                .css({
		                    position: 'absolute',
		                    left: 0,
		                    top: 0
		                })
		        )
		        .append(
		            app.$ui.infoTimeline = new Ox.Element('img')
		                .css({
		                    position: 'absolute',
		                    left: 0,
		                    bottom: 0,
		                    height: '16px',
		                })
		        );
		    return that;
		},
        item: function() {
            var that;
            if (app.user.ui.itemView == 'info') {
                that = new Ox.Element('div');
            } else if (app.user.ui.itemView == 'timeline') {
                that = new Ox.SplitPanel({
                    elements: [
                        {
                            element: new Ox.Element('div'),
                        },
                        {
                            collapsed: !app.user.ui.showAnnotations,
							collapsible: true,
							element: new Ox.Element('div'),
							resizable: true,
							resize: [256, 384],
							size: app.user.ui.annotationsSize
                        }
                    ]
                })
            }
            getItem();
            function getItem() {
                pandora.api.getItem(app.user.ui.item, function(result) {
                    if (app.user.ui.itemView == 'info') {
				        Ox.print('result.data.item', result.data.item)
                        $.get('/static/html/itemInfo.html', {}, function(template) {
                            Ox.print(template);
                            app.$ui.contentPanel.replace(1,
                                app.$ui.item = new Ox.Element('div')
                                .append($.tmpl(template, result.data.item))
                            );
                        });
                    } else if (app.user.ui.itemView == 'timeline') {
                        var video = result.data.item.stream,
    		                cuts = result.data.item.layers.cuts || {},
    		                subtitles = result.data.item.layers.subtitles || [{
    		                    'in': 5,
    		                    'out': 10,
    		                    'text': 'This subtitle is just a test...'
    		                }];
    		            video.height = 96;
    		            video.width = parseInt(video.height * video.aspectRatio / 2) * 2;
    		            video.url = video.baseUrl + '/' + video.height + 'p.' + ($.support.video.webm ? 'webm' : 'mp4');
                        that.replace(0, app.$ui.editor = new Ox.VideoEditor({
    		                cuts: cuts,
    		                duration: video.duration,
    		                find: '',
    		                frameURL: function(position) {
    		                    return '/' + app.user.ui.item + '/frame/' + video.width.toString() + '/' + position.toString() + '.jpg'
    		                },
    						height: app.$ui.contentPanel.size(1),
    		                id: 'editor',
    		                largeTimeline: true,
    		                matches: [],
    		                points: [0, 0],
    		                position: 0,
    		                posterFrame: parseInt(video.duration / 2),
    		                subtitles: subtitles,
    		                videoHeight: video.height,
    		                videoId: app.user.ui.item,
    		                videoWidth: video.width,
    		                videoSize: 'small',
    		                videoURL: video.url,
    		                width: app.$ui.document.width() - app.$ui.mainPanel.size(0) - 1 -
    		                    (app.user.ui.showAnnotations * app.user.ui.annotationsSize) - 1
    		            }).bindEvent('resize', function(event, data) {
    						Ox.print('resize editor', data)
    						app.$ui.editor.options({
    							width: data
    						});
    						Ox.print('resize done')
    					}));
    					that.replace(1, app.$ui.annotations = ui.annotations());
    		            that.bindEvent('resize', function(event, data) {
        				    Ox.print('resize item', data)
        				    app.$ui.editor.options({
        				        height: data
        				    });
        				});
    					/*
    					app.$ui.rightPanel.bindEvent('resize', function(event, data) {
    	                    Ox.print('... rightPanel resize', data, app.$ui.timelinePanel.size(1))
    	                    app.$ui.editor.options({
    	                        width: data - app.$ui.timelinePanel.size(1) - 1
    	                    });
    	                });
    	                */    		            
                    }
		        });
            }
            that.display = function() {
                app.$ui.contentPanel.replaceElements([
                    {
						collapsible: true,
						element: app.$ui.browser = ui.browser(),
						resizable: false,
						size: 112 + ($.browser.mozilla ? 16 : 12)
					},
					{
					    element: new Ox.Element('div')
					}
	            ]);
	            getItem(); // fixme: can the asynchronicity be moved within the video editor?
            }
            return that;
        },
		leftPanel: function() {
			var that = new Ox.SplitPanel({
                    elements: [
						{
							element: app.$ui.sectionbar = ui.sectionbar('buttons'),
							size: 24
						},
                        {
                            element: app.$ui.sections = ui.sections().options({
                                id: 'listsPanel'
                            })
                        },
                        {
                            collapsible: true,
                            element: app.$ui.info = ui.info().options({
                                id: 'infoPanel'
                            }),
                            size: app.user.ui.sidebarSize / app.ui.infoRatio + 16
                        }
                    ],
                    id: 'leftPanel',
                    orientation: 'vertical'
                })
                .bindEvent({
                    resize: function(event, data) {
                        app.user.ui.sidebarSize = data;
                        if (data < app.ui.sectionButtonsWidth && app.$ui.sectionButtons) {
                            app.$ui.sectionButtons.remove();
                            delete app.$ui.sectionButtons;
                            app.$ui.sectionbar.append(app.$ui.sectionSelect = ui.sectionSelect());
                        } else if (data >= app.ui.sectionButtonsWidth && app.$ui.sectionSelect) {
                            app.$ui.sectionSelect.remove();
                            delete app.$ui.sectionSelect;
                            app.$ui.sectionbar.append(app.$ui.sectionButtons = ui.sectionButtons());
                        }
                        ///*
                        app.$ui.leftPanel.find('.OxTextList').css({
                            width: data + 'px'
                        });
                        app.$ui.leftPanel.find('.OxItem').css({
                            width: data + 'px'
                        });
                        app.$ui.leftPanel.find('.OxCell.OxColumnTitle').css({
                            width: (data - 80) + 'px'
                        });
                        //*/
                        Ox.print('resize', data, data / app.ui.infoRatio + 16);
                        app.$ui.leftPanel.size('infoPanel', Math.round(data / app.ui.infoRatio) + 16);
                    },
                    toggle: function(event, data) {
                        app.user.ui.showSidebar = !data.collapsed;
                    }
                });
			return that;
		},
		list: function(view) {
			var that, $map,
		        keys = ['director', 'id', 'poster', 'title', 'year'];
		    Ox.print('constructList', view);
		    if (view == 'list') {
		        that = new Ox.TextList({
		            columns: $.map(app.config.sortKeys, function(key, i) {
		                return $.extend({
		                    align: getAlignment(key.id),
		                    operator: getSortOperator(key.id),
		                    unique: key.id == 'id',
		                    visible: $.inArray(key.id, app.user.ui.columns) > -1
		                }, key);
		            }),
		            columnsMovable: true,
		            columnsRemovable: true,
		            columnsResizable: true,
		            columnsVisible: true,
		            id: 'list',
		            request: function(data, callback) {
		                Ox.print('data, Query.toObject', data, Query.toObject())
		                pandora.api.find($.extend(data, {
		                    query: Query.toObject()
		                }), callback);
		            },
		            sort: app.user.ui.sort
		        })
		        .bindEvent({
		            resize: function(event, data) {
		                that.size();
		            }
		        });
		    } else if (view == 'icons') {
		        that = new Ox.IconList({
		            id: 'list',
		            item: function(data, sort, size) {
		                var ratio = data.poster.width / data.poster.height;
		                size = size || 128;
		                return {
		                    height: ratio <= 1 ? size : size / ratio,
		                    id: data['id'],
		                    info: data[['title', 'director'].indexOf(sort[0].key) > -1 ? 'year' : sort[0].key],
		                    title: data.title + (data.director ? ' (' + data.director + ')' : ''),
		                    url: data.poster.url.replace(/jpg/, size + '.jpg'),
		                    width: ratio >= 1 ? size : size * ratio
		                };
		            },
		            keys: keys,
		            request: function(data, callback) {
		                Ox.print('data, Query.toObject', data, Query.toObject())
		                pandora.api.find($.extend(data, {
		                    query: Query.toObject()
		                }), callback);
		            },
		            size: 128,
		            sort: app.user.ui.sort,
		            unique: 'id'
		        });
			} else if (view == 'map') {
				that = new Ox.SplitPanel({
					elements: [
						{
							element: new Ox.SplitPanel({
								elements: [
									{
										element: new Ox.Toolbar({
		                                    	orientation: 'horizontal',
		                                    	size: 24
		                                	})
											.append(
		                                        app.$ui.findMapInput = new Ox.Input({
		                                            clear: true,
		                                            id: 'findMapInput',
		                                            placeholder: 'Find on Map',
		                                            width: 192
		                                        })
		                                        .css({
		                                            float: 'right',
		                                            margin: '4px'
		                                        })
												.bindEvent({
													submit: function(event, data) {
														app.$ui.map.find(data.value, function(data) {
															app.$ui.mapStatusbar.html(data.geoname + ' ' + JSON.stringify(data.points))
														});
													}
												})
		                                    ),
										size: 24
									},
									{
										element: app.$ui.map = new Ox.Map({
												places: [
													{
														geoname: 'Beirut, Lebanon',
														name: 'Beirut',
														points: {
															'center': [33.8886284, 35.4954794],
															'northeast': [33.8978909, 35.5114868],
															'southwest': [33.8793659, 35.479472]
														}
													},
													{
														geoname: 'Berlin, Germany',
														name: 'Berlin',
														points: {
															'center': [52.506701, 13.4246065],
															'northeast': [52.675323, 13.760909],
															'southwest': [52.338079, 13.088304]
														}
													},
													{
														geoname: 'Mumbai, Maharashtra, India',
														name: 'Bombay',
														points: {
															'center': [19.07871865, 72.8778187],
															'northeast': [19.2695223, 72.9806562],
															'southwest': [18.887915, 72.7749812]
														}
													}
												]
											})
											.bindEvent({								
												select: function(event, data) {
													app.$ui.mapStatusbar.html(data.geoname + ' ' + JSON.stringify(data.points))
												}
											}),
										id: 'map',
										size: 'auto'
									},
									{
										element: app.$ui.mapStatusbar = new Ox.Toolbar({
			                                    orientation: 'horizontal',
			                                    size: 16
			                                })
											.css({
												fontSize: '9px',
												padding: '2px 4px 0 0',
												textAlign: 'right'
											}),
										size: 16
									}
								],
								orientation: 'vertical'
							}),
						},
						{
							element: new Ox.Element(),
							id: 'place',
							size: 128 + 16 + 12
						}
					],
					orientation: 'horizontal'
				})
				.bindEvent('resize', function() {
				    app.$ui.map.triggerResize();
				});
		    } else {
		        $list = new Ox.Element('<div>')
		            .css({
		                width: '100px',
		                height: '100px',
		                background: 'red'
		            });
		    }

		    ['list', 'icons'].indexOf(view) > -1 && that.bindEvent({
		        closepreview: function(event, data) {
		            app.$ui.previewDialog.close();
		            delete app.$ui.previewDialog;
		        },
		        load: function(event, data) {
		            app.$ui.total.html(ui.status('total', data));
		            data = [];
		            $.each(app.config.totals, function(i, v) {
		                data[v.id] = 0;
		            });
		            app.$ui.selected.html(ui.status('selected', data));
		        },
		        open: function(event, data) {
		            var id = data.ids[0];
		            URL.set(id);
		        },
		        openpreview: function(event, data) {
		            app.requests.preview && pandora.api.cancel(app.requests.preview);
		            app.requests.preview = pandora.api.find({
		                keys: ['director', 'id', 'poster', 'title'],
		                query: {
		                    conditions: $.map(data.ids, function(id, i) {
		                        return {
		                            key: 'id',
		                            value: id,
		                            operator: '='
		                        }
		                    }),
		                    operator: '|'
		                }
		            }, function(result) {
		                var documentHeight = app.$ui.document.height(),
		                    item = result.data.items[0],
		                    title = item.title + (item.director ? ' (' + item.director + ')' : ''),
		                    dialogHeight = documentHeight - 100,
		                    dialogWidth;
		                app.ui.previewRatio = item.poster.width / item.poster.height,
		                dialogWidth = parseInt((dialogHeight - 48) * app.ui.previewRatio);
		                if ('previewDialog' in app.$ui) {
		                    app.$ui.previewDialog.options({
		                        title: title
		                    });
		                    app.$ui.previewImage.animate({
		                        opacity: 0
		                    }, 100, function() {
		                        app.$ui.previewDialog.size(dialogWidth, dialogHeight, function() {
		                            app.$ui.previewImage
		                                .attr({
		                                    src: item.poster.url
		                                })
		                                .one('load', function() {
		                                    app.$ui.previewImage
		                                        .css({
		                                            width: dialogWidth + 'px',
		                                            height: (dialogHeight - 48 - 2) + 'px', // fixme: why -2 ?
		                                            opacity: 0
		                                        })
		                                        .animate({
		                                            opacity: 1
		                                        }, 100);
		                                });
		                        });
		                    });
		                    Ox.print(app.$ui.document.height(), dialogWidth, 'x', dialogHeight, dialogWidth / (dialogHeight - 48), item.poster.width, 'x', item.poster.height, item.poster.width / item.poster.height)
		                } else {
		                    app.$ui.previewImage = $('<img>')
		                        .attr({
		                            src: item.poster.url
		                        })
		                        .css({
		                            position: 'absolute',
		                            width: dialogWidth + 'px',
		                            height: (dialogHeight - 48 - 2) + 'px', // fixme: why -2 ?
		                            left: 0,
		                            top: 0,
		                            right: 0,
		                            bottom: 0,
		                            margin: 'auto',
		                        });
		                    app.$ui.previewDialog = new Ox.Dialog({
		                            buttons: [
		                                new Ox.Button({
		                                    title: 'Close',
		                                }).bindEvent({
		                                    click: function() {
		                                        app.$ui.previewDialog.close();
		                                        delete app.$ui.previewDialog;
		                                        app.$ui.list.closePreview();
		                                    }
		                                })
		                            ],
		                            content: app.$ui.previewImage,
		                            height: dialogHeight,
		                            id: 'previewDialog',
		                            minHeight: app.ui.previewRatio >= 1 ? 128 / app.ui.previewRatio + 48 : 176,
		                            minWidth: app.ui.previewRatio >= 1 ? 128 : 176 * app.ui.previewRatio,
		                            padding: 0,
		                            title: title,
		                            width: dialogWidth
		                        })
		                        .bindEvent('resize', function(event, data) {
		                            var dialogRatio = data.width / (data.height - 48),
		                                height, width;
		                            if (dialogRatio < app.ui.previewRatio) {
		                                width = data.width;
		                                height = width / app.ui.previewRatio;
		                            } else {
		                                height = (data.height - 48 - 2);
		                                width = height * app.ui.previewRatio;
		                            }
		                            app.$ui.previewImage.css({
		                                width: width + 'px',
		                                height: height + 'px', // fixme: why -2 ?
		                            })
		                        })
		                        .open();
		                    //app.$ui.previewImage = $image;
		                    //Ox.print(app.$document.height(), dialogWidth, 'x', dialogHeight, dialogWidth / (dialogHeight - 48), item.poster.width, 'x', item.poster.height, item.poster.width / item.poster.height)
		                }
		            });
		        },
		        select: function(event, data) {
		            var $still, $timeline;
		            app.ui.selectedMovies = data.ids;
		            if (data.ids.length) {
		                app.$ui.mainMenu.enableItem('copy');
		                app.$ui.mainMenu.enableItem('openmovie');
		            } else {
		                app.$ui.mainMenu.disableItem('copy');
		                app.$ui.mainMenu.disableItem('openmovie');            
		            }
		            if (data.ids.length == 1) {
		                $still = $('<img>')
		                    .attr({
		                        src: 'http://0xdb.org/' + data.ids[0] + '/still.jpg'
		                    })
		                    .one('load', function() {
		                        if (data.ids[0] != app.ui.selectedMovies[0]) {
		                            Ox.print('cancel after load...')
		                            return;
		                        }
		                        var image = $still[0],
		                            imageWidth = image.width,
		                            imageHeight = image.height,
		                            width = app.$ui.info.width(),
		                            height = imageHeight * width / imageWidth;
		                        app.ui.infoRatio = width / height;
		                        app.$ui.leftPanel.size('infoPanel', height + 16);
		                        $still.css({
		                                position: 'absolute',
		                                left: 0,
		                                top: 0,
		                                //width: width + 'px',
		                                //height: height + 'px',
		                                width: '100%',
		                                opacity: 0
		                            })
		                            .appendTo(app.$ui.info.$element)
		                            .animate({
		                                opacity: 1
		                            });
		                        app.$ui.infoStill.animate({
		                            opacity: 0
		                        }, 250);
		                        app.$ui.info.animate({
		                            height: (height + 16) + 'px'
		                        }, 250, function() {
		                            app.$ui.infoStill.remove();
		                            app.$ui.infoStill = $still;
		                        });
		                    });
		                /*
		                $timeline = $('<img>')
		                    .attr({
		                        src: 'http://0xdb.org/' + data.ids[0] + '/timeline/timeline.png'
		                    })
		                    .one('load', function() {
		                        $timeline.css({
		                                position: 'absolute',
		                                left: 0,
		                                bottom: '16px',
		                                opacity: 0
		                            })
		                            .appendTo($ui.info.$element)
		                            .animate({
		                                opacity: 1
		                            });
		                        $ui.infoTimeline.animate({
		                            opacity: 0
		                        }, 250, function() {
		                            $ui.infoTimeline.remove();
		                            $ui.infoTimeline = $timeline;
		                        });
		                    });
		                */
		            }
		            pandora.api.find({
		                query: {
		                    conditions: $.map(data.ids, function(id, i) {
		                        return {
		                            key: 'id',
		                            value: id,
		                            operator: '='
		                        }
		                    }),
		                    operator: '|'
		                }
		            }, function(result) {
		                app.$ui.selected.html(ui.status('selected', result.data));
		            });
		        },
		        sort: function(event, data) {
		            /* some magic has already set user.ui.sort
		            Ox.print(':', user.ui.sort[0])
		            if (data.key != user.ui.sort[0].key) {
		                app.$ui.mainMenu.checkItem('sort_sortmovies_' + data.key);
		            }
		            if (data.operator != user.ui.sort[0].operator) {
		                app.$ui.mainMenu.checkItem('sort_ordermovies_' + data.operator === '' ? 'ascending' : 'descending');
		            }
		            user.ui.sort[0] = data;
		            */
		            app.$ui.mainMenu.checkItem('sortMenu_sortmovies_' + data.key);
		            app.$ui.mainMenu.checkItem('sortMenu_ordermovies_' + (data.operator === '' ? 'ascending' : 'descending'));
		        }
		    });
            that.display = function() {
                app.$ui.rightPanel.replace(1, app.$ui.contentPanel = ui.contentPanel());
            };
		    return that;
		},
		mainMenu: function() {
			var isGuest = app.user.group == 'guest',
			    that = new Ox.MainMenu({
		            extras: [
		                app.$ui.loadingIcon = new Ox.LoadingIcon({
		                    size: 'medium'
		                })
		            ],
		            id: 'mainMenu',
		            menus: [
		                { id: app.config.site.id + 'Menu', title: app.config.site.name, items: [
	                        { id: 'home', title: 'Home' },
		                    {},
		                    { id: 'about', title: 'About ' + app.config.site.name },
		                    { id: 'news', title: app.config.site.name + ' News' },
		                    { id: 'tour', title: 'Take a Tour' },
		                    { id: 'faq', title: 'Frequently Asked Questions' },
		                    { id: 'tos', title: 'Terms of Service' },
		                    {},
		                    { id: 'software', title: 'Software', items: [
		                        { id: 'about', title: 'About' },
		                        { id: 'download', title: 'Download' },
		                        { id: 'report', title: 'Report a Bug' }
		                    ] },
		                    {},
		                    { id: 'contact', title: 'Contact ' + app.config.site.name }
		                ] },
		                { id: 'userMenu', title: 'User', items: [
		                    { id: 'username', title: 'User: ' + (isGuest ? 'not logged in' : app.user.username), disabled: true },
		                    {},
		                    { id: 'preferences', title: 'Preferences...', disabled: isGuest, keyboard: 'control ,' },
		                    {},
		                    { id: 'register', title: 'Register...', disabled: !isGuest },
		                    { id: 'loginlogout', title: isGuest ? 'Login...' : 'Logout...' }
		                ] },
		                { id: 'listMenu', title: 'List', items: [
		                    { id: 'history', title: 'History', items: [
		                        { id: 'allmovies', title: 'All Movies' }
		                    ] },
		                    { id: 'lists', title: 'View List', items: [
		                        { id: 'favorites', title: 'Favorites' }
		                    ] },
		                    { id: 'features', title: 'View Feature', items: [
		                        { id: 'situationistfilm', title: 'Situationist Film' },
		                        { id: 'timelines', title: 'Timelines' }
		                    ] },
		                    {},
		                    { id: 'newlist', title: 'New List...', keyboard: 'control n' },
		                    { id: 'newlistfromselection', title: 'New List from Selection...', disabled: true, keyboard: 'shift control n' },
		                    { id: 'newsmartlist', title: 'New Smart List...', keyboard: 'alt control n' },
		                    { id: 'newsmartlistfromresults', title: 'New Smart List from Results...', keyboard: 'shift alt control n' },
		                    {},
		                    { id: 'addmovietolist', title: ['Add Selected Movie to List...', 'Add Selected Movies to List...'], disabled: true },
		                    {},
		                    { id: 'setposterframe', title: 'Set Poster Frame', disabled: true }
		                ]},
		                { id: 'editMenu', title: 'Edit', items: [
		                    { id: 'undo', title: 'Undo', disabled: true, keyboard: 'control z' },
		                    { id: 'redo', title: 'Redo', disabled: true, keyboard: 'shift control z' },
		                    {},
		                    { id: 'cut', title: 'Cut', disabled: true, keyboard: 'control x' },
		                    { id: 'copy', title: 'Copy', disabled: true, keyboard: 'control c' },
		                    { id: 'paste', title: 'Paste', disabled: true, keyboard: 'control v' },
		                    { id: 'delete', title: 'Delete', disabled: true, keyboard: 'delete' },
		                    {},
		                    { id: 'selectall', title: 'Select All', disabled: true, keyboard: 'control a' },
		                    { id: 'selectnone', title: 'Select None', disabled: true, keyboard: 'shift control a' },
		                    { id: 'invertselection', title: 'Invert Selection', disabled: true, keyboard: 'alt control a' }
		                ] },
		                { id: 'viewMenu', title: 'View', items: [
		                    { id: 'movies', title: 'View Movies', items: [
		                        { group: 'viewmovies', min: 0, max: 1, items: $.map(app.config.listViews, function(view, i) {
		                            return $.extend({
		                                checked: app.user.ui.listView == view.id,
		                            }, view);
		                        }) },
		                    ]},
		                    { id: 'icons', title: 'Icons', items: [
		                        { id: 'poster', title: 'Poster' },
		                        { id: 'still', title: 'Still' },
		                        { id: 'timeline', title: 'Timeline' }
		                    ] },
		                    { id: 'info', title: 'Info', items: [
		                        { id: 'poster', title: 'Poster' },
		                        { id: 'video', title: 'Video' }
		                    ] },
		                    {},
		                    { id: 'openmovie', title: ['Open Movie', 'Open Movies'], disabled: true, items: [
		                        { group: 'movieview', min: 0, max: 1, items: $.map(app.config.itemViews, function(view, i) {
		                            return $.extend({
		                                checked: app.user.ui.itemView == view.id,
		                            }, view);
		                        }) },
		                    ]},
		                    {},
		                    { id: 'lists', title: 'Hide Lists', keyboard: 'shift l' },
		                    { id: 'info', title: 'Hide Info', keyboard: 'shift i' },
		                    { id: 'groups', title: 'Hide Groups', keyboard: 'shift g' },
		                    { id: 'movies', title: 'Hide Movies', disabled: true, keyboard: 'shift m' }
		                ]},
		                { id: 'sortMenu', title: 'Sort', items: [
		                    { id: 'sortmovies', title: 'Sort Movies by', items: [
		                        { group: 'sortmovies', min: 1, max: 1, items: $.map(app.config.sortKeys, function(key, i) {
		                            return $.extend({
		                                checked: app.user.ui.sort[0].key == key.id,
		                            }, key);
		                        }) }
		                    ] },
		                    { id: 'ordermovies', title: 'Order Movies', items: [
		                        { group: 'ordermovies', min: 1, max: 1, items: [
		                            { id: 'ascending', title: 'Ascending', checked: app.user.ui.sort[0].operator === '' },
		                            { id: 'descending', title: 'Descending', checked: app.user.ui.sort[0].operator == '-' }
		                        ]}
		                    ] },
		                    { id: 'advancedsort', title: 'Advanced Sort...', keyboard: 'shift control s' },
		                    {},
		                    { id: 'groupsstuff', title: 'Groups Stuff' }
		                ] },
		                { id: 'findMenu', title: 'Find', items: [
		                    { id: 'find', title: 'Find', items: [
		                        { group: 'find', min: 1, max: 1, items: $.map(app.config.findKeys, function(key, i) {
		                            return $.extend({
		                                checked: app.user.ui.findQuery.conditions.length && 
		                                        (app.user.ui.findQuery.conditions[0].key == key.id ||
		                                        (app.user.ui.findQuery.conditions[0].key === '' && key.id == 'all')),
		                            }, key)
		                        }) }
		                    ] },
		                    { id: 'advancedfind', title: 'Advanced Find...', keyboard: 'shift control f' }
		                ] },
		                { id: 'dataMenu', title: 'Data', items: [
		                    { id: 'titles', title: 'Manage Titles...' },
		                    { id: 'names', title: 'Manage Names...' },
		                    {},
		                    { id: 'posters', title: 'Manage Stills...' },
		                    { id: 'posters', title: 'Manage Posters...' },
		                    {},
		                    { id: 'places', title: 'Manage Places...' },
		                    { id: 'events', title: 'Manage Events...' },
		                    {},
		                    { id: 'users', title: 'Manage Users...' },
		                    { id: 'lists', title: 'Manage Lists...' },
		                ] },
		                { id: 'codeMenu', title: 'Code', items: [
		                    { id: 'download', title: 'Download' },
		                    { id: 'contribute', title: 'Contribute' },
		                    { id: 'report', title: 'Report a Bug' },
		                ] },
		                { id: 'helpMenu', title: 'Help', items: [
		                    { id: 'help', title: app.config.site.name + ' Help', keyboard: 'shift ?' }
		                ] },
		                { id: 'debugMenu', title: 'Debug', items: [
		                    { id: 'query', title: 'Show Query' }
		                ] },
		                { id: 'testMenu', title: 'Test', items: [
		                    { group: 'foogroup', items: [
		                        { id: 'item1', title: 'Item 1' },
		                        { id: 'item2', title: 'Item 2' }
		                    ] }
		                ] }
		            ]
		        })
		        .bindEvent({
		            change: function(event, data) {
		                if (data.id == 'find') {
		                    var id = data.checked[0].id;
		                    app.$ui.findSelect.selectItem(id);
		                } else if (data.id == 'movieview') {
		                    var view = data.checked[0].id;
		                    var id = document.location.pathname.split('/')[1];
		                    if (view == 'info')
		                        url(id + '/info');
		                    else
		                        url(id);
		                } else if (data.id == 'ordermovies') {
		                    var id = data.checked[0].id;
		                    app.$ui.list.sortList(app.user.ui.sort[0].key, id == 'ascending' ? '' : '-');
		                } else if (data.id == 'sortmovies') {
		                    var id = data.checked[0].id,
		                        operator = getSortOperator(id);
		                    app.$ui.mainMenu.checkItem('sortMenu_ordermovies_' + (operator === '' ? 'ascending' : 'descending'));
		                    app.$ui.sortSelect.selectItem(id);
                            //alert(id + ' ' + operator)
		                    app.$ui.list.sortList(id, operator);
		                    URL.set(Query.toString());
		                } else if (data.id == 'viewmovies') {
		                    var view = data.checked[0].id;
		                    url('#view=' + view);
		                }
		            },
		            click: function(event, data) {
		                if (data.id == 'about') {
		                    var $dialog = new Ox.Dialog({
		                        buttons: [
		                            new Ox.Button({
		                                id: 'close',
		                                title: 'Close'
		                            }).bindEvent({
		                                click: function() {
		                                    $dialog.close();
		                                }
		                            })
		                        ],
		                        id: 'about',
		                        title: 'About'
		                    }).open();
		                } else if (data.id == 'home') {
		                    var $dialog = new Ox.Dialog({
		                        buttons: [
		                            new Ox.Button({
		                                id: 'close',
		                                title: 'Close'
		                            }).bindEvent({
		                                click: function() {
		                                    $dialog.close();
		                                }
		                            })
		                        ],
		                        height: 498,
		                        id: 'home',
		                        keys: {enter: 'close', escape: 'close'},
		                        title: app.config.site.name,
		                        width: 800
		                    }).open();
		                } else if (data.id == 'register') {
		                    app.$ui.accountDialog = ui.accountDialog('register').open();
		                } else if (data.id == 'loginlogout') {
		                    app.$ui.accountDialog = (app.user.group == 'guest' ?
		                        ui.accountDialog('login') : ui.accountLogoutDialog()).open();
		                } else if (data.id == 'places') {
		                    var $manage = new Ox.SplitPanel({
		                            elements: [
		                                {
		                                    collapsible: true,
		                                    element: new Ox.SplitPanel({
		                                        elements: [
		                                            {
		                                                element: new Ox.Toolbar({
		                                                    orientation: 'horizontal',
		                                                    size: 44
		                                                }).append(
		                                                    app.$ui.findPlacesElement = new Ox.FormElementGroup({
		                                                        elements: [
		                                                            app.$ui.findPlacesSelect = new Ox.Select({
		                                                                    id: 'findPlacesSelect',
		                                                                    items: [
		                                                                        { id: 'name', title: 'Find: Name' },
		                                                                        { id: 'region', title: 'Find: Region' },
		                                                                        { id: 'user', title: 'Find: User' }
		                                                                    ],
		                                                                    overlap: 'right',
		                                                                    type: 'image'
		                                                                })
		                                                                .bindEvent({
		                                                                    change: function(event, data) {
		                                                                        app.$ui.findPlacesSelect.loseFocus();
		                                                                        app.$ui.findPlacesInput.options({
		                                                                            placeholder: data.selected[0].title
		                                                                        });
		                                                                    }
		                                                                }),
		                                                            app.$ui.findPlacesInput = new Ox.Input({
		                                                                clear: true,
		                                                                id: 'findPlacesInput',
		                                                                placeholder: 'Find: Name',
		                                                                width: 234
		                                                            })
		                                                        ],
		                                                        id: 'findPlacesElement'
		                                                    }) 
		                                                    .css({
		                                                        float: 'left',
		                                                        margin: '4px'
		                                                    })
		                                                ).append(
		                                                    app.$ui.sortPlacesSelect = new Ox.Select({
		                                                        id: 'sortPlacesSelect',
		                                                        items: [
		                                                            { id: 'name', title: 'Sort by Name', checked: true },
		                                                            { id: 'region', title: 'Sort by Region' },
		                                                            { id: 'size', title: 'Sort by Size' },
		                                                            { id: 'latitude', title: 'Sort by Latitude' },
		                                                            { id: 'longitude', title: 'Sort by Longitude' },
		                                                            { id: 'clips', title: 'Sort by Number of Clips' },
		                                                            { id: 'user', title: 'Sort by User' },
		                                                            { id: 'datecreated', title: 'Sort by Date Added' },
		                                                            { id: 'datemodified', title: 'Sort by Date Modified' }
		                                                        ],
		                                                        width: 246
		                                                    })
		                                                    .css({
		                                                        float: 'left',
		                                                        margin: '0 4px 4px 4px'
		                                                    })
		                                                ),
		                                                size: 44
		                                            },
		                                            {
		                                                element: new Ox.Element('div')
		                                            },
		                                            {
		                                                element: new Ox.Toolbar({
		                                                    orientation: 'horizontal',
		                                                    size: 16
		                                                }),
		                                                size: 16
		                                            }
		                                        ],
		                                        orientation: 'vertical'
		                                    }),
		                                    size: 256
		                                },
		                                {
		                                    element: new Ox.SplitPanel({
		                                        elements: [
		                                            {
		                                                element: new Ox.Toolbar({
		                                                    orientation: 'horizontal',
		                                                    size: 24
		                                                }).append(
		                                                    app.$ui.labelsButton = new Ox.Button({
		                                                            id: 'labelsButton',
		                                                            title: [
		                                                                {id: 'show', title: 'Show Labels'},
		                                                                {id: 'hide', title: 'Hide Labels'}
		                                                            ],
		                                                            width: 96
		                                                        })
		                                                        .css({
		                                                            float: 'left',
		                                                            margin: '4px'
		                                                        })
		                                                ).append(
		                                                    app.$ui.findMapInput = new Ox.Input({
		                                                        clear: true,
		                                                        id: 'findMapInput',
		                                                        placeholder: 'Find on Map',
		                                                        width: 192
		                                                    })
		                                                    .css({
		                                                        float: 'right',
		                                                        margin: '4px'
		                                                    })
															.bindEvent({
																submit: function(event, data) {
																	app.$ui.map.find(data.value, function(location) {
																		/*
																		app.$ui.placeNameInput.options({
																			disabled: false,
																			value: location.name
																		});
																		app.$ui.placeAliasesInput.options({
																			disabled: false
																		});
																		app.$ui.placeGeonameLabel.options({
																			disabled: false,
																			title: location.names.join(', ')
																		});
																		app.$ui.removePlaceButton.options({
																			disabled: false
																		});
																		app.$ui.addPlaceButton.options({
																			disabled: false
																		});
																		*/
																	});
																}
															})
		                                                ),
		                                                size: 24
		                                            },
		                                            {
		                                                element: app.$ui.map = new Ox.Map({
		                                                    	places: ['Boston', 'Brussels', 'Barcelona', 'Berlin', 'Beirut', 'Bombay', 'Bangalore', 'Beijing']
		                                                	})
															.css({
		                                                    	left: 0,
		                                                    	top: 0,
		                                                    	right: 0,
		                                                    	bottom: 0
		                                                	})
															.bindEvent({
																select: function(event, location) {
																	app.$ui.placeNameInput.options({
																		disabled: false,
																		value: location.name
																	});
																	app.$ui.placeAliasesInput.options({
																		disabled: false
																	});
																	app.$ui.placeGeonameLabel.options({
																		disabled: false,
																		title: location.names.join(', ')
																	});
																	app.$ui.removePlaceButton.options({
																		disabled: false
																	});
																	app.$ui.addPlaceButton.options({
																		disabled: false
																	});
																}
															})
		                                            },
		                                            {
		                                                element: app.$ui.bottomBar = new Ox.Toolbar({
		                                                    orientation: 'horizontal',
		                                                    size: 24
		                                                })
														.append(
															app.$ui.placeNameInput = new Ox.Input({
																disabled: true,
																id: 'placeName',
																placeholder: 'Name',
																width: 128
															})
															.css({
																float: 'left',
																margin: '4px 0 0 4px'
															})
														)
														.append(
															app.$ui.placeAliasesInput = new Ox.Input({
																disabled: true,
																id: 'aliases',
																placeholder: 'Aliases',
																width: 128
															})
															.css({
																float: 'left',
																margin: '4px 0 0 4px'
															})
														)
														.append(
															app.$ui.placeGeonameLabel = new Ox.Label({
																disabled: true,
																id: 'placeGeoname',
																title: 'Geoname',
																width: parseInt(app.$ui.document.width() * 0.8) - 256 - 256 - 32 - 24
															})
															.css({
		                                                        float: 'left',
		                                                        margin: '4px 0 0 4px'
		                                                    })
														)
														.append(
		                                                    app.$ui.addPlaceButton = new Ox.Button({
																disabled: true,
		                                                        id: 'addPlaceButton',
		                                                        title: 'add',
																type: 'image'
		                                                    })
		                                                    .css({
		                                                        float: 'right',
		                                                        margin: '4px 4px 0 0'
		                                                    })
		                                                )
														.append(
		                                                    app.$ui.removePlaceButton = new Ox.Button({
																disabled: true,
		                                                        id: 'removePlaceButton',
		                                                        title: 'remove',
																type: 'image'
		                                                    })
		                                                    .css({
		                                                        float: 'right',
		                                                        margin: '4px 4px 0 0'
		                                                    })
		                                                ),
		                                                size: 24
		                                            }
		                                        ],
		                                        orientation: 'vertical'
		                                    })
		                                }
		                            ],
		                            orientation: 'horizontal'
		                        }).css({
		                            top: '24px',
		                            bottom: '24px',
		                        }),
		                        $dialog = new Ox.Dialog({
		                            buttons: [
		                                {
		                                    click: function() {
		                                        $dialog.close();
		                                    },
		                                    id: 'close',
		                                    title: 'Close',
		                                    value: 'Close'
		                                }
		                            ],
		                            height: parseInt(app.$ui.document.height() * 0.8),
		                            id: 'places',
		                            minHeight: 400,
		                            minWidth: 600,
		                            padding: 0,
		                            title: 'Manage Places',
		                            width: parseInt(app.$ui.document.width() * 0.8)
		                        }).css({
		                            overflow: 'hidden'
		                        }).append($manage).open();
		                }
		            }
		        });
			return that;
		},
		mainPanel: function() {
			var that = new Ox.SplitPanel({
                elements: [
                    {
                        collapsible: true,
                        element: app.$ui.leftPanel = ui.leftPanel(),
                        resizable: true,
                        resize: [128, 256, 384, 512],
                        size: app.user.ui.sidebarSize
                    },
                    {
                        element: app.$ui.rightPanel = ui.rightPanel()
                    }
                ],
                orientation: 'horizontal'
            })
			return that;
		},
		rightPanel: function() {
			var that = new Ox.SplitPanel({
                elements: [
                    {
                        element: app.$ui.toolbar = ui.toolbar(),
                        size: 24
                    },
                    {
                        element: app.$ui.contentPanel = ui.contentPanel()
                    },
                    {
                        element: app.$ui.statusbar = ui.statusbar(),
                        size: 16
                    }
                ],
                id: 'rightPanel',
                orientation: 'vertical'
            })
            .bindEvent('resize', function(event, data) {
                Ox.print('???? resize rightPanel', event, data)
                if (!app.user.ui.item) {
                    resizeGroups(data);
                    app.$ui.list.size();
                    if (app.user.ui.listView == 'map') {
                        app.$ui.map.triggerResize();
                    }
                } else {
                    app.$ui.browser.scrollToSelection();
                    app.user.ui.itemView == 'timeline' && app.$ui.editor.options({
    					width: data - (app.user.ui.showAnnotations * app.user.ui.annotationsSize) - 1
    				});
                }
            });
			return that;
		},
		sectionbar: function(mode) {
			var that = new Ox.Bar({
		            size: 24
		        })
		        .append(
		            mode == 'buttons' ?
		            app.$ui.sectionButtons = ui.sectionButtons() :
		            app.$ui.sectionSelect = ui.sectionSelect()
		        );
			that.toggle = function() {
				
			};
			return that;
		},
		sectionButtons: function() {
		    var that = new Ox.ButtonGroup({
                    buttons: [
    					{id: 'site', selected: app.user.ui.section == 'site', title: app.config.site.name},
                        {id: 'items', selected: app.user.ui.section == 'items', title: app.config.itemName.plural},
                        {id: 'texts', selected: app.user.ui.section == 'texts', title: 'Texts'},
    					{id: 'admin', selected: app.user.ui.section == 'admin', title: 'Admin'}
                    ],
                    id: 'sectionButtons',
                    selectable: true
                }).css({
	                float: 'left',
                    margin: '4px'
	            });
	        return that;
		},
		sections: function() {
			var that = new Ox.Element();
			var $sections = [];
			app.$ui.sectionLists = [];
		    $.each(app.user.ui.sections, function(i, id) {
		        var menu = [];
		        if (id == 'my') {
		            menu = [
		                { id: 'new', title: 'New List...' },
		                { id: 'newfromselection', title: 'New List from Selection...' },
		                { id: 'newsmart', title: 'New Smart List...' },
		                { id: 'newfromresults', title: 'New Smart List from Results...' },
		                {},
		                { id: 'addselection', title: 'Add Selection to List...' }
		            ];
		        } else if (id == 'public') {
		            menu = [
		                { id: 'browse', title: 'More Public Lists...' },
		            ];
		        }
		        var $section = new Ox.CollapsePanel({
		            id: id,
		            menu: menu,
		            size: 16,
		            title: Ox.getObjectById(app.config.sections, id).title
		        });
		        $sections.push($section);
	            $section.$content.css({
	                height: app.user.lists[id].length * 16 + 'px'
	            });
	            app.$ui.sectionLists[i] = new Ox.TextList({
	                columns: [
                        {
                            align: 'left',
                            id: 'title',
                            operator: '+',
                            unique: true,
                            visible: true,
                            width: 184
                        },
                        {
                            align: 'right',
                            id: 'items',
                            operator: '-',
                            visible: true,
                            width: 40
                        },
	                    {
	                        align: 'left',
	                        format: function(value) {
	                            return $('<img>').attr({
                                    src: 'static/oxjs/build/png/ox.ui.modern/symbol' +
                                    (value ? 'Find' : 'None') + '.png'
                                });
	                        },
	                        id: 'query',
	                        operator: '+',
	                        visible: true,
	                        width: 16
	                    },
	                    {
	                        align: 'left',
	                        format: function(value) {
	                            return $('<img>').attr({
                                    src: 'static/oxjs/build/png/ox.ui.modern/symbol' +
                                    (value ? 'Publish' : 'None') + '.png'
                                });
	                        },
	                        id: 'public',
	                        operator: '+',
	                        visible: true,
	                        width: 16
	                    }
	                ],
	                max: 1,
	                min: 0,
	                request: function(data, callback) {
	                    if ($.isEmptyObject(data)) {
	                        callback({data: {items: app.user.lists[id].length}});
	                    } else {
	                        callback({data: {items: $.map(app.user.lists[id], function(v, i) {
	                            return $.extend(v, {
	                                query: v.query,
	                                public: v.public,
	                                items: v.items ? v.items.length.toString() : (v.title == '1960s' || v.title == 'All Movies' ? '?' : '100')
	                            });
	                        })}});
	                    }
                    },
                    sort: [
                        {key: 'title', operator: '+'}
                    ]
	            })
	            .css({
	                left: 0,
	                top: 0,
	                width: app.user.ui.sidebarSize + 'px',
	                height: app.user.lists[id].length * 16 + 'px'
	            })
	            .bindEvent('select', function(event, data) {
	                app.$ui.sectionLists.forEach(function($list, i_) {
	                    if (i != i_) {
	                        $list.options('selected', []);
	                    }
	                });
	            })
	            .appendTo($section.$content);
		    });
		    $.each($sections, function(i, $section) {
		        that.append($section);
		    });
			that.toggle = function() {
				
			}
			return that;
		},
        sectionSelect: function() {
		    var that = new Ox.Select({
		            id: 'sectionSelect',
                    items: [
    					{checked: app.user.ui.section == 'site', id: 'site', title: app.config.site.name},
                        {checked: app.user.ui.section == 'items', id: 'items', title: app.config.itemName.plural},
                        {checked: app.user.ui.section == 'texts', id: 'texts', title: 'Texts'},
    					{checked: app.user.ui.section == 'admin', id: 'admin', title: 'Admin'}
                    ]
                }).css({
	                float: 'left',
                    margin: '4px'
	            });
	        return that;
		},
        sortSelect: function() {
            var that = new Ox.Select({
                    id: 'sortSelect',
                    items: $.map(app.config.sortKeys, function(key) {
                        Ox.print('????', app.user.ui.sort.key, key.id)
                        return $.extend($.extend({}, key), {
                            checked: app.user.ui.sort[0].key == key.id,
                            title: 'Sort by ' + key.title
                        });
                    }),
                    width: 144
                })
                .css({
                    float: 'left',
                    margin: '4px 0 0 4px'
                })
                .bindEvent('change', function(event, data) {
                    var id = data.selected[0].id,
                        operator = getSortOperator(id);
                    /*
                    app.user.ui.sort[0] = {
                        key: id,
                        operator: operator
                    };
                    */
                    app.$ui.mainMenu.checkItem('sortMenu_sortmovies_' + id);
                    app.$ui.mainMenu.checkItem('sortMenu_ordermovies_' + (operator === '' ? 'ascending' : 'descending'));
                    //alert(id + ' ' + operator)
                    app.$ui.list.sortList(id, operator);
                    URL.set(Query.toString());
                });
            return that;
        },
		status: function(key, data) {
			var that = Ox.toTitleCase(key) + ': ' + [
		        Ox.formatNumber(data.items) + ' movie' + (data.items != 1 ? 's' : ''),
		        Ox.formatDuration(data.runtime, 'medium'),
		        data.files + ' file' + (data.files != 1 ? 's' : ''),
		        Ox.formatDuration(data.duration, 'short'),
		        Ox.formatValue(data.size, 'B'),
		        Ox.formatValue(data.pixels, 'px')
		    ].join(', ');
			return that;
		},
		statusbar: function() {
			var that = new Ox.Bar({
		            size: 16
		        })
		        .css({
		            textAlign: 'center'
		        })
		        .append(
		            new Ox.Element()
		                .css({
		                    marginTop: '2px',
		                    fontSize: '9px'
		                })
		                .append(
		                    app.$ui.total = new Ox.Element('span')
		                )
		                .append(
		                    new Ox.Element('span').html(' &mdash; ')
		                )
		                .append(
		                    app.$ui.selected = new Ox.Element('span')
		                )
		        );
		    return that;
		},
		toolbar: function() {
			var that = new Ox.Bar({
			        size: 24
			    }).css({
		            zIndex: 2 // fixme: remove later
		        });
		    app.user.ui.item && that.append(
		        app.$ui.backButton = ui.backButton()
		    );
			that.append(
			    app.$ui.viewSelect = ui.viewSelect() 
			);
			!app.user.ui.item && that.append(
			    app.$ui.sortSelect = ui.sortSelect()
			);
			that.append(
			    app.$ui.findElement = ui.findElement()
			);
			that.display = function() {
				app.$ui.rightPanel.replace(0, app.$ui.toolbar = ui.toolbar()); // fixme: remove later
			}
			return that;
		},
		viewSelect: function() {
			var that = new Ox.Select({
                    id: 'viewSelect',
                    items: !app.user.ui.item ? $.map(app.config.listViews, function(view) {
                        return $.extend($.extend({}, view), {
                            checked: app.user.ui.listView == view.id,
                            title: 'View ' + view.title
                        });
                    }) : $.map(app.config.itemViews, function(view) {
                        return $.extend($.extend({}, view), {
                            checked: app.user.ui.itemView == view.id,
                            title: view.title
                        });
                    }),
                    width: !app.user.ui.item ? 144 : 96
                })
                .css({
                    float: 'left',
                    margin: '4px 0 0 4px'
                })
                .bindEvent('change', !app.user.ui.item ? function(event, data) {
                    var id = data.selected[0].id;
                    app.user.ui.listView = id;
                    app.$ui.mainMenu.checkItem('viewMenu_movies_' + id);
                    app.$ui.contentPanel.replace(1, app.$ui.list = ui.list(id));
                    URL.set(Query.toString());
                } : function(event, data) {
                    var id = data.selected[0].id;
                    app.user.ui.itemView = id;
                    app.$ui.contentPanel.replace(1, app.$ui.item = ui.item());
                });
			return that;
		}
	}

    function autovalidateCode(value, blur, callback) {
        value = $.map(value.toUpperCase().split(''), function(v) {
            return /[0-9A-Z]/(v) ? v : null;
        }).join('');
        callback(value);
    }

    function autovalidateEmail(value, blur, callback) {
        value = $.map(value.toLowerCase().split(''), function(v, i) {
            return /[0-9a-z\.\+\-_@]/(v) ? v : null;
        }).join('');
        callback(value);
    }

    function autovalidateUsername(value, blur, callback) {
        var length = value.length;
        value = $.map(value.toLowerCase().split(''), function(v, i) {
            if (new RegExp('[0-9a-z' + ((i == 0 || (i == length - 1 && blur)) ? '' : '\-_') + ']')(v)) {
                return v
            } else {
                return null;
            }
        }).join('');
        $.each(['--', '-_', '_-', '__'], function(i, v) {
            while (value.indexOf(v) > -1) {
                value = value.replace(new RegExp(v, 'g'), v[0]);
            }
        })
        callback(value);
    }

    function getAlignment(key) { // fixme: make static
        return ['person', 'string', 'text', 'title'].indexOf(
            Ox.getObjectById(app.config.sortKeys, key).type
        ) > -1 ? 'left' : 'right';
    }

	function getGroupWidth(pos, panelWidth) {
	    var width = {};
	    width.list = Math.floor(panelWidth / 5) + (panelWidth % 5 > pos);
	    width.column = width.list - 40 - ($.browser.mozilla ? 16 : 12);
	    return width;
	}

    function getSortOperator(key) { // fixme: make static
        return ['person', 'string', 'text', 'title'].indexOf(
            Ox.getObjectById(app.config.sortKeys, key).type
        ) > -1 ? '' : '-';
    }

    function reloadGroups(i) {
        var query = Query.toObject();
        app.$ui.list.options({
            request: function(data, callback) {
                return pandora.api.find($.extend(data, {
                    query: query
                }), callback);
            }
        });
        $.each(app.ui.groups, function(i_, group_) {
            if (i_ != i) {
                Ox.print('setting groups request', i, i_)
                app.$ui.groups[i_].options({
                    request: function(data, callback) {
                        delete data.keys;
                        //alert(i_ + " Query.toObject " + JSON.stringify(Query.toObject(group_.id)))
                        return pandora.api.find($.extend(data, {
                            group: group_.id,
                            query: Query.toObject(group_.id)
                        }), callback);
                    }
                });
            }
        });
        history.pushState({}, '', Query.toString(query));
    }

    function resizeGroups(width) {
        var widths = $.map(app.ui.groups, function(v, i) {
            return getGroupWidth(i, width);
        });
        Ox.print('widths', widths);
        app.$ui.browser.size(0, widths[0].list).size(2, widths[4].list);
        app.$ui.groupsInnerPanel.size(0, widths[1].list).size(2, widths[3].list);
        $.each(app.$ui.groups, function(i, list) {
            list.resizeColumn('name', widths[i].column);
        });
    }

    function validateUser(key, existing) {
        existing = existing || false;
        var string = key == 'username' ? 'username' : 'e-mail address';
        return function(value, callback) {
            var valid = key == 'username' ? !!value.length : Ox.isValidEmail(value);
            valid ? pandora.api.findUser({
                key: key,
                value: value,
                operator: '='
            }, function(result) {
                var valid = existing == !!result.data.users.length;
                Ox.print(existing, result.data.users)
                callback({
                    message: existing ? 
                        'Unknown ' + string :
                        string[0].toUpperCase() + string.substr(1) + ' already exists',
                    valid: valid
                });
            }) : callback({
                message: (!value.length ? 'Missing' : 'Invalid') + ' ' + string,
                valid: false
            });
        };
    }

	var Query = (function() {

	    function constructFind(query) {
	        Ox.print('cF', query)
	        return /*encodeURI(*/$.map(query.conditions, function(v, i) {
	            if (!Ox.isUndefined(v.conditions)) {
	                return '[' + constructFind(v) + ']';
	            } else {
	                return v.value !== '' ? v.key + (v.key ? ':' : '') + constructValue(v.value, v.operator) : null;
	            }
	        }).join(query.operator)/*)*/;
	    }

	    function constructValue(value, operator) {
	        operator = operator.replace('=', '^$');
	        if (operator.indexOf('$') > -1) {
	            value = operator.substr(0, operator.length - 1) + value + '$'
	        } else {
	            value = operator + value;
	        }
	        return value;
	    }

	    function mergeFind() {
	    }

	    function parseFind(str) {
	        var find = {
	                conditions: [],
	                operator: ''
	            },
	            subconditions = str.match(/\[.*?\]/g) || [];
	        $.each(subconditions, function(i, v) {
	            subconditions[i] = v.substr(1, v.length - 2);
	            str = str.replace(v, '[' + i + ']');
	        });
	        if (str.indexOf(',') > -1) {
	            find.operator = '&';
	        } else if (str.indexOf('|') > -1) {
	            find.operator = '|';
	        }
	        Ox.print('pF', str, find.operator)
	        find.conditions = $.map(find.operator === '' ? [str] : str.split(find.operator == '&' ? ',' : '|'), function(v, i) {
	            Ox.print('v', v)
	            var ret, kv;
	            if (v[0] == '[') {
	                Ox.print('recursion', subconditions)
	                ret = parseFind(subconditions[parseInt(v.substr(1, v.length - 2))]);
	            } else {
	                kv = ((v.indexOf(':') > - 1 ? '' : ':') + v).split(':');
	                ret = $.extend({
	                    key: kv[0]
	                }, parseValue(kv[1]));
	            }
	            return ret;
	        });
	        return find;
	    }

	    function parseValue(str) {
	        var value = {
	                value: decodeURI(str),
	                operator: ''
	            };
	        if (value.value[0] == '!') {
	            value.operator = '!'
	            value.value = value.value.substr(1);
	        }
	        if ('^<>'.indexOf(value.value[0]) > -1) {
	            value.operator += value.value[0];
	            value.value = value.value.substr(1);
	        }
	        if (value.value.substr(-1) == '$') {
	            value.operator += '$';
	            value.value = value.value.substr(0, value.value.length - 1);
	        }
	        value.operator = value.operator.replace('^$', '=');
	        return value;
	    }

	    return {

	        fromString: function(str) {
	            var query = Ox.unserialize(str.substr(1)),
	                sort = [];
	            if ('find' in query) {
	                app.user.ui.findQuery = parseFind(query.find);
	                Ox.print('user.ui.findQuery', app.user.ui.findQuery)
	            }
	            if ('sort' in query) {
	                sort = query.sort.split(',')
	                app.user.ui.sort = $.map(query.sort.split(','), function(v, i) {
	                    var hasOperator = '+-'.indexOf(v[0]) > -1,
	                        key = hasOperator ? query.sort.substr(1) : query.sort,
	                        operator = hasOperator ? v[0].replace('+', '') : getSortOperator(key);
	                    return {
	                        key: key,
	                        operator: operator
	                    };
	                });
	            }
	            if ('view' in query) {
	                app.user.ui.listView = query.view;
	            }
	        },

	        toObject: function(groupId) {
	            Ox.print('tO', app.user.ui.findQuery.conditions)
	            // the inner $.merge() creates a clone
	            var conditions = $.merge(
	                    $.merge([], app.user.ui.listQuery.conditions),
	                    app.user.ui.findQuery.conditions
	                ),
	                operator;
	            $.merge(conditions, app.ui.groups ? $.map(app.ui.groups, function(v, i) {
                    if (v.id != groupId && v.query.conditions.length) {
                        return v.query.conditions.length == 1 ?
                            v.query.conditions : v.query;
                    }
                }) : []);
	            operator = conditions.length < 2 ? '' : ','; // fixme: should be &
	            Ox.print('>>', groupId, app.user.ui.find, conditions);
	            return {
	                conditions: conditions,
	                operator: operator
	            };
	        },

	        toString: function() {
	            Ox.print('tS', app.user.ui.find)
	            return '?' + Ox.serialize({
	                find: constructFind(Query.toObject()),
	                sort: app.user.ui.sort[0].operator + app.user.ui.sort[0].key,
	                view: app.user.ui.listView
	            });
	        }

	    };

	})();

    var URL = (function() {

        var old = {
                user: {
                    ui: {}
                }
            },
            regexps = {
                '^\\?': function(url) {
                    Query.fromString(url);
                    app.user.ui.section = 'items';
                    app.user.ui.item = '';
                },
    			'^(|about|faq|home|news|software|terms|tour)$': function(url) {
			        app.user.ui.section = 'site';
			        app.user.ui.sitePage = url;
    			},
    			'^(|find)$': function() {
			        app.user.ui.section = 'items';
			        app.user.ui.item = '';
    			},
    			'^(calendar|calendars|clips|icons|flow|map|maps|timelines)$': function() {
			        app.user.ui.section = 'items';
			        app.user.ui.item = '';
			        app.user.ui.listView = url;
    			},
                '^[0-9A-Z]': function() {
    				var split = url.split('/'),
					    item = split[0],
		        	    view = new RegExp(
		        	        '^(calendar|clips|files|info|map|player|statistics|timeline)$'
		        	    )(split[1]) || app.user.ui.itemView;
    				app.user.ui.section = 'items';
    				app.user.ui.item = item;
    				app.user.ui.itemView = view;
    			},
    			'^texts$': function() {
    			    app.user.ui.section = 'texts';
    			},
    			'^admin$': function() {
    			    app.user.ui.section = 'admin';
    			}
            };

        return {

            set: function(url) {
                history.pushState({}, '', '/' + url);
                old.user.ui = $.extend({}, app.user.ui); // make a clone
                URL.update();
            },

            parse: function() {
                url = document.location.pathname.substr(1) +
                        document.location.search + document.location.hash;
                $.each(regexps, function(re, fn) {
                    Ox.print(url, 're', re)
                    re = new RegExp(re);
        			if (re(url)) {
        				fn(url);
        				return false;
        			}
                });
            },

            update: function() {
                URL.parse();
                if (app.user.ui.section == 'items') {
                    if (!old.user.ui.item) {
                        if (!app.user.ui.item) {

                        } else {
                            app.$ui.mainPanel.replace(1, app.$ui.rightPanel = ui.rightPanel());
                            //app.$ui.rightPanel.replace(0, app.$ui.toolbar = ui.toolbar());
                            //ui.item().display();
                        }
                    } else {
                        if (!app.user.ui.item) {
                            app.$ui.mainPanel.replace(1, app.$ui.rightPanel = ui.rightPanel());
                            //ui.list(app.user.ui.listView).display();
                        } else {
                            app.$ui.contentPanel.replace(1, ui.item());
                        }
                    }
                }
                delete old.user.ui;
            }

        }

    }());

	var url = function(url) {
		var currentURL = document.location.pathname.substr(1) + document.location.hash,
			match = false;
			regexps = {
				'^[0-9A-Z]': function() {
					var split = url.split('/'),
						id = split[0],
			        	view = split[1] || app.user.ui.itemView;
			        ui.item(id, view);
				}
			};
	    if (!url)
	        url = currentURL;
	    else {
	        if (url != currentURL) {
	            var title = document.title + ' ' + url;
	            history.pushState({}, title, url);
	        } else {
	            //FIXME: this is a workaround since open gets called twice
	            //       due to a bug with double click
	            Ox.print('ignore double call of app.url, double click need to be fixed');
	            return;
	        }
	    }
		$.each(regexps, function(re, fn) {
			re = new RegExp(re);
			if (re(url)) {
				fn();
				match = true;
				return false;
			}
		});
		if (!match) {
			Query.fromString(location.hash.substr(1));
	        app.$ui.rightPanel.replace(1, ui.contentPanel());
		}
	}

	load();

});



// Objects

/*
    // Menu


    Ox.Event.bind('change_viewmovies', function(event, data) {
        app.$ui.viewSelect.selectItem(data.id);
    });
    Ox.Event.bind('change_find', function(event, data) {
        app.$ui.findInput.changeLabel(data.id);
    });
    Ox.Event.bind('click_query', function(event, data) {
        var $dialog = new Ox.Dialog({
            buttons: [
                {
                    click: function() {
                        $dialog.close();
                    },
                    id: 'close',
                    title: 'Close',
                    value: 'Close'
                }
            ],
            id: 'query',
            title: 'Query'
        }).append(Query.toString() + '<br/><br/>' + JSON.stringify(Query.toObject())).open();
    });

    // Resize

    Ox.Event.bind('click_show_query', function(event, data) {
        var query = constructQuery(),
            html = 'Conditions<br/><br/>' + $.map(query.conditions, function(v) {
                return v.key + ' ' + v.operator + ' ' + v.value;
            }).join('<br/>') + '<br/><br/>Operator: ' + query.operator,
            $dialog = new Ox.Dialog({
                buttons: [
                    {
                        value: 'Close',
                        click: function() {
                            $dialog.close();
                        }
                    }
                ],
                title: 'Show Query'
            })
            .append(html)
            .open();
    });

// Functions
*/

/*

app.constructAnnotations = function() {
    var $annotations = new Ox.Element();
    $.each(app.constructBins(), function(i, $bin) {
        $annotations.append($bin);
    });
    return $annotations;
}

app.constructApp = function() {
    return new Ox.SplitPanel({
        elements: [
            {
                element: app.$ui.mainMenu,
                size: 20
            },
            {
                element: app.$ui.mainPanel = new Ox.SplitPanel({
                    elements: [
                        {
                            collapsible: true,
                            element: app.$ui.leftPanel = new Ox.SplitPanel({
                                    elements: [
										{
											element: app.$ui.sectionbar,
											size: 24
										},
                                        {
                                            element: app.$ui.lists.options({
                                                id: 'listsPanel'
                                            })
                                        },
                                        {
                                            collapsible: true,
                                            element: app.$ui.info.options({
                                                id: 'infoPanel'
                                            }),
                                            size: app.user.ui.listsSize / app.ui.infoRatio + 16
                                        }
                                    ],
                                    id: 'leftPanel',
                                    orientation: 'vertical'
                                })
                                .bindEvent('resize', function(event, data) {
                                    Ox.print('resize', data, data / app.ui.infoRatio + 16);
                                    app.$ui.leftPanel.size('infoPanel', Math.round(data / app.ui.infoRatio) + 16);
                                }),
                            resizable: true,
                            resize: [128, 256, 384, 512],
                            size: app.user.ui.listsSize
                        },
                        {
                            element: app.$ui.rightPanel = new Ox.SplitPanel({
                                elements: [
                                    {
                                        element: app.$ui.toolbar.css({ zIndex: 2 }), // fixme: remove later
                                        size: 24
                                    },
                                    {
                                        element: app.constructContentPanel()
                                    },
                                    {
                                        element: app.$ui.statusbar,
                                        size: 16
                                    }
                                ],
                                id: 'rightPanel',
                                orientation: 'vertical'
                            })
                            .bindEvent('resize', function(event, data) {
                                var widths = $.map(app.ui.groups, function(v, i) {
                                    return app.getGroupWidth(i, data);
                                });
                                Ox.print('widths', widths);
                                app.$ui.groupsOuterPanel.size(0, widths[0].list).size(2, widths[4].list);
                                app.$ui.groupsInnerPanel.size(0, widths[1].list).size(2, widths[3].list);
                                $.each(app.$ui.groups, function(i, list) {
                                    list.resizeColumn('name', widths[i].column);
                                });
                                app.$ui.list.size();
                            })
                        }
                    ],
                    orientation: 'horizontal'
                })
            }
        ],
        orientation: 'vertical'
    });
}

app.constructBins = function() {
    var $bins = [];
    $.each(app.config.layers, function(i, layer) {
        var $bin = new Ox.CollapsePanel({
            id: layer.id,
            size: 16,
            title: layer.title
        });
        $bins.push($bin);
        $bin.$content.append(
            $('<div>').css({ height: '20px' }).append(
                $('<div>').css({ float: 'left', width: '16px', height: '16px', margin: '1px'}).append(
                    $('<img>').attr({ src: 'static/oxjs/build/png/ox.ui.modern/iconFind.png' }).css({ width: '16px', height: '16px', border: 0, background: 'rgb(64, 64, 64)', WebkitBorderRadius: '2px' })
                )
            ).append(
                $('<div>').css({ float: 'left', width: '122px', height: '14px', margin: '2px' }).html('Foo')
            ).append(
                $('<div>').css({ float: 'left', width: '40px', height: '14px', margin: '2px', textAlign: 'right' }).html('23')
            )
        );
    });
    return $bins;	
}

app.constructGroups = function() {
    var $groups = [],
        panelWidth = app.$document.width() - app.user.ui.listsSize - 1;
    app.ui.groups = $.map(app.config.groups, function(id, i) {
        var title = Ox.getObjectById(app.config.sortKeys, id).title,
            width = app.getGroupWidth(i, panelWidth);
        return {
            id: id,
            element: $groups[i] = new Ox.TextList({
                    columns: [
                        {
                            align: 'left',
                            id: 'name',
                            operator: id == 'year' ? '-' : '+',
                            title: title,
                            unique: true,
                            visible: true,
                            width: width.column
                        },
                        {
                            align: 'right',
                            id: 'items',
                            operator: '-',
                            title: '#',
                            visible: true,
                            width: 40
                        }
                    ],
                    id: 'group_' + id,
                    request: function(data, callback) {
                        Ox.print('sending request', data)
                        delete data.keys;
                        return pandora.api.find($.extend(data, {
                            group: id,
                            query: app.Query.toObject()
                        }), callback);
                    },
                    sort: [
                        {
                            key: id == 'year' ? 'name' : 'items',
                            operator: '-'
                        }
                    ]
                })
                .bindEvent('select', function(event, data) {
                    Ox.print('select', i)
                    var group = app.ui.groups[i],
                        query;
                    app.ui.groups[i].query.conditions = $.map(data.ids, function(v) {
                        return {
                            key: group.id,
                            value: v,
                            operator: '='
                        };
                    });
                    query = app.Query.toObject();
                    app.$ui.list.options({
                        request: function(data, callback) {
                            return pandora.api.find($.extend(data, {
                                query: query
                            }), callback);
                        }
                    });
                    $.each(app.ui.groups, function(i_, group_) {
                        if (i_ != i) {
                            Ox.print('setting groups request', i, i_)
                            app.$ui.groups[i_].options({
                                request: function(data, callback) {
                                    delete data.keys;
                                    return pandora.api.find($.extend(data, {
                                        group: group_.id,
                                        query: app.Query.toObject(group_.id)
                                    }), callback);
                                }
                            });
                        }
                    });
                    history.pushState({}, '', '/#' + app.Query.toString(query));
                }),
            query: {
                conditions: [],
                operator: '|'
            },
            size: width.list,
            title: title
        };
        Ox.print('--OK--');
    });
    return $groups;
}

app.constructInfo = function() {
    return new Ox.Element()
        .append(
            app.$ui.infoStill = new Ox.Element('img')
                .css({
                    position: 'absolute',
                    left: 0,
                    top: 0
                })
        )
        .append(
            app.$ui.infoTimeline = new Ox.Element('img')
                .css({
                    position: 'absolute',
                    left: 0,
                    bottom: 0,
                    height: '16px',
                })
        );
}

app.constructContentPanel = function(listView) {
    listView = listView || app.user.ui.listView;
    return app.$ui.contentPanel = new Ox.SplitPanel({
        elements: [
            {
                collapsible: true,
                element: app.$ui.groupsOuterPanel = new Ox.SplitPanel({
                    elements: [
                        {
                            element: app.$ui.groups[0],
                            size: app.ui.groups[0].size
                        },
                        {
                            element: app.$ui.groupsInnerPanel = new Ox.SplitPanel({
                                elements: [
                                    {
                                        element: app.$ui.groups[1],
                                        size: app.ui.groups[1].size
                                    },
                                    {
                                        element: app.$ui.groups[2],
                                    },
                                    {
                                        element: app.$ui.groups[3],
                                        size: app.ui.groups[3].size
                                    }
                                ],
                                orientation: 'horizontal'
                            })
                        },
                        {
                            element: app.$ui.groups[4],
                            size: app.ui.groups[4].size
                        },
                    ],
                    id: 'browser',
                    orientation: 'horizontal'
                })
                .bindEvent('resize', function(event, data) {
                    Ox.print('resizing groups...')
                    $.each(app.$ui.groups, function(i, list) {
                        list.size();
                    });
                }),
                resizable: true,
                resize: [96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256],
                size: app.user.ui.groupsSize
            },
            {
                element: app.$ui.list = app.constructList(listView)
            }
        ],
        orientation: 'vertical'
    });
}

app.constructItem = function(id, view) {

}

app.constructList = function(view) {


}

app.constructLists = function() {
    var $lists = new Ox.Element;
    $.each(app.$ui.sections, function(i, $section) {
        $lists.append($section);
    });
    return $lists;
}

app.constructMainMenu = function() {
    return new Ox.MainMenu({
            extras: [
                app.$ui.loadingIcon = new Ox.LoadingIcon({
                    size: 'medium'
                })
            ],
            id: 'mainMenu',
            menus: [
                { id: app.config.site.id + 'Menu', title: app.config.site.name, items: [
                    { id: 'about', title: 'About' },
                    {},
                    { id: 'home', title: 'Home Screen' },
                    { id: 'faq', title: 'Frequently Asked Questions' },
                    { id: 'tos', title: 'Terms of Service' },
                    {},
                    { id: 'contact', title: 'Contact...' }
                ] },
                { id: 'userMenu', title: 'User', items: [
                    { id: 'username', title: 'User: not logged in', disabled: true },
                    {},
                    { id: 'preferences', title: 'Preferences...', disabled: true, keyboard: 'control ,' },
                    {},
                    { id: 'register', title: 'Create an Account...' },
                    { id: 'loginlogout', title: ['Login...', 'Logout...'] }
                ] },
                { id: 'listMenu', title: 'List', items: [
                    { id: 'history', title: 'History', items: [
                        { id: 'allmovies', title: 'All Movies' }
                    ] },
                    { id: 'lists', title: 'View List', items: [
                        { id: 'favorites', title: 'Favorites' }
                    ] },
                    { id: 'features', title: 'View Feature', items: [
                        { id: 'situationistfilm', title: 'Situationist Film' },
                        { id: 'timelines', title: 'Timelines' }
                    ] },
                    {},
                    { id: 'newlist', title: 'New List...', keyboard: 'control n' },
                    { id: 'newlistfromselection', title: 'New List from Selection...', disabled: true, keyboard: 'shift control n' },
                    { id: 'newsmartlist', title: 'New Smart List...', keyboard: 'alt control n' },
                    { id: 'newsmartlistfromresults', title: 'New Smart List from Results...', keyboard: 'shift alt control n' },
                    {},
                    { id: 'addmovietolist', title: ['Add Selected Movie to List...', 'Add Selected Movies to List...'], disabled: true },
                    {},
                    { id: 'setposterframe', title: 'Set Poster Frame', disabled: true }
                ]},
                { id: 'editMenu', title: 'Edit', items: [
                    { id: 'undo', title: 'Undo', disabled: true, keyboard: 'control z' },
                    { id: 'redo', title: 'Redo', disabled: true, keyboard: 'shift control z' },
                    {},
                    { id: 'cut', title: 'Cut', disabled: true, keyboard: 'control x' },
                    { id: 'copy', title: 'Copy', disabled: true, keyboard: 'control c' },
                    { id: 'paste', title: 'Paste', disabled: true, keyboard: 'control v' },
                    { id: 'delete', title: 'Delete', disabled: true, keyboard: 'delete' },
                    {},
                    { id: 'selectall', title: 'Select All', disabled: true, keyboard: 'control a' },
                    { id: 'selectnone', title: 'Select None', disabled: true, keyboard: 'shift control a' },
                    { id: 'invertselection', title: 'Invert Selection', disabled: true, keyboard: 'alt control a' }
                ] },
                { id: 'viewMenu', title: 'View', items: [
                    { id: 'movies', title: 'View Movies', items: [
                        { group: 'viewmovies', min: 0, max: 1, items: $.map(app.config.listViews, function(view, i) {
                            return $.extend({
                                checked: app.user.ui.listView == view.id,
                            }, view);
                        }) },
                    ]},
                    { id: 'icons', title: 'Icons', items: [
                        { id: 'poster', title: 'Poster' },
                        { id: 'still', title: 'Still' },
                        { id: 'timeline', title: 'Timeline' }
                    ] },
                    { id: 'info', title: 'Info', items: [
                        { id: 'poster', title: 'Poster' },
                        { id: 'video', title: 'Video' }
                    ] },
                    {},
                    { id: 'openmovie', title: ['Open Movie', 'Open Movies'], disabled: true, items: [
                        { group: 'movieview', min: 0, max: 1, items: $.map(app.config.itemViews, function(view, i) {
                            return $.extend({
                                checked: app.user.ui.itemView == view.id,
                            }, view);
                        }) },
                    ]},
                    {},
                    { id: 'lists', title: 'Hide Lists', keyboard: 'shift l' },
                    { id: 'info', title: 'Hide Info', keyboard: 'shift i' },
                    { id: 'groups', title: 'Hide Groups', keyboard: 'shift g' },
                    { id: 'movies', title: 'Hide Movies', disabled: true, keyboard: 'shift m' }
                ]},
                { id: 'sortMenu', title: 'Sort', items: [
                    { id: 'sortmovies', title: 'Sort Movies by', items: [
                        { group: 'sortmovies', min: 1, max: 1, items: $.map(app.config.sortKeys, function(key, i) {
                            return $.extend({
                                checked: app.user.ui.sort[0].key == key.id,
                            }, key);
                        }) }
                    ] },
                    { id: 'ordermovies', title: 'Order Movies', items: [
                        { group: 'ordermovies', min: 1, max: 1, items: [
                            { id: 'ascending', title: 'Ascending', checked: app.user.ui.sort[0].operator === '' },
                            { id: 'descending', title: 'Descending', checked: app.user.ui.sort[0].operator == '-' }
                        ]}
                    ] },
                    { id: 'advancedsort', title: 'Advanced Sort...', keyboard: 'shift control s' },
                    {},
                    { id: 'groupsstuff', title: 'Groups Stuff' }
                ] },
                { id: 'findMenu', title: 'Find', items: [
                    { id: 'find', title: 'Find', items: [
                        { group: 'find', min: 1, max: 1, items: $.map(app.config.findKeys, function(key, i) {
                            return $.extend({
                                checked: app.user.ui.findQuery.conditions.length && 
                                        (app.user.ui.findQuery.conditions[0].key == key.id ||
                                        (app.user.ui.findQuery.conditions[0].key === '' && key.id == 'all')),
                            }, key)
                        }) }
                    ] },
                    { id: 'advancedfind', title: 'Advanced Find...', keyboard: 'shift control f' }
                ] },
                { id: 'dataMenu', title: 'Data', items: [
                    { id: 'titles', title: 'Manage Titles...' },
                    { id: 'names', title: 'Manage Names...' },
                    {},
                    { id: 'posters', title: 'Manage Stills...' },
                    { id: 'posters', title: 'Manage Posters...' },
                    {},
                    { id: 'places', title: 'Manage Places...' },
                    { id: 'events', title: 'Manage Events...' },
                    {},
                    { id: 'users', title: 'Manage Users...' },
                    { id: 'lists', title: 'Manage Lists...' },
                ] },
                { id: 'codeMenu', title: 'Code', items: [
                    { id: 'download', title: 'Download' },
                    { id: 'contribute', title: 'Contribute' },
                    { id: 'report', title: 'Report a Bug' },
                ] },
                { id: 'helpMenu', title: 'Help', items: [
                    { id: 'help', title: app.config.site.name + ' Help', keyboard: 'shift ?' }
                ] },
                { id: 'debugMenu', title: 'Debug', items: [
                    { id: 'query', title: 'Show Query' }
                ] },
                { id: 'testMenu', title: 'Test', items: [
                    { group: 'foogroup', items: [
                        { id: 'item1', title: 'Item 1' },
                        { id: 'item2', title: 'Item 2' }
                    ] }
                ] }
            ]
        })
        .bindEvent({
            change: function(event, data) {
                if (data.id == 'find') {
                    var id = data.checked[0].id;
                    app.$ui.findSelect.selectItem(id);
                } else if (data.id == 'movieview') {
                    var view = data.checked[0].id;
                    var id = document.location.pathname.split('/')[1];
                    if (view == 'info')
                        app.url('/'+id+'/info');
                    else
                        app.url('/'+id);
                } else if (data.id == 'ordermovies') {
                    var id = data.checked[0].id;
                    app.$ui.list.sortList(user.ui.sort[0].key, id == 'ascending' ? '' : '-');
                } else if (data.id == 'sortmovies') {
                    var id = data.checked[0].id,
                        operator = Ox.getObjectById(app.config.sortKeys, id).operator;
                    app.$ui.mainMenu.checkItem('sortMenu_ordermovies_' + (operator === '' ? 'ascending' : 'descending'));
                    app.$ui.list.sortList(id, operator);
                } else if (data.id == 'viewmovies') {
                    var view = data.checked[0].id;
                    app.url('/#view='+view);
                }
            },
            click: function(event, data) {
                if (data.id == 'about') {
                    var $dialog = new Ox.Dialog({
                        buttons: [
                            {
                                click: function() {
                                    $dialog.close();
                                },
                                id: 'close',
                                title: 'Close'
                            }
                        ],
                        id: 'about',
                        title: 'About'
                    }).open();
                } else if (data.id == 'home') {
                    var $dialog = new Ox.Dialog({
                        buttons: [
                            {
                                click: function() {
                                    $dialog.close();
                                },
                                id: 'close',
                                title: 'Close'
                            }
                        ],
                        height: 498,
                        id: 'home',
                        title: app.options('name'),
                        width: 800
                    }).open();
                } else if (data.id == 'loginlogout') {
                    var $form = new Ox.Form({
                            error: 'Unknown username or wrong password',
                            id: 'login',
                            items: [
                                {
                                    element: new Ox.Input({
                                        autovalidate: function(value, blur, callback) {
                                            var length = value.length;
                                            value = $.map(value.toLowerCase().split(''), function(v, i) {
                                                if (new RegExp('[a-z0-9' + ((i == 0 || (i == length - 1 && blur)) ? '' : '\- ') + ']')(v)) {
                                                    return v
                                                } else {
                                                    return null;
                                                }
                                            }).join('');
                                            $.each(['--', '- ', ' -', '--'], function(i, v) {
                                                while (value.indexOf(v) > -1) {
                                                    value = value.replace(new RegExp(v, 'g'), v[0]);
                                                }
                                            })
                                            callback(value);
                                        },
                                        id: 'username',
                                        label: 'Username',
                                        labelWidth: 120,
                                        validate: function(value, callback) {
                                            pandora.api.findUser({
                                                key: 'username',
                                                value: value,
                                                operator: '='
                                            }, function(result) {
                                                Ox.print('result', result)
                                                var valid = result.data.users.length == 1;
                                                callback({
                                                    message: 'Unknown Username',
                                                    valid: valid
                                                });
                                            });
                                        },
                                        width: 300
                                    })
                                },
                                {
                                    element: new Ox.Input({
                                        id: 'password',
                                        label: 'Password',
                                        labelWidth: 120,
                                        type: 'password',
                                        validate: /.+/,
                                        width: 300
                                    })
                                }
                            ],
                            submit: function(data, callback) {
                                pandora.api.login(data, function(result) {
                                    if (result.status.code == 200) {
                                        $dialog.close();
                                        app.user = result.data.user;
                                        app.$ui.mainMenu.getItem('username').options({
                                            title: 'User: ' + app.user.name
                                        });
                                        app.$ui.mainMenu.getItem('preferences').options({
                                            disabled: false
                                        });
                                        app.$ui.mainMenu.getItem('register').options({
                                            disabled: true
                                        });
                                    } else {
                                        callback([{ id: 'password', message: 'Incorrect Password' }]);
                                    }
                                });
                            }
                        })
                        .bindEvent({
                            validate: function(event, data) {
                                $dialog[(data.valid ? 'enable' : 'disable') + 'Button']('signin');
                            }
                        }),
                        $dialog = new Ox.Dialog({
                            buttons: [
                                [
                                    {
                                        click: function() {

                                        },
                                        id: 'signup',
                                        title: 'Sign up...'
                                    },
                                    {
                                        click: function() {

                                        },
                                        id: 'reset',
                                        title: 'Reset Password...'
                                    }
                                ],
                                [
                                    {
                                        click: function() {
                                            $dialog.close();
                                            app.$ui.mainMenu.getItem('loginlogout').toggleTitle();
                                        },
                                        id: 'cancel',
                                        title: 'Cancel'
                                    },
                                    {
                                        click: $form.submit,
                                        disabled: true,
                                        id: 'signin',
                                        title: 'Sign in'
                                    }
                                ]
                            ],
                            id: 'login',
                            minWidth: 332,
                            title: 'Sign in',
                            width: 332
                        }).append($form).open();
                } else if (data.id == 'places') {
                    var $manage = new Ox.SplitPanel({
                            elements: [
                                {
                                    collapsible: true,
                                    element: new Ox.SplitPanel({
                                        elements: [
                                            {
                                                element: new Ox.Toolbar({
                                                    orientation: 'horizontal',
                                                    size: 44
                                                }).append(
                                                    app.$ui.findPlacesElement = new Ox.FormElementGroup({
                                                        elements: [
                                                            app.$ui.findPlacesSelect = new Ox.Select({
                                                                    id: 'findPlacesSelect',
                                                                    items: [
                                                                        { id: 'name', title: 'Find: Name' },
                                                                        { id: 'region', title: 'Find: Region' },
                                                                        { id: 'user', title: 'Find: User' }
                                                                    ],
                                                                    overlap: 'right',
                                                                    type: 'image'
                                                                })
                                                                .bindEvent({
                                                                    change: function(event, data) {
                                                                        app.$ui.findPlacesSelect.loseFocus();
                                                                        app.$ui.findPlacesInput.options({
                                                                            placeholder: data.selected[0].title
                                                                        });
                                                                    }
                                                                }),
                                                            app.$ui.findPlacesInput = new Ox.Input({
                                                                clear: true,
                                                                id: 'findPlacesInput',
                                                                placeholder: 'Find: Name',
                                                                width: 234
                                                            })
                                                        ],
                                                        id: 'findPlacesElement'
                                                    }) 
                                                    .css({
                                                        float: 'left',
                                                        margin: '4px'
                                                    })
                                                ).append(
                                                    app.$ui.sortPlacesSelect = new Ox.Select({
                                                        id: 'sortPlacesSelect',
                                                        items: [
                                                            { id: 'name', title: 'Sort by Name', checked: true },
                                                            { id: 'region', title: 'Sort by Region' },
                                                            { id: 'size', title: 'Sort by Size' },
                                                            { id: 'latitude', title: 'Sort by Latitude' },
                                                            { id: 'longitude', title: 'Sort by Longitude' },
                                                            { id: 'clips', title: 'Sort by Number of Clips' },
                                                            { id: 'user', title: 'Sort by User' },
                                                            { id: 'datecreated', title: 'Sort by Date Added' },
                                                            { id: 'datemodified', title: 'Sort by Date Modified' }
                                                        ],
                                                        width: 246
                                                    })
                                                    .css({
                                                        float: 'left',
                                                        margin: '0 4px 4px 4px'
                                                    })
                                                ),
                                                size: 44
                                            },
                                            {
                                                element: new Ox.Element('div')
                                            },
                                            {
                                                element: new Ox.Toolbar({
                                                    orientation: 'horizontal',
                                                    size: 16
                                                }),
                                                size: 16
                                            }
                                        ],
                                        orientation: 'vertical'
                                    }),
                                    size: 256
                                },
                                {
                                    element: new Ox.SplitPanel({
                                        elements: [
                                            {
                                                element: new Ox.Toolbar({
                                                    orientation: 'horizontal',
                                                    size: 24
                                                }).append(
                                                    app.$ui.labelsButton = new Ox.Button({
                                                            id: 'labelsButton',
                                                            title: [
                                                                {id: 'show', title: 'Show Labels'},
                                                                {id: 'hide', title: 'Hide Labels'}
                                                            ],
                                                            width: 96
                                                        })
                                                        .css({
                                                            float: 'left',
                                                            margin: '4px'
                                                        })
                                                ).append(
                                                    app.$ui.findMapInput = new Ox.Input({
                                                        clear: true,
                                                        id: 'findMapInput',
                                                        placeholder: 'Find on Map',
                                                        width: 192
                                                    })
                                                    .css({
                                                        float: 'right',
                                                        margin: '4px'
                                                    })
													.bindEvent({
														submit: function(event, data) {
															app.$ui.map.find(data.value, function(location) {
																app.$ui.placeNameInput.options({
																	disabled: false,
																	value: location.name
																});
																app.$ui.placeAliasesInput.options({
																	disabled: false
																});
																app.$ui.placeGeonameLabel.options({
																	disabled: false,
																	title: location.names.join(', ')
																});
																app.$ui.removePlaceButton.options({
																	disabled: false
																});
																app.$ui.addPlaceButton.options({
																	disabled: false
																});
															});
														}
													})
                                                ),
                                                size: 24
                                            },
                                            {
                                                element: app.$ui.map = new Ox.Map({
                                                    	places: ['Boston', 'Brussels', 'Barcelona', 'Berlin', 'Beirut', 'Bombay', 'Bangalore', 'Beijing']
                                                	})
													.css({
                                                    	left: 0,
                                                    	top: 0,
                                                    	right: 0,
                                                    	bottom: 0
                                                	})
													.bindEvent({
														select: function(event, location) {
															app.$ui.placeNameInput.options({
																disabled: false,
																value: location.name
															});
															app.$ui.placeAliasesInput.options({
																disabled: false
															});
															app.$ui.placeGeonameLabel.options({
																disabled: false,
																title: location.names.join(', ')
															});
															app.$ui.removePlaceButton.options({
																disabled: false
															});
															app.$ui.addPlaceButton.options({
																disabled: false
															});
														}
													})
                                            },
                                            {
                                                element: app.$ui.bottomBar = new Ox.Toolbar({
                                                    orientation: 'horizontal',
                                                    size: 24
                                                })
												.append(
													app.$ui.placeNameInput = new Ox.Input({
														disabled: true,
														id: 'placeName',
														placeholder: 'Name',
														width: 128
													})
													.css({
														float: 'left',
														margin: '4px 0 0 4px'
													})
												)
												.append(
													app.$ui.placeAliasesInput = new Ox.Input({
														disabled: true,
														id: 'aliases',
														placeholder: 'Aliases',
														width: 128
													})
													.css({
														float: 'left',
														margin: '4px 0 0 4px'
													})
												)
												.append(
													app.$ui.placeGeonameLabel = new Ox.Label({
														disabled: true,
														id: 'placeGeoname',
														title: 'Geoname',
														width: parseInt(app.$document.width() * 0.8) - 256 - 256 - 32 - 24
													})
													.css({
                                                        float: 'left',
                                                        margin: '4px 0 0 4px'
                                                    })
												)
												.append(
                                                    app.$ui.addPlaceButton = new Ox.Button({
														disabled: true,
                                                        id: 'addPlaceButton',
                                                        title: 'add',
														type: 'image'
                                                    })
                                                    .css({
                                                        float: 'right',
                                                        margin: '4px 4px 0 0'
                                                    })
                                                )
												.append(
                                                    app.$ui.removePlaceButton = new Ox.Button({
														disabled: true,
                                                        id: 'removePlaceButton',
                                                        title: 'remove',
														type: 'image'
                                                    })
                                                    .css({
                                                        float: 'right',
                                                        margin: '4px 4px 0 0'
                                                    })
                                                ),
                                                size: 24
                                            }
                                        ],
                                        orientation: 'vertical'
                                    })
                                }
                            ],
                            orientation: 'horizontal'
                        }).css({
                            top: '24px',
                            bottom: '24px',
                        }),
                        $dialog = new Ox.Dialog({
                            buttons: [
                                {
                                    click: function() {
                                        $dialog.close();
                                    },
                                    id: 'close',
                                    title: 'Close',
                                    value: 'Close'
                                }
                            ],
                            height: parseInt(app.$document.height() * 0.8),
                            id: 'places',
                            minHeight: 400,
                            minWidth: 600,
                            padding: 0,
                            title: 'Manage Places',
                            width: parseInt(app.$document.width() * 0.8)
                        }).css({
                            overflow: 'hidden'
                        }).append($manage).open();
                }
            }
        });
}

app.constructSectionbar = function() {
	return new Ox.Bar({
            size: 24
        })
        .append(
            app.$ui.sectionButtons = new Ox.ButtonGroup({
                    buttons: [
						{id: 'site', title: app.config.site.name},
                        {id: 'items', title: app.config.itemName.plural},
                        {id: 'texts', title: 'Texts'},
						{id: 'admin', title: 'Admin', type: 'image'}
                    ],
                    id: 'sectionButtons',
                    selectable: true
                })
                .css({
                    float: 'left',
	                margin: '4px'
                })
        );
}

app.constructSections = function() {
    var $sections = [];
    $.each(app.user.ui.sections, function(i, id) {
        var $section = new Ox.CollapsePanel({
            id: id,
            size: 16,
            title: Ox.getObjectById(app.config.sections, id).title
        });
        $sections.push($section);
        $section.$content.append(
            $('<div>').css({ height: '20px' }).append(
                $('<div>').css({ float: 'left', width: '16px', height: '16px', margin: '1px'}).append(
                    $('<img>').attr({ src: 'static/oxjs/build/png/ox.ui.modern/iconFind.png' }).css({ width: '16px', height: '16px', border: 0, background: 'rgb(64, 64, 64)', WebkitBorderRadius: '2px' })
                )
            ).append(
                $('<div>').css({ float: 'left', width: '122px', height: '14px', margin: '2px' }).html('Foo')
            ).append(
                $('<div>').css({ float: 'left', width: '40px', height: '14px', margin: '2px', textAlign: 'right' }).html('23')
            )
        );
    });
    return $sections;
}

app.constructStatusbar = function() {
    return new Ox.Bar({
            size: 16
        })
        .css({
            textAlign: 'center'
        })
        .append(
            new Ox.Element()
                .css({
                    marginTop: '2px',
                    fontSize: '9px'
                })
                .append(
                    app.$ui.total = new Ox.Element('span')
                )
                .append(
                    new Ox.Element('span').html(' &mdash; ')
                )
                .append(
                    app.$ui.selected = new Ox.Element('span')
                )
        );
}

app.constructStatus = function(key, data) {
    return Ox.toTitleCase(key) + ': ' + [
        Ox.formatNumber(data.items) + ' movie' + (data.items != 1 ? 's' : ''),
        Ox.formatDuration(data.runtime, 'medium'),
        data.files + ' file' + (data.files != 1 ? 's' : ''),
        Ox.formatDuration(data.duration, 'short'),
        Ox.formatValue(data.size, 'B'),
        Ox.formatValue(data.pixels, 'px')
    ].join(', ');
}

app.constructToolbar = function() {
    return new Ox.Bar({
            size: 24
        })
        .append(
            app.$ui.groupsButton = new Ox.Button({
                    id: 'groupsButton',
                    title: [
                        {id: 'show', title: 'Show Groups'},
                        {id: 'hide', title: 'Hide Groups'}
                    ],
                    width: 96
                })
                .css({
                    float: 'left',
                    margin: '4px'
                })
        )
        .append(
            app.$ui.viewSelect = new Ox.Select({
                    id: 'viewSelect',
                    items: $.map(app.config.listViews, function(view, i) {
                        view.title = 'View ' + view.title
                        return $.extend({
                            checked: app.user.ui.listView == view.id,
                        }, view);
                    }),
                    width: 144
                })
                .css({
                    float: 'left',
                    margin: '4px'
                })
                .bindEvent('change', function(event, data) {
                    var id = data.selected[0].id;
                    app.$ui.mainMenu.checkItem('viewMenu_movies_' + id);
                    //$ui.list.$element.replaceWith(constructList(id));
                    Ox.print('change ... id', id, list = app.constructList(id), list.options(), list.options('id'))
                    //$ui.contentPanel.replace('list', constructList(id));
                    app.$ui.contentPanel.replace(1, app.constructList(id));
                })
        )
        .append(
            app.$ui.findElement = new Ox.FormElementGroup({
                    elements: [
                        app.$ui.findSelect = new Ox.Select({
                                id: 'select',
                                items: $.map(app.config.findKeys, function(key, i) {
                                    return {
                                        id: key.id,
                                        title: 'Find: ' + key.title
                                    };
                                }),
                                overlap: 'right',
                                width: 112
                            })
                            .bindEvent('change', function(event, data) {
                                var key = data.selected[0].id;
                                app.user.ui.findQuery.conditions[0].key = key
                                app.$ui.mainMenu.checkItem('findMenu_find_' + key);
                                app.$ui.findInput.focus();
                            }),
                        app.$ui.findInput = new Ox.Input({
                                autocomplete: function(value, callback) {
                                    var key = 'title';
                                    var findKey = Ox.getObjectById(app.config.findKeys, key);
                                    Ox.print('autocomplete', key, value);
                                    value === '' && Ox.print('Warning: autocomplete function should never be called with empty value');
                                    if ('autocomplete' in findKey && findKey.autocomplete) {
                                        pandora.api.find({
                                            keys: [key],
                                            query: {
                                                conditions: [
                                                    {
                                                        key: key,
                                                        value: value,
                                                        operator: ''
                                                    }
                                                ],
                                                operator: ''
                                            },
                                            sort: [
                                                {
                                                    key: key,
                                                    operator: ''
                                                }
                                            ],
                                            range: [0, 10]
                                        }, function(result) {
                                            callback($.map(result.data.items, function(v) {
                                                return v.title;
                                            }));
                                        });
                                    } else {
                                        callback();                            
                                    }
                                },
                                autocompleteSelect: true,
                                autocompleteSelectHighlight: true,
                                autocompleteSelectSubmit: true,
                                clear: true,
                                id: 'input',
                                width: 192
                            })
                        .bindEvent('submit', function(event, data) {
                            var key = app.user.ui.findQuery.conditions[0].key,
                                query;
                            Ox.print('key', key);
                            app.user.ui.findQuery.conditions = [
                                {
                                    key: key == 'all' ? '' : key,
                                    value: data.value,
                                    operator: ''
                                }
                            ];
                            $.each(app.ui.groups, function(i, group) {
                                group.query.conditions = [];
                                app.$ui.groups[i].options({
                                    request: function(data, callback) {
                                        delete data.keys;
                                        return pandora.api.find($.extend(data, {
                                            group: group.id,
                                            query: app.Query.toObject(group.id)
                                        }), callback);
                                    }
                                });
                            });
                            app.$ui.list.options({
                                request: function(data, callback) {
                                    return pandora.api.find($.extend(data, {
                                        query: query = app.Query.toObject()
                                    }), callback);
                                }
                            });
                            history.pushState({}, '', '/#' + app.Query.toString(query));
                        })
                    ],
                    id: 'findElement'
                })
                .css({
                    float: 'right',
                    margin: '4px'
                })
        );
}

*/



/*

    //FIXME: how to properly overwrite functions without replacing them
    var super_launch = app.launch;
    app.launch = function() {
        app.request.send('hello', function(result) {
            app.user = result.data.user;
            if(app.user.group!='guest') {
                app.menu.getItem('status').options('title', 'User: ' + app.user.username);
                app.menu.getItem('login').options('title', 'Logout');
            }
        });
        super_launch();
    };


    var loadingIcon = new Ox.LoadingIcon({
            size: 'medium'
        })
        .css({
            marginLeft: '4px'
        });

    app.menu = new Ox.MainMenu({
                    extras: [
                        new Ox.Input({
                            autocomplete: function(option, value, callback) {
                                    var field = option.substring(6).toLowerCase();
                                    if(typeof(callback) == 'undefined') {
                                        callback = value;
                                        value = null;
                                    }
                                    Ox.debug('app.menu.find.autocomplete: option: ', option, 'value: ', value, ', callback:',callback);
                                    Ox.debug('app.menu.find.autocomplete: field: ', field);
                                    if(field == 'all') {
                                        callback([]);
                                    } else if (value) {
                                        value = value.toLowerCase();
                                        //var order = $.inArray(field, ['year', 'date'])?'-':'';
                                        app.request.send('find', {
                                            query: {
                                                conditions: [
                                                {
                                                    key: field,
                                                    value: value,
                                                    operator: '~'
                                                }
                                                ]
                                            },
                                            list: 'all',
                                            sort: [{key:field, operator: ''}],
                                            keys: [field],
                                            range: [0, 10]
                                        }, function(result) {
                                            var items = $.map(
                                                result.data.items,
                                                function(item, i) { return item.title; }
                                            );
                                            callback(items);
                                        });
                                    }
                            },
                            clear: true,
                            highlight: false,
                            id: 'find',
                            label: [
                                { id: 'all', title: 'Find: All' },
                                { id: 'title', title: 'Find: Title' },
                                { id: 'director', title: 'Find: Director' },
                                { id: 'country', title: 'Find: Country' },
                                { id: 'year', title: 'Find: Year' },
                                { id: 'language', title: 'Find: Language' },
                                { id: 'writer', title: 'Find: Writer' },
                                { id: 'producer', title: 'Find: Producer' },
                                { id: 'cinematographer', title: 'Find: Cinematographer' },
                                { id: 'editor', title: 'Find: Editor' },
                                { id: 'actor', title: 'Find: Actor' },
                                { id: 'character', title: 'Find: Character' },
                                { id: 'name', title: 'Find: Name' },
                                { id: 'genre', title: 'Find: Genre' },
                                { id: 'keyword', title: 'Find: Keyword' },
                                { id: 'summary', title: 'Find: Summary' },
                                { id: 'dialog', title: 'Find: Dialog' }
                            ],
                            labelWidth: 96
                        }).width(320),
                        loadingIcon
                    ],
        menus: [
            {
                id: 'pandoraMM',
                title: site.name,
                items: [
                    { id: 'about', title: 'About ' + site.name },
                    {},
                    { id: 'faq', title: 'Frequently Asked Questions'},
                    { id: 'tos', title: 'Terms of Service'},
                    { id: 'sas', title: 'Security Advisory System'},
                    {},
                    { id: 'contact', title: 'Contact'},
                    {},
                    { id: 'technology', title: 'Technology'},
                    { id: 'source', title: 'Source Code'},
                    { id: 'report', title: 'Report a Bug...'},
                ]
            },
            {
                id: 'user',
                id: 'user',
                title: 'User',
                items: [
                    { id: 'status', title: 'User: not logged in', disabled:true },
                    {},
                    { id: 'accunt', title: 'Account', disabled:true},
                    { id: 'preferences', title: 'Preferences', disabled:true},
                    {},
                    {id: 'login', title: 'Login'},
                ]
            },
            {
                id: 'file',
                title: 'File',
                items: [
                    { id: 'load', keyboard: 'control o', title: 'Open' },
                    { id: 'save', keyboard: 'control a', title: 'Save' },
                    { id: 'save_ad', keyboard: 'shift control s', title: 'Save As...'}
                ]
            },
            {
                id: 'edit',
                title: 'Edit',
                items: [
                    { id: 'undo', keyboard: 'control z', title: 'Undo' },
                    { id: 'redo', keyboard: 'shift control z', title: 'Redo' },
                    {},
                    { id: 'cut', keyboard: 'control x', title: 'Cut' },
                    { id: 'copy', keyboard: 'control c', title: 'Copy' },
                    { id: 'paste', keyboard: 'control v', title: 'Paste'},
                    { id: 'delete', keyboard: 'delete', title: 'Delete'},
                    {},
                    { id: 'select_all', keyboard: 'control a', title: 'Select All' },
                    { id: 'select_none', keyboard: 'shift control a', title: 'Select None' },
                    { id: 'invert_selection', keyboard: 'alt control a', title: 'Invert Selection' },
                ]
            },
            {
                id: 'sort',
                title: 'Sort',
                items: [
                    { id: 'sort_movies', title: 'Sort Movies by', items: [
                        { checked: true, group: 'sort_movies', id: 'title', title: 'Title'},
                        { checked: false, group: 'sort_movies', id: 'director', title: 'Director' },
                    ] },
                    { id: 'order_movies', title: 'Order Movies', items: [
                        { checked: false, group: 'order_movies', id: 'ascending', title: 'Ascending'},
                        { checked: true, group: 'order_movies', id: 'descending', title: 'Descending' },
                    ] }
                ]
            },
            {
                id: 'help',
                title: 'Help',
                items: [
                    { id: 'help', keyboard: 'control h', title: 'Help' }
                ]
            }
        ],
        size: 'large'
    });

    function pageDialog(title, page) {
        //Ox.debug(title, page);
        var $dialog = new Ox.Dialog({
            title: title,
            buttons: [
                {
                    title: 'Close',
                    click: function() { $dialog.close(); }
                }
            ]
        })
        .append(page)
        .open();
    };
    //this should be: mainMenu.bind('click_about', function(event) {
    app.menu.bindEvent('click_about', function() {
        pageDialog('About ' + site.name, site.pages.about);
    });
    app.menu.bindEvent('click_faq', function() {
        pageDialog(app.menu.getItem('faq').options('title')[0],
                   site.pages.faq);
    });
    app.menu.bindEvent('click_tos', function() {
        pageDialog(app.menu.getItem('tos').options('title')[0],
                   site.pages.tos);
    });
    app.menu.bindEvent('click_sas', function() {
        pageDialog(app.menu.getItem('sas').options('title')[0],
                   site.pages.sas);
    });

    OxForm = function(options, self) {
        var self = self || {},
            that = new Ox.Element({}, self)
                .defaults({
                    elements: [],
                })
                .options(options || {})
                .addClass('OxForm'),
            length = self.options.elements.length;
        $.each(self.options.elements, function(i, v) {
            that.append(Ox.Container().css({'margin': '5px'}).append(v));
        });
        that.values = function() {
            var values = {};
            $.each(self.options.elements, function(i, v) {
                values[v.$input.attr('name')] = v.$input.val();
            });
            return values;
        }
        return that;
    };

    app.menu.bindEvent('click_contact', function() {
        var labelWidth = 64;
        var inputWidth = 380;

        var u = new Ox.Input({
            label: 'Your Email',
            labelWidth: labelWidth,
            name:'email',
            size: 'medium'
        }).width(inputWidth).addClass('margin');
        if(app.user && app.user.preferences.email) {
            u.val(app.user.preferences.email);
        }

        var form = new OxForm({
            elements: [
                u,
                new Ox.Input({
                    label: 'Subject',
                    labelWidth: labelWidth,
                    name: 'subject',
                    size: 'medium'
                }).width(inputWidth).addClass('margin'),
                new Ox.Input({
                    label: 'Message',
                    labelWidth: labelWidth,
                    type: 'textarea',
                    size: 'medium',
                    name: 'message'
                }).width(380).height(176).addClass('margin')
            ]
        });

        var $dialog = new Ox.Dialog({
            title: 'Contact',
            width: 424,
            height: 320,
            buttons: [
                {
                    value: 'Cancel',
                    click: function() { $dialog.close(); }
                },
                {
                    value: 'Contact',
                    click: function() {
                        app.request.send('contact', form.values(),
                        function(result) {
                            if(result.status.code == 200) {
                                $dialog.close();                               
                            } else {
                                $dialog.append(result.status.text);
                            }
                        });
                    }
                }
            ]
        })
        .append(form)
        .open();
    });
    app.menu.bindEvent('click_technology', function() {
        pageDialog(app.menu.getItem('technology').options('title')[0],
                   site.pages.technology);
    });
    app.menu.bindEvent('click_source', function() {
        pageDialog(app.menu.getItem('source').options('title')[0],
                   site.pages.source);
    });
    app.menu.bindEvent('click_report', function() {
        pageDialog(app.menu.getItem('report').options('title')[0],
                   site.pages.report);
    });

    app.logout = function () {
        this.request('logout');
        this.user = {};
        this.menu.getItem('logout').toggle();
        this.menu.getItem('status').options('title', 'User: not logged in');
    };
    app.menu.bindEvent('click_logout', function(event, data) {
        app.logout();
    });

    app.menu.bindEvent('click_login', function(element) {
        var labelWidth = 64;
        var inputWidth = labelWidth+200;
        var loginForm = new OxForm({
            elements: [
                new Ox.Input({
                    label: 'Username',
                    labelWidth: labelWidth,
                    name:'username',
                    size: 'medium'
                }).addClass('margin').width(inputWidth),
                new Ox.Input({
                    label: 'Password',
                    labelWidth: labelWidth,
                    name:'password',
                    type: 'password',
                    size: 'medium'
                }).addClass('margin').width(inputWidth)
            ]
        }).css({
            'padding-top': '48px',
        });

        var submit = function() {
            app.request.send('login', loginForm.values(), function(result) {
                if(result.status.code == 200) {
                    $dialog.close();
                    app.user = result.data.user;
                    app.menu.getItem('status').options(title, 'User: ' + app.user.username);
                    app.menu.getItem('login').toggle();
                    
                } else {
                    $dialog.append('Login failed ' + result.status.text);
                }
            });
        }

        var d = new Ox.Container();
        var registerInfo = new Ox.Panel();

        registerInfo.append(Ox.Element().css({'margin-left': '4px'}).append('<br>Forgot your password? <a href="">Recover Password</a><br>Dont have an account? <a href="">Register Now</a>'));

        var panel = Ox.SplitPanel({
            elements: [
                {
                    element: loginForm,
                },
                {
                    element: registerInfo,
                    size: 80
                }
            ],
            orientation: 'vertical'
        }).appendTo(d);

        var $dialog = new Ox.Dialog({
            title: 'Login',
            width: inputWidth+24,
            height: 184,
            buttons: [
                [],[
                    {
                        value: 'Cancel',
                        click: function() { $dialog.close(); }
                    },
                    {
                        value: 'Login',
                        click: submit
                    }
                ]
            ]
        })
        .append(d)
        .open();
    });
    var bottomPanel = Ox.Toolbar({size: 'small'})
        .css({
            zIndex: 2,
            MozBoxShadow: '0 0 4px rgb(0, 0, 0)',
            WebkitBoxShadow: '0 0 4px rgb(0, 0, 0)'
        })
        .append(
            $('<div/>')
                .addClass('bottom')
                .html(site.url + ' - a rather unique kind of movie database.')
        );
    var mainSplitPanel = Ox.SplitPanel({
        elements: [
            {
                element: app.menu,
                size: 24
            },
            {
                element: middleSplitPanel
            },
            {
                element: bottomPanel,
                size: 24
            }
        ],
        orientation: 'vertical'
    }).appendTo($body);

    var listPanel = new Ox.CollapsePanel({
        title: 'Lists'
    }).appendTo(sidePanel);
    listPanel.$content.html('Nouvelle Vague<br/>Hollywood 40\'s<br/>Pirate Cinema Berlin')

    var historyPanel = new Ox.CollapsePanel({
        title: 'Search History'
    }).appendTo(sidePanel);

    historyPanel.$content.html('Paris<br/>Matirx<br/>Godard')

    app.results = new Ox.TextList({
        columns: [ {
                align: 'left',
                id: 'title',
                operator: '+',
                title: 'Title',
                visible: true,
                width: 160
            },
            {
                align: 'left',
                id: 'director',
                operator: '+',
                title: 'Director',
                visible: true,
                width: 160
            },
            {
                align: 'right',
                id: 'year',
                operator: '-',
                title: 'Year',
                visible: true,
                width: 80
            }	
        ],
        request: function(options) {
            app.request.send('find', $.extend(options, {
                query: {
                    conditions: [],
                    operator: ',' // fixme: should be &
                }
            }), options.callback);
        },
        id: 'results',
        sort: [{
            key: 'year',
            operator: '-'
        }]
    }).appendTo(content);

    app.menu.bindEvent('submit_find', function(event, data) {
        app.results.options({
            request: function(options) {
                app.request.send('find', $.extend(options, {
                    query: {
                        key: data.option.substr(6).toLowerCase(),
                        value: data.value,
                        operator: '~'
                    }
                }), options.callback);
            },
        });
    });
    app.launch();

});

*/