/*
 * version: 0.1
 */

(function($) {
    $.conf = {
        'urls':    {'base':         '/',
                    'media':        '/media/',
                    'mediaStatic':  '/media/s/',
                    'login':        '/user/login/'},

        'box':     {'boxEvents':       ['click', 'mousedown', 'mouseup',
                                        'mouseover', 'mouseout'],
                    'class':            '',
                    'closeButton':      true,
                    'closeButtonClass': 'closelil',
                    'closeOnKeyEsc':    true,
                    'closeOnInnerClick':false,
                    'closeOnOuterClick':false, // @todo available if modal
                    'content':          'Ничё се контент.',
                    'container':        undefined,
                    'drag':             false,
                    'dragOptions':      {},
                    'fit':              true,
                    'fitFactor':        .75,
                    'fxOptions':        {'show': {}, 'hide': {}, 'transition': {}},
                    'height':           'auto',
                    'margin':           0,
                    'manager':          function() {return $.widgetManager;},
                    'managerEvents':    function() {return $.widgetManager.events;},
                    'modal':            false, // @todo
                    'offset':           {'x': 0, 'y': 0},
                    'position':         'center',
                    'relativeTo':       'trigger',
                    'scale':            function(from, to) {return to;},
                    'width':            'auto'},
                
        'dialog':  {'buttons':          {},
                    'class':            '',
                    'drag':             true,
                    'dragOptions':      {'handle': '.dialog-title'},
                    'title':            true,
                    'trigger':          undefined,
                    'triggerEvent':     'click',
                    'width':            300},

        'notify':  {'class':            '',
                    'duration':         5000,
                    'margin':           10,
                    'width':            250},

        'zoomer':  {'className':        'zoomer',
                    'cutOut':           false,
                    'margin':           100,
                    'opacityResize':    0.4,
                    'origin':           'img',
                    'resizeFactor':     0.75,
                    'shadow':           'onOpen'},

        'processing':  {'class': 'processing',
                        'style': {
                            'opacity': 0.6
                        },
                        'fxOptions': {
                            'duration': 'short',
                            'link': 'cancel'
                        }},

        'loading': {'class': 'loading',
                    'style': {
                        'opacity': 0.6
                    },
                    'fxOptions': {
                        'duration': 'short',
                        'link': 'cancel'
                    }},

        'request': {'cache':    false, // @todo my option: "hard" cache the responses
                    'noCache':  true, // mootools core option: true cos of fcukin IE
                    'process':  {},
                    'pool':     'default', // possible values: 'default', 'new' or custom name of pool
                    'type':     'json',
                    'wait':     true}, // wait for queue in a pool, otherwise kill all the processes in a queue and start immediatly

        'jsonHandlers': {
            'notify': function() {
                $.notify({'class': this.response.json.status,
                    'content': this.response.json.content});
            },
            'html': function() {
                try {
                    new Request.HTML().success.apply(this, [this.response.json.content]);
                } catch (e) {}
            }
        }
    };
    
    $.ready = function(callback) {window.addEvent('domready', callback);}
    $.callable = function(obj) {return typeOf(obj) === 'function';}
    $.log = function() {window.console && $.callable(console.log) && console.log.apply(console, arguments);}
    
    $.getElement = function(mixed, fallbackVal) {
        var type = typeOf(mixed);
        var elem = undefined;

        if (type) switch (type) {
            case 'string':
                elem = $$(mixed);
                elem && (elem = elem.length ? elem[0] : $(mixed));
                break;
            case 'element':
                elem = mixed;
                break;
            case 'collection':
                if (mixed.length) {
                    elem = mixed[0];
                }
        }
        
        return elem || fallbackVal;
    }

    $.getParent = function(mixed, fallbackVal) {
        var elem = $.getElement(mixed);
        if (elem) {
            var parent = elem.getParent();
            if (parent) {
                return parent;
            }
        }
        return fallbackVal;
    }

    $.widgetManager = new new Class({
        Implements: Events,

        'events': ['managerActivate', 'managerInactivate',
                   'managerKeyEsc', 'managerWindowResize'],
        'widgets': Array.from(),
        'zIndex': 10,
        
        'initialize': function() {
            this.widgets = [];
            
            new Keyboard({
                'events': {
                    'esc': function() {
                        this.activeWidget && this.activeWidget.fireEvent('managerKeyEsc');
                    }.bind(this)
                }
            });

            window.addEvent('resize', function() {
                this.widgets.each(function(widget) {
                    widget.fireEvent('managerWindowResize');
                });
            }.bind(this));
            
            this.addEvents({
                'mouseover': function(widget) {
                    widget.active || this.activate(widget);
                },
                'activate': function(widget) {
                    widget.active || this.activate(widget);
                },
                'inactivate': function(widget) {
                    widget.active && this.inactivate(widget);
                }
            });
        },

        'register': function(widget) {this.widgets.push(widget);},
        'unregister': function(widget) {this.widgets.erase(widget);},

        'activate': function(widget) {
            this.widgets.each(function(item) {
                if (item === widget) {
                    this.zIndex += 2;
                    item.fireEvent('managerActivate', this.zIndex);
                    this.activeWidget = item;
                } else {
                    item.active && item.fireEvent('managerInactivate');
                }
            }.bind(this));
        },
        
        'inactivate': function(widget) {
            var nextWidget = undefined;
            var maxZIndex = 0;

            this.widgets.each(function(item) {
                if (item === widget) {
                    widget.fireEvent('managerInactivate');
                    this.activeWidget = undefined;
                } else if (!item.active && item.visible) {
                    var zIndex = parseInt(item.box.getStyle('z-index'));
                    if (zIndex > maxZIndex) {
                        maxZIndex = zIndex;
                        nextWidget = item;
                    }
                }
            }.bind(this));

            nextWidget && this.activate(nextWidget);
        }
    });

    $.widgets = {};

    $.widgets.Box = new Class({
        Implements: [Events, Options],

        'options': $.conf.box,
        'dragmove': null,
        'visible': false,
        'active': false,
        'state': '',

        'initialize': function(options) {
            this.setOptions(options);

            this.box = new Element('div', {
                'class': ['box', this.options['class'], Browser.name + Browser.version].join(' '),
                'styles': {'display': 'none', 'position': 'absolute'}
            });

            this.boxInner = new Element('div', {
                'class': 'box-inner',
                'styles': {'position': 'relative'}
            }).inject(this.box);

            this.edgesContainer = new Element('div', {'class': 'box-edges'})
                .inject(this.boxInner);

            this.edges = $$();
            
            ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'].each(function(side) {
                var styles = {'position': 'absolute'}
                if (side == 'n' || side == 's') {
                    Object.append(styles, {'width': '100%'});
                }
                if (side == 'w' || side == 'e') {
                    Object.append(styles, {'height': '100%'});
                }
                this.edges.push(new Element('div', {
                    'class': 'box-edge ' + side,
                    'styles': styles}).inject(this.edgesContainer));
            }.bind(this));

            this.body = new Element('div', {
                'class': 'box-body',
                'styles': {'overflow': 'hidden', 'position': 'relative'}
            }).inject(this.boxInner);

            this.header = new Element('div', {'class': 'box-header'})
                .inject(this.body);
            this.content = new Element('div', {'class': 'box-content'})
                .inject(this.body);
            this.footer = new Element('div', {'class': 'box-footer'})
                .inject(this.body);

            this.options.boxEvents.each(function(eventName) {
                this.box.addEvent(eventName, function(event) {
                    this.fireBoxEvent(eventName, event);
                }.bind(this));
            }.bind(this));
            
            if ($.callable(this.options.manager) && (this.manager = this.options.manager())) {
                this.manager.register(this);
                this.options.managerEvents().each(function(event) {
                    this.addEvent(event, this[event].bind(this));
                }.bind(this));
            }

            this.addEvents({
                'mousedown': function(event) {
                    this.mousedown = Object.clone(event.client);
                },
                'mouseup': function(event) {
                    if (this.mousedown) {
                        var mouseup = Object.clone(event.client);
                        var delta = mouseup.x - this.mousedown.x + 
                            mouseup.y - this.mousedown.y;
                        if (delta == 0) {
                            this.fireBoxEvent('innerClick');
                        }
                        this.mousedown = undefined;
                    }
                },
                'innerClick': function() {
                    this.options.closeOnInnerClick && this.hide();
                }
            });

            this.needCalculate = true;
            this.setState('initialize');
            return this;
        },

        'init': function(options) {
            var oldOptions = Object.clone(this.options);
            this.setOptions(options);
            this.needCalculate = (this.options !== oldOptions);
            this.setState('init');
            return this;
        },

        'fireBoxEvent': function(eventName, event) {
            this.manager && this.manager.fireEvent(eventName, this);
            this.fireEvent(eventName, event);
            return this;
        },

        'setState': function(state) {
            this.state = state;
            this.fireBoxEvent(state);
            return this;
        },

        'injectContent': function() {
            if (!this.contentChanged) return this;
            this.content.innerHTML = this.options.content;
            this.contentChanged = false;
            return this;
        },

        'calculate': function() {
            if (!this.needCalculate) return this;
            $.log('calculate');
            this.injected || this.box.inject(document.body) && (this.injected = true);
            this.setState('calculate');
            var height = parseInt(this.options.height) || 'auto';
            var width = parseInt(this.options.width) || 'auto';
            var optsContainer = $.getElement(this.options.container);
            var container = optsContainer || this.box.getDocument();
            var limit = container.getSize();
            var scroll = container.getScroll();
            var margin = parseInt(this.options.margin);
            var pos = {'left': 0, 'top': 0};
            var contentSize = {'x': 0, 'y': 0};
            var boxInnerSize = {'x': 0, 'y': 0};

            this.box.measure(function() {
                var curBody = this.body.clone(true, true);
                curBody.cloneEvents(this.body);
                
                if (this.edgesContainer.isDisplayed()) {
                    var edges = this.edges.getSize();
                    var delta = {
                        'x': edges[2].x + edges[6].x,
                        'y': edges[0].y + edges[4].y
                    };
                    this.boxInner.setStyles({
                        'marginTop': edges[0].y,
                        'marginRight': edges[2].x,
                        'marginBottom': edges[4].y,
                        'marginLeft': edges[6].x
                    });
                } else {
                    var delta = {'x': 0, 'y': 0};
                    this.boxInner.setStyle('margin', 0);
                }

                this.content.setStyles({'height': height, 'width': width});
                this.contentChanged = true;
                this.injectContent();
                boxInnerSize = this.boxInner.getSize();
                var size = {'x': boxInnerSize.x + delta.x, 'y': boxInnerSize.y + delta.y};

                if (this.options.fit) {
                    var fit = {'x': limit.x * this.options.fitFactor,
                        'y': limit.y * this.options.fitFactor};

                    if ((size.x > fit.x) && (size.y > fit.y)) {
                        boxInnerSize.x = fit.x - delta.x;
                        boxInnerSize.y = fit.y - delta.y;
                    } else if (size.x > fit.x) {
                        this.boxInner.setStyles({
                            'height': 'auto', 'width': fit.x - delta.x});
                        boxInnerSize = this.boxInner.getSize();
                        if ((boxInnerSize.y + delta.y) > fit.y) {
                            boxInnerSize.y = fit.y - delta.y;
                        }
                    } else if (size.y > fit.y) {
                        this.boxInner.setStyles({
                            'height': fit.y - delta.y, 'width': 'auto'});
                        boxInnerSize = this.boxInner.getSize();
                        if ((boxInnerSize.x + delta.x) > fit.x) {
                            boxInnerSize.x = fit.x - delta.x;
                        }
                    }
                }

                this.boxInner.setStyles({'height': boxInnerSize.y, 'width': boxInnerSize.x});
                var contentDims = this.content.getDimensions({'computeSize': true});
                contentSize = this.content.getSize();
                contentSize = {
                    'x': contentSize.x - parseInt(contentDims.computedLeft) - parseInt(contentDims.computedRight),
                    'y': contentSize.y - parseInt(contentDims.computedTop) - parseInt(contentDims.computedBottom)
                };
                $.log(contentSize);
                contentSize = this.options.scale.apply(this,
                    [{'x': width, 'y': height}, contentSize]);
                this.content.setStyles({'height': contentSize.y, 'width': contentSize.x});
                boxInnerSize = this.boxInner.getSize();
                size = {'x': boxInnerSize.x + delta.x, 'y': boxInnerSize.y + delta.y};
                margin = Math.min((limit.x - size.x) / 2, (limit.y - size.y) / 2, margin).toInt();

                $.log(contentSize);
                $.log(boxInnerSize);
                $.log(size);

                var posOptions = {'offset': this.options.offset, 'returnPos': true};
                var trigger = $.getElement(this.options.trigger);

                if (trigger && this.options.relativeTo == 'trigger') {
                    Object.append(posOptions, {'relativeTo': trigger});
                } else if (optsContainer && this.options.relativeTo == 'container') {
                    Object.append(posOptions, {'relativeTo': optsContainer});
                }

                this.options.position &&
                    Object.append(posOptions, {'position': this.options.position});

                pos = this.box.position(posOptions);

                if (this.options.fit) {
                    pos.left = pos.left.limit(scroll.x + margin,
                        scroll.x + limit.x - margin - size.x);
                    pos.top = pos.top.limit(scroll.y + margin,
                        scroll.y + limit.y - margin - size.y);
                }

                /*for (var _ = 2; _--;) {
                    if (size.x > max.x) {
                        size.y *= max.x / size.x;
                        size.x = max.x;
                    } else if (size.y > max.y) {
                        size.x *= max.y / size.y;
                        size.y = max.y;
                    }
                }*/

                this.boxInner.setStyles({'height': 'auto', 'width': 'auto'});
                this.contentChanged = (this.body.innerHTML != curBody.innerHTML);
                this.body.innerHTML = curBody.innerHTML;
                this.body.cloneEvents(curBody);
                this.content = this.body.getElement('.box-content');
            }.bind(this));

            if (this.options.closeButton) {
                if (this.closeButton) {
                    this.closeButton.className = this.options.closeButtonClass;
                } else {
                    this.closeButton = new Element('a', {
                        'class': this.options.closeButtonClass, href: '#close', title: 'Скрыть'
                    }).addEvent('click', function() {
                        this.hide();
                        return false;
                    }.bind(this)).inject(this.boxInner);
                }
            } else if (this.closeButton) {
                this.closeButton.destroy();
                this.closeButton = undefined;
            }

            this.calculated = {
                'box': pos,
                'boxInner': boxInnerSize,
                'content': contentSize
            }

            this.needCalculate = false;
            return this;
        },

        'forceCalculate': function() {
            this.needCalculate = true;
            return this;
        },

        'applyCalculated': function() {
            this.content.setStyles({
                'height': this.calculated.height,
                'width': this.calculated.width
            });
            this.boxInner.setStyles({
                'height': this.calculated.inner.y,
                'width': this.calculated.inner.x
            });
            this.box.setStyles({
                'left': this.calculated.left,
                'top': this.calculated.top
            });
            return this;
        },

        'transition': function(fxOptions, before, after) {
            this.calculate();
            before();
            this.setState('beforeTransition');

            if (this.edgesContainer.isDisplayed()) {
                if (Browser.ie) {
                    //this.edges.show();
                    //Browser.ie7 && this.edges.show();
                }
            }

            if (Object.getLength(fxOptions)) {
                this.box.addClass('transition');
                this.closeButton && this.closeButton.hide();
                var doAfter = function() {
                    this.closeButton && this.closeButton.show();
                    this.box.removeClass('transition');
                    this.setState('afterTransition');
                    after();
                }.bind(this);
                $.log('fx to be applied');
            } else {
                this.injectContent();
                this.applyCalculated();
                this.box.show();
                this.setState('afterTransition');
                after();
            }
            
            return this;
        },

        'show': function(fxOptions, needCalculate) {
            needCalculate && (this.needCalculate = true);

            if (this.visible) {
                this.transition(Object.merge(this.options.fxOptions.transition, fxOptions),
                    function() {}, function() {});
            } else {
                var before = function() {
                    this.visible = true;
                    this.activate();
                    this.setState('beforeShow');
                }.bind(this);
                
                var after = function() {
                    this.options.drag && this.attachDrag(this.getDragOptions(), true);
                    this.setState('afterShow');
                }.bind(this);

                this.transition(Object.merge(this.options.fxOptions.show, fxOptions),
                    before, after);
            }
            
            return this;
        },
        
        'hide': function(fxOptions) {
            if (this.visible) {
                var before = function() {
                    this.visible = false;
                    this.setState('beforeHide');
                }.bind(this);

                var after = function() {
                    this.box.hide();
                    this.inactivate();
                    this.detachDrag();
                    this.setState('afterHide');
                }.bind(this);

                this.transition(Object.merge(this.options.fxOptions.hide, fxOptions),
                    before, after);
            }
                
            return this;
        },

        'activate': function() {
            this.manager && this.manager.fireEvent('activate', this);
            return this;
        },

        'inactivate': function() {
            this.manager && this.manager.fireEvent('inactivate', this);
            return this;
        },

        'attachDrag': function(options, forceAttach) {
            if (!this.dragmove || forceAttach) {
                if (this.dragmove) {
                    this.dragmove.detach();
                    this.dragmove = null;
                }
                
                this.dragmove = new Drag.Move(this.box, Object.merge({
                    'includeMargins': false,
                    'snap': 3,
                    'onSnap': function() {
                        this.box.addClass('drag');
                    }.bind(this),
                    'onComplete': function() {
                        this.box.removeClass('drag');
                    }.bind(this)
                }, options));
            }

            this.dragmove.attach();
            return this;
        },

        'detachDrag': function() {
            this.dragmove && this.dragmove.detach();
            return this;
        },

        'getDragOptions': function() {
            var dragOptions = Object.clone(this.options.dragOptions);
            var container = $.getElement(dragOptions.container,
                $.getElement(this.options.container, this.box.getDocument()));
            var dragBounds = true;
            var type = typeOf(container);

            if (type == 'document' || type == 'window') {
                if (container.getSize().x < container.getScrollSize().x) {
                    dragBounds = false;
                }
            }

            dragBounds
                ? Object.append(dragOptions, {'container': container})
                : delete dragOptions['container'];

            if (dragOptions.handle) {
                var handle = this.box.getElements(dragOptions.handle);
                if (handle.length) {
                    var handleSize = handle[0].getSize();
                    if (handleSize.x > 0 && handleSize.y > 0) {
                        dragOptions.handle = handle[0];
                    } else {
                        delete dragOptions['handle'];
                    }
                } else {
                    delete dragOptions['handle'];
                }
            }

            return dragOptions;
        },

        'managerActivate': function(zIndex) {
            this.active = true;
            this.box.setStyle('z-index', zIndex);
            this.setState('active');
        },

        'managerInactivate': function() {
            this.active = false;
            this.setState('inactive');
        },

        'managerKeyEsc': function() {
            this.fireEvent('keyEsc');
            this.options.closeOnKeyEsc && this.hide();
        },

        'managerWindowResize': function() {
            this.forceCalculate();
            if (this.visible) {
                this.show();
                this.options.drag && this.attachDrag(this.getDragOptions(), true);
            }
        }
    });

    $.widgets.Dialog = new Class({
        Extends: $.widgets.Box,
        Implements: [Events, Options],
        
        'options': $.conf.dialog,

        'initialize': function(options) {
            this.setOptions(options);
            this.options['class'] = 'dialog ' + this.options['class'];
            var trigger = $.getElement(this.options.trigger);

            if (trigger) {
                if (this.options.triggerEvent) {
                    trigger.addEvent(this.options.triggerEvent, function() {
                        try {this.show();} catch (e) {}
                        return false;
                    }.bind(this));
                }
                Object.append(this.options, {'trigger': trigger});
            } else {
                this.options.trigger = undefined;
            }

            this.parent(this.options);
            this.init();
            this.addEvent('afterShow', this.afterShow.bind(this));
        },

        'init': function(options) {
            options && this.setOptions(options);
            var content = $$(this.options.content);
            content = content.match('html') ? this.options.content : content.get('html');
            var index = 0;
            var buttons = [];

            for (var buttonName in this.options.buttons) {
                var buttonOptions = Object.merge({
                    'index': index++, 'name': buttonName, 'title': ''
                }, this.options.buttons[buttonName]);

                this.options.buttons[buttonName] = buttonOptions;

                buttons.push({
                    'index': index - 1,
                    'html': '<input type="button" class="button ' +
                        buttonOptions.name + '" value="' + buttonOptions.title + '"/>'
                });
            }

            // @todo buttons ordering
            var buttonsHtml = '';

            for (var _ = 0; _ < buttons.length; _++) {
                buttonsHtml += buttons[_].html;
            }

            this.options.content =
                '<div class="dialog-title"'
                    + (this.options.title ? '' : ' style="display: none"') +'>'
                    + (this.options.title === true ? '' : this.options.title.toString()) +
                '</div>' +
                '<div class="dialog-content">' + content + '</div>' +
                '<div class="dialog-buttons"' + (buttonsHtml ? '' : ' style="display: none"') +'>' +
                '   <div class="dialog-buttons-inner">' + buttonsHtml + '</div>' +
                '</div>';

            this.parent(this.options);
        },

        'afterShow': function() {
            $.log('after show');
            for (var buttonName in this.options.buttons) {
                var button = this.options.buttons[buttonName];
                var domButton = this.getButton(button.name);
                if (domButton) {
                    domButton.addEvent('click', (function(func, dialog) {
                        return function() {
                            return func.apply(this, [dialog]);
                        }
                    })(button.onClick, this));
                }
            }
        },

        'getButton': function(name) {
            return this.box.getElement('.dialog-buttons-inner .' + name);
        }
    });

    $.widgets.Notify = new Class({
        Implements: [Events, Options],

        'options': $.conf.notify,

        'initialize': function(options) {
            this.setOptions(Object.merge({'position': {'top': 0}}, options));
        },

        'build': function() {
            this.__base__ = new $.widgets.Box({
                'class': 'notify ' + this.options['class'],
                'closeButtonClass': this.options.closeButtonClass,
                'closeOnInnerClick': true,
                'content': this.options.content,
                'drag': false,
                'fitFactor': .5,
                'manager': false,
                'margin': this.options.margin,
                'position': 'topright',
                'width': this.options.width
            }, this).calculate();

            this.__base__.calculated.top = -500;
            this.__base__.show();
            this.box = this.__base__.box;
            
            this.box.setStyles({
                'left': 'auto', 'position': 'fixed',
                'right': this.options.margin});
            
            this.tweens = {
                'show': new Fx.Tween(this.box, {
                    'property': 'top',
                    'duration': 600,
                    'link': 'cancel',
                    'transition': Fx.Transitions.Sine.easeOut,
                    'onComplete': function() {this.fireEvent('afterShow', this);}.bind(this)
                }),
                'move': new Fx.Tween(this.box, {
                    'property': 'top',
                    'duration': 400,
                    'link': 'cancel',
                    'transition': Fx.Transitions.Sine.easeInOut
                }),
                'hide': new Fx.Tween(this.box, {
                    'property': 'top',
                    'duration': 600,
                    'transition': Fx.Transitions.Expo.easeIn,
                    'onComplete': function() {this.fireEvent('afterHide', this);}.bind(this)
                })
            };
        },

        'show': function() {
            this.__base__ || this.build();
            this.display = true;
            this.fireEvent('show', this);
            this.tweens.show.start(this.options.position.top);
            this.options.duration && this.hide.delay(this.options.duration, this);
            return this;
        },

        'move': function() {
            if (!this.display) return this;
            this.tweens.show.cancel();
            this.tweens.move.start(this.options.position.top);
            return this;
        },

        'hide': function() {
            if (!this.display) return this;
            this.display = false;
            this.fireEvent('hide', this);
            this.tweens.show.cancel();
            this.tweens.move.cancel();
            this.tweens.hide.start(-500);
            return this;
        }
    });

    var notifyFactory = new new Class({
        Implements: Chain,

        'shown': Array.from(),
        'wait': 0,

        'initialize': function() {
            this.ready = false;
            $.ready(function() {
                this.ready = true;
                this.callChain();
            }.bind(this));
        },

        'create': function(options) {
            var widget = new $.widgets.Notify(options);
            
            widget.addEvents({
                'show': function() {
                    this.shown.include(widget);
                }.bind(this),

                'afterShow': function() {
                    if (this.wait) {
                        this.wait--;
                        this.callChain();
                    }
                }.bind(this),
                
                'afterHide': function() {
                    this.shown = this.shown.filter(function(item) {return item.display;});
                    var delta = 10;
                    this.shown.each(function(widget) {
                        widget.setOptions({'position': {'top': delta + 'px'}});
                        widget.move();
                        delta += widget.box.getSize().y + 10;
                    });
                }.bind(this)
            });

            var show = function() {
                var delta = 10;
                this.shown.each(function(widget) {
                    delta += widget.box.getSize().y + 10;
                });
                widget.setOptions({'position': {'top': delta + 'px'}});
                widget.show();
            }.bind(this);

            if (!this.wait && this.ready) {
                show();
            } else {
                this.wait++;
                this.chain(show);
            }
            
            return widget;
        }
    });

    $.widgets.ProcessBar = new Class({
        Implements: [Events, Options],

        'options': {'class': 'process bar',
                    'container': 'xsidebar',
                    'content': 'Процесс...',
                    'kill': true},

        'initialize': function(options, process) {
            this.setOptions(options);
            this.process = process;

            this.box = new Element('div', {'class': this.options['class'], 'styles': {
               'display': 'none'
            }}).grab(
                new Element('div', {'class': 'content'}).appendText(this.options.content)
            );

            this.options.kill &&
                new Element('a', {'class': 'close', 'href': '#close', 'title': 'Отменить'})
                    .appendText('x').inject(this.box)
                    .addEvent('click', function() {
                        this.process && this.process.kill();
                        return false;
                    }.bind(this));
                    
            this.box.grab(new Element('div', {'class': 'cf'}));
            var container = $.getElement(this.options.container, this.box.getDocument());

            this.addEvents({
                'start': function() {this.box.inject(container).show();},
                'success': function() {$.callable(this.box.hide) && this.box.hide();}, // verify cos of IE
                'error': function() {$.callable(this.box.hide) && this.box.hide();}, // verify cos of IE
                'complete': function() {this.box.destroy();},
                'kill': function() {this.box.destroy();}
            });
        }
    });

    var Process = new Class({
        Implements: [Events, Options],

        'options': {
            'widget': {'widgetClass': $.widgets.ProcessBar},
            'onStart': function(){}
        },

        'initialize': function(options) {
            var tempOptions = Object.merge(this.options, options);
            var events = [];

            for (var option in tempOptions) {
                if (0 == option.indexOf('on')) {
                    events.push(option.substr(2).toLowerCase());
                }
            }

            this.setOptions(options);

            this.widget = (this.options.widget && this.options.widget['widgetClass'])
                ? new this.options.widget['widgetClass'](this.options.widget, this)
                : undefined;
                
            events.each(function(event) {
                this.addEvent(event, function() {
                    this.widget && this.widget.fireEvent(event);
                }.bind(this));
                
                this['fire' + event.capitalize()] = function() {
                    this.fireEvent(event);
                    return this;
                }.bind(this);
            }.bind(this));
        }
    });

    $.dialog = function(options) {
        var trigger = $$(options.trigger);

        if (trigger.length) {
            var dialogs = [];
            trigger.each(function(item) {
                dialogs.push(new $.widgets.Dialog(Object.merge(options, {'trigger': item})));
            });
            if (dialogs.length == 1) {
                return dialogs[0];
            }
            return dialogs;
        }

        return new $.widgets.Dialog(options);
    }

    $.confirm = function(options) {
        return $.dialog(Object.merge({
            'buttons': {
                'ok': {'title': 'Да', 'onClick': function(d) {d.hide();}},
                'no': {'title': 'Нет', 'onClick': function(d) {d.hide();}}
            },
            'class': 'confirm',
            'title': 'Подтвердите, пожалуйста'
        }, options));
    }

    $.notify = function(content, options) {
        return notifyFactory.create(Object.merge({
            'content': content
        }, options || content));
    }

    $.process = function(options) {
        return new Process(options);
    }

    $.request = new (function() {
        var Pool = function() {
            var q = []; // pool queue
            var current = undefined; // cur process in the pool

            this.push = function(process) {
                process.pool = this;

                if (!process.options.wait) {
                    q = []; // clear queue
                    current && current.kill(); // kill cur process
                    current = undefined;
                }

                q.push(process);
                current || this.next();
            }

            this.next = function() {
                current = q.shift();
                current ? current.start() : current = undefined;
            }
        }

        var pools = {
            'default': new Pool(),
            'new': [] // empty pools array for anonymous pools passed in options as 'new'
        };

        this.request = function(options) {
            options = Object.merge(Object.clone($.conf.request), options);

            var process = new Process(Object.merge({
                'onStart': function(){},
                'onSuccess': function(){},
                'onError': function(){},
                'onComplete': function(){},
                'onKill': function(){}
            }, options, options.process));

            process.spinner = new (function(spinnerTarget) {
                this.spinner = spinnerTarget ? spinnerTarget.loading(): undefined;
                this.show = function() {this.spinner && this.spinner.show();}
                this.hide = function() {this.spinner && this.spinner.hide();}
            })($.getElement(options.spinnerTarget));

            var requestConditer = [Request, function(responseText, responseXML) {
                this.response = {
                    'text': responseText,
                    'xml': responseXML
                };
            }];

            if (options.type == 'json') {
                requestConditer = [Request.JSON, function(responseJSON, responseText) {
                    this.response = {
                        'json': responseJSON,
                        'text': responseText
                    }
                }];
            } else if (options.type == 'html') {
                requestConditer = [Request.HTML, function(responseTree, responseElements, responseHTML, responseJavaScript) {
                    this.response = {
                        'tree': responseTree,
                        'elements': responseElements,
                        'html': responseHTML,
                        'js': responseJavaScript
                    }
                }];
            }

            process.start = function() {
                this.request = new requestConditer[0](Object.merge(Object.clone(options), {
                    'onRequest': process.onstart.bind(process),
                    'onSuccess': process.onsuccess.bind(process),
                    'onFailure': process.onerror.bind(process),
                    'onComplete': process.oncomplete.bind(process),
                    'onCancel': process.onkill.bind(process)
                })).send();
            };

            process.onstart = function() {
                this.spinner.show();
                this.fireStart();
            }

            process.onsuccess = function() {
                requestConditer[1].apply(this, arguments);
                var data = this.response.json;
                var preventHandler = $.callable(this.options.handler)
                    ? this.options.handler.apply(this)
                    : false;
                if (!!!preventHandler && data && data.type && $.callable($.conf.jsonHandlers[data.type])) {
                    $.conf.jsonHandlers[data.type].apply(this);
                }
                
                this.fireSuccess();
                data && this.fireEvent((data.action + data.status).toString().toLowerCase());
            }

            process.onerror = function() {
                this.fireError();
            }

            process.oncomplete = function() {
                this.spinner.hide(); this.pool.next();
                this.fireComplete();
            }

            process.onkill = function() {
                this.spinner.hide(); this.pool.next();
                this.fireKill();
            }

            process.kill = function() {
                if (!this.killed) {
                    this.request.cancel();
                    this.killed = true;
                }
            }.bind(process);

            if ('new' == options.pool) {
                pools['new'].push(new Pool().push(process));
            } else {
                options.pool || (options.pool = 'default');
                pools[options.pool] || (pools[options.pool] = new Pool(options.pool));
                pools[options.pool].push(process);
            }

            return process;
        }

        this.get = function(url, options) {
            return this.request(Object.merge({
                'url': url,
                'method': 'get',
                'widget': {'content': 'загрузка...'}
            }, options || url));
        }

        this.post = function(url, options) {
            return this.request(Object.merge({
                'url': url,
                'method': 'post',
                'widget': {'content': 'отправка...'}
            }, options || url));
        }
    });

    $.url = function(url) {
        if ($.isRelUrl(url)) {
            url = '/' + $.trim($.conf.urls.base, '/') +
                  '/' + $.trim(url, '/') + trailSlash(url);
        }

        return url;
    }

    $.mediaUrl = function(url) {
        if ($.isRelUrl(url)) {
            url = '/' + $.trim($.conf.urls.media, '/') +
                  '/' + $.trim(url, '/') + trailSlash(url);
        }

        return url;
    }

    $.mediaStaticUrl = function(url) {
        if ($.isRelUrl(url)) {
            url = '/' + $.trim($.conf.urls.mediaStatic, '/') +
                  '/' + $.trim(url, '/') + trailSlash(url);
        }

        return url;
    }

    $.isAbsUrl = function(url) {
        return /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/.test(url);
    }

    $.isRelUrl = function(url) {
        return /^\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/.test(url);
    }

    $.isUrl = function(url) {
        return $.isAbsUrl(url) || $.isRelUrl(url);
    }

    $.trim = function (str, charlist) {
        charlist = !charlist ? ' \s\xA0' : charlist.replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, '\$1');
        var re = new RegExp('^[' + charlist + ']+|[' + charlist + ']+$', 'g');
        return str.replace(re, '');
    }

    var trailSlash = function(url) {
        if (-1 != url.indexOf('/?') || /\.[^\.\/]+$/.test(url)) {
            return '';
        }
        return '/';
    }

    Element.implement({
        'zoomer': function(options) {
            //return new ReMooz(this, Object.merge($.conf.zoomer, options));
        },
        'processing': function(options) {
            options = Object.merge(Object.clone($.conf.processing), options);
            options['class'] = 'spinner ' + options['class'];
            return new Spinner(this, options);
        },
        'loading': function(options) {
            options = Object.merge(Object.clone($.conf.loading), options);
            options['class'] = 'spinner ' + options['class'];
            return new Spinner(this, options);
        },
        'vanish': function(options) {
            options = Object.merge({
                'property': '',
                'onComplete': function() {},
                'destroyOnComplete': true,
                'tweenPadding': [false, false, false, false], // according to sides variable below
                'tweenMargin': [false, false, false, false], // according to sides variable below
                'tweenOpacity': false
            }, options);

            var tweenProps = {'border': 0}
            tweenProps[options.property] = 0;

            var complete = options.destroyOnComplete
                ? function() {options.onComplete.apply(this); this.destroy();}.bind(this)
                : options.onComplete.bind(this);

            var tweenPadding = options.tweenPadding;
            var tweenMargin = options.tweenMargin;
            var tweenOpacity = options.tweenOpacity;

            delete options['property'];
            delete options['destroyOnComplete'];
            delete options['tweenPadding'];
            delete options['tweenMargin'];
            delete options['tweenOpacity'];

            var vanishTween = new Fx.Morph(this, Object.merge({
                'duration': 'short'
            }, options, {'onComplete': complete}));

            var sides = ['Top', 'Right', 'Bottom', 'Left'];

            if (tweenPadding) {
                sides.each(function(side, _) {
                    tweenPadding[_] && (tweenProps['padding' + side] = 0);
                });
            }

            if (tweenMargin) {
                sides.each(function(side, _) {
                    tweenMargin[_] && (tweenProps['margin' + side] = 0);
                });
            }

            if (tweenOpacity) {
                new Fx.Tween(this, {
                    'duration': 'short',
                    'property': 'opacity',
                    'onComplete': function() {vanishTween.start(tweenProps);}
                }).start(0);
            } else {
                vanishTween.start(tweenProps);
            }
            return vanishTween;
        },
        'vanishWidth': function(options) {
            return this.vanish(Object.merge({
                'property': 'width',
                'tweenPadding': [false, true, false, true],
                'tweenMargin': [false, true, false, true]
            }, options));
        },
        'vanishFadeWidth': function(options) {
            return this.vanishWidth(Object.merge({'tweenOpacity': true}, options));
        },
        'vanishHeight': function(options) {
            return this.vanish(Object.merge({
                'property': 'height',
                'tweenPadding': [true, false, true, false],
                'tweenMargin': [true, false, true, false]
            }, options));
        },
        'load': function(url, options) {
            return $.request.get(Object.merge({
                'url': url,
                'update': this
            }, options || url));
        }
    });

    $.ready(function() {
        new Element('div', {'id': 'xsidebar', 'styles': {'z-index': 10000}}).inject(document.body);
        new Fx.SmoothScroll({links: 'a.smooth.anchor'});
    });

    window.$ = $;
    
})(document.id);
