/**
 * @class {abstract} ?
 * Abstract layer providing basic properties & methods for every layer. «Layers» are floating elements like,
 * for example, dialogs. Actually, this class is a wrapper, it does not create any element.
 */
Layer = Class.create({
    /** @var {protected element} ? Element this layer wraps. */
    element: null,

    /** @var {protected boolean} ? Tells if this layer is modal or not. If it is modal it is managed by the {@link LayerStack LayerStack}. */
    modal: false,

    /**
     * @constructor ?
     * @param {string} element Identifier for the layer {@link Layer.element element}.
     */
    initialize: function(element, options) {
		options = Object.extend({/* default options */}, options);
        this.element = $(element);
        this.modal = !!options.modal;
        this.element.setStyle({
            position: this.modal ? (navigator.appVersion.match(/MSIE 6/) ? "absolute" : "fixed") : "absolute",
            zIndex: 100
        });
    },

    /**
     * @function ? Shows this layer.
     * @see Layer.hide
     */
    show: function() {
        if(!this.element.visible()) {
            if(this.modal) {
                LayerStack.getInstance().raise(this);
            }
            else {
                this.element.show();
            }

			if(navigator.appVersion.match(/MSIE 6/)) {
				var iframe = $("divIE6Iframe");
				if(!iframe) {
					var body = $(document.body);
					body.insert({top: '<iframe id="divIE6Iframe" src="javascript:false;" frameborder="0" scrolling="no" style="position: absolute; display: none; width: 0; height: 0; z-index: 2;"/>'});
					iframe = $(body.firstChild);
				}
				iframe.style.top = this.element.cumulativeOffset().top + "px";		
				iframe.style.left = this.element.cumulativeOffset().left + "px";		
				iframe.style.width = this.element.getWidth() + "px";
				iframe.style.height = this.element.getHeight() + "px";	
				iframe.show();
			}
        }
    },

    /**
     * @function ? Hides this layer.
     * @see Layer.show
     */
    hide: function() {
		if(navigator.appVersion.match(/MSIE 6/) && $("divIE6Iframe")) {
			$("divIE6Iframe").hide();
		}    
        if(this.modal) {
            LayerStack.getInstance().collapse(this);
        }
        else {
            this.element.hide();
        }
    },

    /** @function ? Centers this layer.
     * @param frame Frame to consider for the centering.
     * @param {optional object} offset Optional offset from center: {x,y} as pixel values.
     */
    center: function(frame, offset) {
        // Allows elements to be used as frames.
        if(frame && frame.tagName) {
            var position = frame.cumulativeOffset();
            var dimensions = frame.getDimensions();
            frame = {
                x: position[0],
                y: position[1],
                width: dimensions.width,
                height: dimensions.height
            };
        }

        // In any other case, the default frame is the window itself.
        if(!frame) {
            frame = Object.extend({x: 0, y:0}, Zindow.getDimensions());
        }

        offset = Object.extend({
            x: 0,
            y: 0
        }, offset);

        var dimensions = this.element.getDimensions();
        var x = (frame.x + (frame.width / 2) - (dimensions.width / 2)) + offset.x;
        var y = (frame.y + (frame.height / 2) - (dimensions.height / 2)) + offset.y + (navigator.appVersion.match(/MSIE 6/)?document.documentElement.scrollTop:0);
        y = (y < 0) ? 0 : y;
        this.element.setStyle({
            left: x + "px",
            top: y + "px"
        });

		if(navigator.appVersion.match(/MSIE 6/)) {			
			var iframe = $('divIE6Iframe');
			if (iframe){
				iframe.style.top = this.element.cumulativeOffset().top + "px";		
				iframe.style.left = this.element.cumulativeOffset().left + "px";		
				iframe.style.width = this.element.getWidth() + "px";
				iframe.style.height = this.element.getHeight() + "px";
			}	
		}        
    },

    /**
     * @function {element} ? Returns the {@link Layer.element element} this layer wraps.
     */
    getElement: function() {
        return $(this.element);
    }
});


LayerStack = Class.create({
    layers: null,
    /** @var {protected element} ? Veil over the background giving the focused layer the exclusivity. */
    mask: null,
    elevation: 100,

    initialize: function() {
        this.layers = [];

        // The key listener must be bound to this instance in order to use the "this" keyword.
        this.keyListener = this.keyListener.bind(this);
        // Start listening the "escape" key.
        Event.observe(document, "keypress", this.keyListener);
    },

    raise: function(layer) {
        if(!this.layers.contains(layer)) {
            this.layers.push(layer);
        }

        var elevation = this.elevation;

        // Set the appropriate elevation for each layer.
        for(var index = 0; index < this.layers.length; index++) {
            var other = this.layers[index];
            if(other != layer) {
                other.element.setStyle({zIndex: elevation});
                elevation++;
            }
        }
        layer.element.setStyle({zIndex: elevation + 1});

        // Sort the layers array according to their elevation.
        this.layers = this.layers.sortBy(function(layer) {
            return parseInt(layer.element.getStyle("z-index"));
        });

        // Show the layer stack mask.
        if(!this.mask) {
            this.mask = Mask.create({
                opacity: 0,
                color: "#EEEEEE"
            });
        }
        this.mask.setStyle({zIndex: elevation}),
        this.mask.show();

        // Finally show the layer.
        layer.element.show();
    },

    collapse: function(layer) {
        if(layer && layer.element.visible()) {
            layer.element.hide();
            var cursor = this.layers.indexOf(layer);
            if(cursor > -1) {
                var elevation = this.elevation + cursor;
                for(var index = (cursor + 1); index < this.layers.length; index++) {
                    this.layers[index].element.setStyle({zIndex: elevation});
                    elevation++;
                }
                this.layers = this.layers.without(layer);
                if(this.layers.length > 0) {
                    this.layers[this.layers.length - 1].element.setStyle({zIndex: elevation});
                    this.mask.setStyle({zIndex: elevation - 1});
                }
                else {
                    this.mask.hide();
                }
            }
            else {
                // Hide all the layers and remove them from this stack.
                this.layers.each(function(layer) {
                    layer.element.hide();
                });
                this.layers = [];
                this.mask.hide();
            }
        }
    },

    getElevation: function() {
        return this.elevation;
    },

    // Listener which hides this layer when the "escape" key is pressed.
    keyListener: function(event) {
        if(event.keyCode == Event.KEY_ESC) {
            if((this.layers.length > 0) && this.mask.visible()) {
                this.collapse(this.layers[this.layers.length - 1]);
            }
        }
    }
});

Singleton.decorate(LayerStack);