/**
 * @class {abstract} ?
 * Abstract dialog widget providing basic properties & methods for every dialog component.
 */
Dialog = Class.create(Layer, {
    /** @var {private element} ?
     * Veil over this dialog content. Masks this dialog when its {@link Dialog.content content} is {@link Dialog.update
     * updated}.
     */
    innerMask: null,

    _request: null,

    /** @var {private object[]} ? */
    schedules: null,
    listeners: null,
    type: "AJAX",
    printable: false,

    /**
     * @constructor ?
     * Creates a dialog using the common HTML template.
     * @param {string} id Identifier for this dialog {@link Layer.element element}.
     */
    initialize: function($super, id, options) {
		if($(id)) {
			throw new Error("Element with id \"" + id + "\" already exists!");
		}

		Dialog.instances.push(this);

        options = Object.extend({title: "dialog"}, options);

        this.printable = !!options.printable;

        var dummy = new Element("div");
        dummy.innerHTML = Dialog.template.evaluate({
            title: options.title,
            instance: Dialog.instances.indexOf(this)
        });
        $super(dummy.firstChild, options);
        this.element.id = id;
        this.element.hide();
        if (options.style){
        	this.element.setStyle(options.style);
        }
        document.body.appendChild(this.element);

		if(!navigator.appVersion.match(/MSIE 6/)) {
        	// Allow "drag & drop" on this dialog, using its header to grab it.
        	new Draggable(this.element, {handle: this.element.down("div.title"), starteffect: null, endeffect: null});
		}
		
        // Use a distinct instance of schedules array.
        this.schedules = [];
        this.listeners = [];

        /* pre-loading */new Image().src = "/resources/images/close-highlighted.gif";
    },

    /**
     * @function ? Shows this dialog, after some common operations:
     * <ul><li>show the page {@link Mask mask} to have the user interactivity excelusive to this dialog;</li>
     * <li>update this dialog content using the {@link Dialog.update update} method;</li>
     * <li>center this dialog.</li></ul>
     */
    show: function($super) {
        if(!this.element.visible()) {
            var schedule = function() {
                $super(); // Layer#show
                if(this.printable) {
                    this.element.siblings().each(function(sibling) {
                        sibling.addClassName("not-printable");
                    });
                }
            }.bind(this);

            if(this.type == "AJAX") {
                this.schedule(schedule);
                this.update();
            }
            else {
                this.update();
                schedule.apply(this);
            }
        }
    },

    /**
     * @function ? Hides this dialog.
     * @see Dialog.show
     */
    hide: function($super) {
        $super();

        if(this.printable) {
            this.element.siblings().each(function(sibling) {
                sibling.removeClassName("not-printable");
            });
        }

// :TODO: writes a generic "dispatch" function
        this.listeners.each(function(listener) {
            if(listener["notifyHiding"]) listener.notifyHiding(this.currentField);
        }.bind(this));
    },

    /**
     * @function ? Adds a listener to this dialog.
     * @see Dialog.show
     */
    addListener: function(listener) {
        this.listeners.push(listener);
    },

    /**
     * @function {element} ? Returns this dialog content.
     */
    getContent: function() {
        return this.element.down("div.content");
    },

    /**
     * @function {abstract} ?
     * Updates this dialog content.
     */
    update: function() {
        throw new Error("Dialog#update is abstract, it must be implemented.");
    },

    /**
     * @function {protected} ? Performs a request using HTTP (AJAX). Actually, it is a wrapper for
     * the Ajax.Request usage providing a way to defer some operations.
     * @see Dialog.schedule
     */
    request: function(url, options) {
        // Default callbacks definition.
        options = Object.extend({
            onFailure: function(response) {
                this.getContent().update('Request failed (' + response.status
                    + '), <a href="#">try again</a>.');
                Event.observe(this.getContent().down("a"), "click", function() {
                    this.update();
                }.bind(this));
            }.bind(this),

            onException: function(response, exception) {
                console.error(exception);
            },

            // tells the spinner to show when this dialog is being updated using HTTP
            spinner: true
        }, options);

        // Memorize the original callbacks.
        var callbacks = {
            loading: options.onLoading,
            complete: options.onComplete
        };

        Object.extend(options, {
            onComplete: function() {
//                AJAXSpinner.hide();
                if(this.innerMask) {
                    this.innerMask.hide();
                }

                // Execute the original "complete" callback.
                if(callbacks["complete"]) {
                    callbacks["complete"].apply();
                }

                // Run the schedules.
                var exception = null;
                try {
                    this.notifyComplete();
                }
                catch(_exception) {
                    exception = _exception;
                }

                // Throw the possible exception after hiding the mask / spinner.
                if(exception) {
                    throw exception;
                }
            }.bind(this)
        });
/*
        // Is the spinner supposed to show?
        if(options.spinner && this.element.visible()) {
            var content = this.getContent();
            this.innerMask = Mask.create({subject: content, cursor: "wait"});
            this.innerMask.show();
            var position = content.cumulativeOffset();
            var dimensions = content.getDimensions();
            AJAXSpinner.show({
                x: position[0] + (dimensions.width - 50) / 2,
                y: position[1] + (dimensions.height - 50) / 2
            });
        }
*/
        this._request = new Ajax.Request(url, options);

        return this._request;
    },

    /**
     * @function ? Schedules an operation.
     * All these operations are executed once after the next request then they are flushed from the
     * schedule list.
     * @see Dialog.request
     */
    schedule: function(operation) {
        this.schedules.push(operation.bind(this));
    },

    notifyComplete: function() {
        this.schedules.each(function(operation) {
            operation.apply();
        });
        this.schedules = [];
    }
});

Object.extend(Dialog, {
    /** @var {private} ? Dialog instances. */
    instances: [],

    /** @var {private} ? Template used to produce a dialog. */
    template: new Template('<div class="dialog">'
        + '    <div class="header">'
        + '        <div class="commands">'
        + '            <a class="button" href="javascript:Dialog.getInstance(%instance%).hide();">'
        + '                <span class="label">' + closeLabel  + '</span>'
        + '                <img src="/static/gfx/adr/details_close.gif"/>'
        + '            </a>'
        + '        </div>'
        + '        <div class="title">%title%</div>'
        + '    </div>'
        + '    <div class="content"><!-- ... --></div>'
        + '    <!--div class="footer">...</div-->'
        + '</div>',
        /(^|.|\r|\n)(%\s*(\w+)\s*%)/),

    getInstance: function(index) {
        return Dialog.instances[index || 0];
    }
});


SimpleDialog = Class.create(Dialog, {
    content: null,

    initialize: function($super, id, content, options) {
        $super(id, options);
        this.type = "default";

        if($(content)) {
            this.getContent().append($(content));
        }
    },

    update: function() {
    }
});


// WARP-specific ---------------------------------------------------------------------------------

PopUp = Class.create(Dialog, {
    initialize: function($super, target, options) {
        options = Object.extend({title: null}, options);
        PopUp.register(this, target);
        $super(options.id || ("popUp" + PopUp.getIndex(this)), options);
        this.getContent().setStyle({
        	width: options.width ? (options.width + "px") : null,
			height: options.height ? (options.height + "px") : null
        });
        this.type = options.type || "AJAX";
        this.target = target;

        if(this.type != "AJAX") {
            var target = $(this.target);
            if(target) {
                target.show();
                this.getContent().append(target);
            }
            else {
                throw new Error("Unkonwn target: \"" + target + "\".");
            }
        }
    },

    update: function() {
        if(this.type == "AJAX") {
            this.request(this.target, {
                onSuccess: function(response) {
                    this.getContent().update(response.responseText);
                }.bind(this)
            });
        }
    }
});

Registry.decorate(PopUp);

/**
 * Replacing the old pop-up system. Now, for example, in the data-list we would have:
 *     <Link url="/popup.do?id=browserRequirements" onclick="popUp(this.href); return false;">...</Link>
 * or in a HTML fragment:
 *     <a href="/popup.do?id=browserRequirements" onclick="popUp(this.href); return false;">...</a>
 * The URL is the one used previously to open a browser-native dialog. Using it directly would lead to the associated
 * HTML page which content is now retrieved with AJAX.
 * 
 * The "popUp" function can also be used for static HTML content hidden somewhere initially. For that, the "type" option
 * should be set with something else than "AJAX". For example:
 *     <a href="javascript:popUp('myHiddenDiv');">?</a>
 */
function popUp(target, options) {
	options = Object.extend({/* default options */}, options);

	var popUp = PopUp.get(target);
    if(!popUp) {
        popUp = new PopUp(target, options);
    }

    if(options.type == "AJAX") {
        popUp.schedule(function(){popUp.center();});
    }


    if(options.type != "AJAX") {
        popUp.center();
    }

    popUp.show();

    return popUp;
}
