var WBLAjaxAlert = new Class({
    
    initialize: function(uniqueID, remoteAction){
        window.WBLAjaxAlertByID[uniqueID] = this;
        this.remoteAction = remoteAction;
        this.nextSheetCommands = [];
    },
                             
    processCommandFromServer: function(command, parameters){
        if(command == 'close')
            this.endSheet();
        else if(command == 'shake')
            this.shake();
        else if(command == 'show'){
            // If the sheet is already visible, defer the next show command.
            var me = this;
            var block = function(){
                me.beginSheetWithParameters(parameters);
            };
            if(this.isShowingSheet)
                this.nextSheetCommands.push(block);
            else
                block();
        }
    },
    
    processNextSheetCommandIfNeeded: function(){
        var command = this.nextSheetCommands.pop();
        if(command)
            command();
    },

    // Can be overwritten in subclasses in order to return a form element for example.
    detailMessageElementFromParameters: function(parameters){
        return parameters.detailMessage ? parameters.detailMessage : null;
    },
        
    // Parameters are: imageURL, text, subtext and buttons which is an array of button titles.
    // subtext can be plain text or an HTML element.
    beginSheetWithParameters: function(p){
        this.topMarginWhenVisible=p['top'];
        this.shakeOffset = 0;
		this.windowResizedHandler = this.centerHorizontally.bind(this);
        this.shield = new Element('div', {'class':'alertShield'});
		this.alertContainer = new Element('div', {'class':'alertContainer'});
        var willShowMethodName = p['willShowMethod'];
        if(willShowMethodName)
            this.willShowMethod = window[willShowMethodName];
        var willHideMethodName = p['willHideMethod'];
        if(willHideMethodName)
            this.willHideMethod = window[willHideMethodName];

        // Prepare content
        var image = p.imageURL;
        var text = p.message;
        var subtext = this.detailMessageElementFromParameters(p);
        
        var alertTable          = new Element('table', {'class':'alertTable'});
        var tableBody           = new Element('tbody');
        var tr1                 = new Element('tr');
        var alertTextContainer  = new Element('td', {'id':'alertTextContainer', styles:{'text-align':'left'}});
        var alertText           = new Element('span', {'id':'alertText'});
        var p1                  = new Element('p', {styles:{}});
        var alertSubtext        = new Element('span', {'id':'alertSubtext'});
        var alertText           = new Element('span', {'id':'alertText'});
        var alertTop            = new Element('li', {'class':'alertTop', styles:{'list-style-type':'none'}});
        var alertBottom         = new Element('li', {'class':'alertBottom', styles:{'list-style-type':'none'}});
        this.alertTitle         = new Element('div', {'class':'alertTitle'});
        alertTop.appendChild(this.alertTitle);
        var title = p['alertTitle'];
        if(title)
            this.alertTitle.appendText(title);
        else
            alertTop.setStyle('display', 'none');
        this.alertContainer.appendChild(alertTop);
        this.alertContainer.appendChild(alertBottom);
        alertBottom.appendChild(alertTable);
        
        alertTable.appendChild(tableBody);
        tableBody.appendChild(tr1);
		if(image){
            var alertImageContainer = new Element('td', {'id':'alertImageContainer'});
            var alertIcon = new Element('img', {'id':'alertIcon', 'src':image});
            alertImageContainer.appendChild(alertIcon);
            tr1.appendChild(alertImageContainer);
        }
        tr1.appendChild(alertTextContainer);
        alertTextContainer.appendChild(alertText);
        alertTextContainer.appendChild(p1);
        p1.appendChild(alertSubtext);

        var tr2 = new Element('tr');
        var alertButtonContainer = new Element('td', {'id':'alertButtonContainer', 'colspan':(image ? 2 : 1)});
        tableBody.appendChild(tr2);
        tr2.appendChild(alertButtonContainer);

        this.shield.inject(document.body);
        this.alertContainer.inject(document.body);

		var me = this;
        this.buttons = p.buttons;
        var buttonCount = p.buttons ? p.buttons.length : 0;
        
		for(var i=0; i<buttonCount; i++){
			var label = p.buttons[i];
			var button = new Element('div', {'class':'Button'});
            var isFirst = i==0;
            var isLast  = i+1==buttonCount;
            if(isFirst)
                button.addClass('default');
            
            button.appendChild(document.createTextNode(label));

            // The first button is the one whose action is validated if needed (the 'OK' button).
            // (from left to right) The last button is the escape button (usually the 'Cancel' button)
            me.addClickHandlerToButton(button, i);
			button.value = label;
			alertButtonContainer.appendChild(button);
			this.setSelectionEnabled(button, false);
		}
        
		if(text)
			alertText.appendChild(document.createTextNode(text));

		if(subtext) {
			if(typeof subtext == 'string')
				alertSubtext.appendChild(document.createTextNode(subtext));
			else {
				this.customElement = subtext;
				alertSubtext.appendChild(subtext);
                this.prepareInputElements(alertSubtext.getElements('input'));
			}
		}
        
        // Prepare content end
        
		this.alertWidth = this.alertContainer.getWidth(); 
		this.alertHeight = this.alertContainer.getHeight(); 
		this.alertContainer.style.top = -this.alertHeight+"px";
		this.alertContainer.style.visibility = "visible";
		this.centerHorizontally();
        this.createKeyboard();
        this.beginSheet();
    },
        
    beginSheet: function(){
        this.isShowingSheet = true;
        window.addEvent('resize', this.windowResizedHandler);
        var shieldMorpher = new Fx.Morph(this.shield, {duration: 'short'}).start({'opacity':[0, 0.2]});
        var top = this.topMarginWhenVisible;
        if(this.willShowMethod)
           this.willShowMethod();
        new Fx.Move(this.alertContainer, {
            relativeTo: document.body,
            position: 'upperLeft',
            duration: 'short',
            transition:Fx.Transitions.Expo.easeinOut,
            offset: {x:this.alertContainer.getPosition().x, y:top}
        }).start();
    },
    
    shake: function(){
		var shakeTimer;
		var shakes = 8;
		var me = this;
		var shakerMethod = function() {
            if(me.isShowingSheet){
                me.shakeOffset = ((shakes--)%2==0) ? 20 : 0;
                me.centerHorizontally();
                if(!shakes)
                    window.clearInterval(shakeTimer);
            }
		}
		shakeTimer = window.setInterval(shakerMethod, 50);
    },
    
    endSheet: function(){
        if(this.willHideMethod)
           this.willHideMethod();
        var alertContainer = this.alertContainer;
        window.removeEvent('resize', this.windowResizedHandler);
        var me = this;
        var shieldMorpher = new Fx.Morph(this.shield, {
            duration: 'short',
        }).start({'opacity':[0.15, 0]});
        var mover = new Fx.Morph(alertContainer,{
            duration: 'short',
            transition:Fx.Transitions.Expo.easeInOut
        }).addEvent('complete', function(){
            me.isShowingSheet = false;
            alertContainer.destroy();
            me.shield.destroy();
            me.disposeKeyboard();
            me.processNextSheetCommandIfNeeded();
        }).start({'top':[me.topMarginWhenVisible, -me.alertHeight]});
    },
    
    centerHorizontally: function(){
        this.alertContainer.style.left = ((window.innerWidth-this.alertWidth)/2)+this.shakeOffset+"px";
    },
    
    createKeyboard: function(){
        this.keyboard = new Keyboard();
        this.keyboard.addEvents({
            'esc':this.onEscape.bind(this),     // activates the leftmost button
            'enter':this.onEnter.bind(this),    // activates the rightmost button
            'tab':this.onTab.bind(this)         // puts the foxus on the next input field
        });
        this.keyboard.activate();
    },

    disposeKeyboard: function(){
        var keyboard = this.keyboard;
        keyboard.relinquish();
        keyboard.deactivate();
    },

    onEscape: function(){
        if(this.buttons.length > 0)
            this.onButtonAction(this.buttons.length-1);
    },
    
    onEnter: function(){
        if(this.buttons.length > 0)
            this.onButtonAction(0);
    },

    onTab: function(event){
        this.focusNextInput();
        event.stop();
    },

    // Can be overwritten in subclasses
    parametersForButtonAction: function(buttonIndex){
        return this.formValues();
    },

    onButtonAction: function(index){
        // For an unknown reason the button is focused on click and the currently focused input field loses focus.
        // So we need to restore it after the click.
        var focusedInput = this.focusedInput;
        if(focusedInput)
            this.focusInput(focusedInput);
        var parameters = this.parametersForButtonAction(index);
        parameters['WBLAjaxAlertCommand'] = 'buttonClicked';
        parameters['WBLAjaxAlertButtonIndex'] = index;        
        this.remoteAction(parameters);
    },

    addClickHandlerToButton: function(button, index){
        var me = this;
        button.onclick = function(event) {
            me.onButtonAction(index);
        };
    },

    prepareInputElements: function(inputElements){
        this.inputs = inputElements;
        var me = this;
        if(inputElements) {
            var count = inputElements.length;
            if(count > 0){
                for(var i=0; i < count; i++) {
                    var input = inputElements[i];
                    input.index = i;
                    input.addEvent('focus', function(e){
                        me.focusedInput = e.target;
                    });
                }
                // focus the first text input after a delay, because the sheet is faded in and at this point in time a focus call has no effect.
                window.setTimeout(function(){
                    me.focusFirstTextInput();
                }, 0);
            }
        }
    },
    
    focusFirstTextInput: function(){
		if(this.inputs) {
            var inputElements = this.inputs;
            for(var i=0; i < inputElements.length; i++) {
                var input = inputElements[i];
                var type = input.getAttribute('type');
                if(!input.disabled && input.getStyle('display') != 'none' && (type == 'text' || type == 'password')){
                    this.focusInput(input);
                    break;
                }
            }
        }
    },

	focusNextInput: function() {
		if(this.inputs) {
            var oldIndex = this.getIndexOfFocusedInput();
            var newIndex = null;
            var firstFocusableIndex = null;
            for(var i=0; i<this.inputs.length; i++) {
                var input = this.inputs[i];
                if(!input.disabled && input.getStyle('display') != 'none' && input.isVisible()){
                    if(i > oldIndex){
                        newIndex = i;
                        break;
                    }else if(i != oldIndex && firstFocusableIndex == null)
                        firstFocusableIndex = i;
                }
            }
            if(newIndex == null)
                newIndex = firstFocusableIndex;
            var newInput = this.inputs[newIndex]
            this.focusInput(newInput);
		}
	},
    
    getIndexOfFocusedInput: function(){
        return this.focusedInput.index;
    },
        
    focusInput: function(newFocusedInput){
		if(this.inputs) {
            for(var i=0; i<this.inputs.length; i++) {
                var input = this.inputs[i];
                if(newFocusedInput == input){
                    if(input.focus) {
                        input.focus();
                        if(input.select)
                            input.select();
                    }
                    this.focusedInput = newFocusedInput;
                }
            }
        }
    },

    formValues: function(){
        var formValues = {};
		if(this.inputs) {
            for(var i=0; i<this.inputs.length; i++) {
                var input = this.inputs[i];
                if(input.getAttribute('type')!='radio' || input.checked)
                    formValues[input.name] = input.value;
            }
        }
        return formValues;
    },
    
    setSelectionEnabled: function(element, enable){
		var changed = true;
		if(element.selectionState)
			changed = element.selectionState.enable != enable;
		element.selectionState = {enable:enable};
		if(changed){
			if(enable){
				element.style.cursor = element.origCursor;
				element.onselectstart = element.origOnSelectStart;
				element.unselectable = "off";
				element.style.MozUserSelect = "";
				element.style.KhtmlUserSelect = "";
			}else{
				if(!element.origOnSelectStart){
					element.origOnSelectStart = element.onselectstart;
					element.origCursor = element.style.cursor;
				}
				element.onselectstart = function() {
					return false;
				};
				element.unselectable = "on";
				element.style.MozUserSelect = "none";
				element.style.KhtmlUserSelect = "none";
				element.style.cursor = "default";
			}		
		}
	},

});

window.WBLAjaxAlertByID = window.WBLAjaxAlertByID ? window.WBLAjaxAlertByID : {};

window.WBLAjaxAlertWithID = function(uniqueID){
    return window.WBLAjaxAlertByID[uniqueID];
}
