///<reference path="/Web/Js/TESCO.js" />
///<reference path="/Web/Js/system/event.js" />
///<reference path="/Web/Js/system/event.manager.js" />
///<reference path="/Web/Js/system/exception.js" />
///<reference path="/Web/Js/system/DOM.node.js" />
///<reference path="/Web/Js/system/connection/XMLHTTP.js" />
///<reference path="/Web/Js/system/connection/ajax.js" />
///<reference path="/Web/Js/system/serialization.js" />
///<reference path="/Web/Js/UI/position.js" />
///<reference path="/Web/Js/UI/effects.js" />

/*
Abstract class, calls the load method, but does not implement it.

Take care to remove eventListeners after derived instances are finished with.
*/

TESCO.$("sites.retail.UI").Dialogue = (function() {

	//	constants
	var NODE = TESCO.system.DOM.node;
    var EVENT = TESCO.system.event;
    var RECT = TESCO.UI.Rectangle;

    //  private static
    var _loaded = false;
    var _active = false;
    var _waiting = [];
    var _dialogue;
    var _containerDiv;
    var _form = null;
    var _actions = {
        "cancel": "cancel",
        "confirm": "confirm"
    }
    var _action = _actions.confirm; //	assume the confirm button is the default submit button
    var _infoContainer = new TESCO.sites.retail.UI.InfoContainer();
    var _originalHeight;
    var _heightDiff;
    var _scrollContainer;
    
    function _submit(e, o) {
        var _dispatchAction = _action;
        if (_action === _actions.cancel) {
            _action = _actions.confirm;
            o.hide();
            e.prevent();
        }
        //  dispatch confirm/cancel event with submit event as argument
        _dialogue.dispatchEvent(_dispatchAction, e, true);
        if (!o.allowsubmit) {
            e.prevent();
        }
    }

    function _position() {
        _dialogue.makeCentreOfWindow();
    }
    
    function _size() {
		if (_scrollContainer) {
			var _margin = TESCO.sites.Configuration.dialogue.margin;
			var _winHeight = TESCO.UI.document.getWindowSize().innerHeight - (_margin.top + _margin.bottom);
			var _height = _infoContainer.container.offsetHeight;

			if (_height > _winHeight) {	
				//	page is too small to display the content of the dialogue
				_dialogue.resizeH(_winHeight);
				_scrollContainer.resizeH(_winHeight - _heightDiff);
			} else if (_height < _originalHeight) {
				//	page has previously been resized smaller than the dialogue
				_dialogue.resizeH(_originalHeight);
				_scrollContainer.resizeH(_originalHeight - _heightDiff);
			}
		}
        _position();
	}
	
    //  private instance
    function _render(content) {
        if (content.nodeType == 1) {
			NODE.append(_infoContainer.content, content);
        } else {
            this.hide();
            throw new Error(this.NAME + ": content must be an element");
        }
        var _rect = new RECT.Position(content);
        _rect.css.display = "block";
		this.resize(TESCO.sites.Configuration.infoContainer.left + _rect.width + TESCO.sites.Configuration.infoContainer.right, _infoContainer.container.offsetHeight); 
		//	does the dialogue have a scrollable area?
		if (_scrollContainer = NODE.getElementsByClassName(_infoContainer.container, "scrollContainer")) {
			_scrollContainer = new RECT.Position(_scrollContainer[0]);
			_originalHeight = _infoContainer.container.offsetHeight;
			_heightDiff = _originalHeight - _scrollContainer.height;
		}
		_size();
        this.raise();
        //  call base show method
        _constructor.base.show.call(this);
        this.dispatchEvent("render", this);
        _form = _containerDiv.getElementsByTagName("form");
        if (_form.length > 0) { // stop cancel button submitting form
            EVENT.attach(_form[0], "submit", _submit, this);
        } else {
            _form = null;
        }
    }

    //  constructor
    function _constructor() {
        //  container
        _containerDiv = NODE.create("div");

        //  event delegates
        //  wait for the body
        EVENT.document.addEventListener("load",
            function() {
                //  attach div to body
                _containerDiv.appendChild(_infoContainer.container);
                document.body.appendChild(_containerDiv);

                // delegate click handler
                EVENT.attach(_containerDiv, "click",
		            function(e) {
		                if (NODE.hasClassName(e.target, _actions.confirm)) {
		                    if (!_form) {
		                        _dialogue.dispatchEvent(_actions.confirm, e);
		                    }
		                } else if (NODE.hasClassName(e.target, _actions.cancel)) {
		                    if (_form) {
		                        _action = _actions.cancel;
		                    } else {
		                        _dialogue.dispatchEvent(_actions.cancel, e);
		                        _dialogue.hide();
		                    }
		                }
		            }
		        );

                //  window scroll and resize events
                if (TESCO.system.browser.supportsFixed) {
                    NODE.addClassName(_containerDiv, "hidden fixed");
                } else {
                    NODE.addClassName(_containerDiv, "hidden abs");
                    EVENT.attach(window, "scroll", _position);
                }
				EVENT.attach(window, "resize", 
					function() {
						if (_active) {
							_size();
						}	
					}
				);

                //  links with 'dialogue' class
                EVENT.attach(document.body, "click",
				    function(e) {
				        if (NODE.hasClassName(e.target, "dialogue")) {
				            var _id = e.target.id;
				            if (_id) {
				                var _key = (_id.indexOf("-") > -1) ?
					                            _id.slice(0, _id.indexOf("-")) : _id;
				                if (TESCO.sites.retail.UI.Dialogue[_key]) {
				                    new TESCO.sites.retail.UI.Dialogue[_key](_key, e).show();
				                    e.prevent();
				                } else {
				                    throw new Error(_dialogue.NAME + "['" + _key + "'] is undefined");
				                }
				            } else {
				                throw new Error(_dialogue.NAME + ": '" + e.target + "' id attribute is required");
				            }
				        }
				    }
				);
            }
        );

        //  extend 'this'
        EVENT.manager.call(this, _actions.cancel, _actions.confirm, "hide", "render", "result", "error", "loaded");
        _constructor.base.constructor.call(this, _containerDiv);

        //  finished loading
        _loaded = true;

        return this;
    }
    _constructor.extend(RECT.Position);

    _constructor.prototype.NAME = "TESCO.sites.retail.UI.Dialogue";
 
    //  public methods
    //  override position show
    _constructor.prototype.show = function(content) {
        if (!_active) {
            _active = true;
            if (_waiting.length === 0) {
                TESCO.UI.effects.Background.document.show(); //  fade up the background
            }
            //  call abstract method
            if (!this.load) {
                this.hide();
                throw new Error(this.NAME + ": abstract load method undefined");
            }
            this.load(_render, content);
        } else {
            _waiting.push({
                "dialogue": this,
                "content": content
            });    //  add to the count of dialogues being held back
        }
    }

    //  augment position hide
    _constructor.prototype.hide = function(e) {
        if (_form) {
            EVENT.detach(_form[0], "submit", _submit);
        }
        _active = false;
        if (_waiting.length === 0) {
            TESCO.UI.effects.Background.document.hide();
        }
        _constructor.base.hide.call(this);
        NODE.removeChildNodes(_infoContainer.content);
        this.dispatchEvent("hide");

        if (_waiting.length > 0) {	//  when the current dialogue hides, we can show this one
            setTimeout(
				function() {
				    _dialogue.show.call(_waiting[0].dialogue, _waiting[0].content); //  show the content
				    _waiting.splice(0, 1); //  decrement the count of waiting dialogues
				}, 250
			);
        }
    }

    //  return dialogue instance
    _dialogue = new _constructor()
	
    _dialogue.content = _infoContainer.content;

    return _dialogue;
})();

/**/

TESCO.$("sites.retail.UI.Dialogue").Request = function(key, action, params) {
    this.request = {
        "dialogue": {
            "name": key,
            "action": action || null,
            "params": params || null
        }
    }
}

TESCO.sites.retail.UI.Dialogue.Ajax = (function() {

    //  private static variables/functions
    var _url = TESCO.sites.Configuration.application.path + "/Ajax/Dialogue.aspx";
    var _attach = TESCO.system.event.attach;
    var CONNECTION = TESCO.system.connection;

    function _connect(callback) {
        var _connection = new CONNECTION.ajax(false);
        _attach(_connection, "complete", callback);
        return _connection;
    }

    function _request(connection, url, entity) {
        connection.request(url, entity, CONNECTION.XMLHTTP.METHOD.post, CONNECTION.XMLHTTP.MIMETYPE.xml);
    }

    //	public instance
    function _post(callback, entity) {
        var _connection = _connect(callback);
        var _self = this;
        _attach(_connection, "error",
			function(e) {
			    _self.dispatchEvent("error", e);
			    e.exception.dialogue.show(e.exception);
			    TESCO.sites.retail.UI.Dialogue.hide();
			}
		);
        _request(_connection, _url, entity);
    }

    //  constructor
    function _constructor(entity, allowsubmit) {        
        this.entity = entity;
        this.allowsubmit = allowsubmit || false;
        return this;
    }
    TESCO.sites.retail.UI.Dialogue.extend(_constructor);

    _constructor.prototype.NAME = "TESCO.sites.retail.UI.Dialogue.Ajax";

    //  implement load abstract method
    _constructor.prototype.load = function(callback) {
        var _self = this;
        //  show loader
        callback.call(_self, TESCO.sites.retail.UI.Loader.rect);
        _post.call(this,
            function(e) {   //  success callback, update content
                TESCO.system.DOM.node.removeChildNodes(TESCO.sites.retail.UI.Dialogue.content);
                callback.call(_self, e.response.response.content.nodeValue.firstChild);
                _self.dispatchEvent("loaded");
            },
            _self.entity
        );
    }

    _constructor.prototype.post = function(callback, url, entity) {
        var _connection = _connect(callback);
        _request(_connection, url, entity);
    }

    //  return constructor as function pointer
    return _constructor;
})();

/*
Derived from TESCO.sites.retail.UI.Dialogue, implements load method, augments show
*/
TESCO.sites.retail.UI.Dialogue.Static = (function() {

    //  private static
    var NODE = TESCO.system.DOM.node;

    function _assemble(content, cancelable) {
        var _container = NODE.create("div");
        _container.setAttribute("id", "staticDialog");      
        _container.appendChild(content);

        var _buttons = NODE.create("div");
        NODE.addClassName(_buttons, "dialogueButtonsCtrl");
        _container.appendChild(_buttons);

        var _btn = NODE.create("input");
        _btn.setAttribute("type", "image");
        _btn.setAttribute("alt", TESCO.locale.dialogue.button.ok.alt);
        _btn.setAttribute("value", TESCO.locale.dialogue.button.ok.value);
        _btn.setAttribute("src", TESCO.locale.dialogue.button.ok.src);
        
        if (cancelable) {
			NODE.addClassName(_btn, "confirm");
			_buttons.appendChild(_btn);
			_btn = NODE.create("input");
			_btn.setAttribute("type", "image");
			_btn.setAttribute("alt", TESCO.locale.dialogue.button.cancel.alt);
			_btn.setAttribute("value", TESCO.locale.dialogue.button.cancel.value);
			_btn.setAttribute("src", TESCO.locale.dialogue.button.cancel.src);
        }
       
        NODE.addClassName(_btn, TESCO.locale.dialogue.button.btnClass);
        _buttons.appendChild(_btn);

        return _container;
    }

    //  constructor
    function _constructor() {

        return this;
    }
    TESCO.sites.retail.UI.Dialogue.extend(_constructor);

    _constructor.prototype.NAME = "TESCO.sites.retail.UI.Dialogue.Static";

    //  public methods
    _constructor.prototype.load = function(callback, content) {
        callback.call(this, _assemble(content, this.cancelable));
    }

    //  augment show
    _constructor.prototype.show = function(content, cancelable) {
        if (!content) {
            throw new Error(this.NAME + ".show: content is undefined");
        }
        if (content.nodeType !== 1) {
            throw new Error(this.NAME + ".show: content must be an element");
        }
        this.cancelable = !!cancelable;
        _constructor.base.show.call(this, content);
    }

    //  return instance of _constructor
    return new _constructor();
})();

/*
Derived from TESCO.sites.retail.UI.Dialogue.Static
Take a string, put it in a <p><strong/></p> and show it in a dialogue
*/
TESCO.sites.retail.UI.Dialogue.Simple = (function() {

    //  constructor
    function _constructor() {
        return this;
    }
    TESCO.sites.retail.UI.Dialogue.Static.extend(_constructor);

    _constructor.prototype.NAME = "TESCO.sites.retail.UI.Dialogue.Simple";

    _constructor.prototype.show = function(message, cancelable) {
        _constructor.base.show.call(this, TESCO.locale.message.create(message), cancelable);
    }

    //  return constructor as function pointer
    return new _constructor();
})();

/* 
Derived from TESCO.sites.retail.UI.Dialogue.Simple
Show a confirm dialogue with an error message
*/
TESCO.sites.retail.UI.Dialogue.Error = (function() {

    //  constructor
    function _constructor() {
        return this;
    }
    TESCO.sites.retail.UI.Dialogue.Simple.extend(_constructor);

    _constructor.prototype.NAME = "TESCO.sites.retail.UI.Dialogue.Error";
    
    _constructor.prototype.show = function(exception) {
        _constructor.base.show.call(this, exception.message);
    }

    //  return instance
    return new _constructor();
})();

/*
Derived from TESCO.sites.retail.UI.Dialogue.Error
Show a confirm dialogue, and redirect to 302 location on confirm
*/
TESCO.sites.retail.UI.Dialogue.AuthenticationError = (function() {

    //  private static
    var _location = null;
    var _self;

    //  constructor
    function _constructor() {
        return this;
    }
    TESCO.sites.retail.UI.Dialogue.Error.extend(_constructor);

    _constructor.prototype.NAME = "TESCO.sites.UI.Dialogue.AuthenticationError";

    _constructor.prototype.show = function(exception) {
        this.addEventListener("hide",
            function() {
                //  redirect to 302 response 'Location' 
                document.location.href = exception.location + "?from=" + escape(document.location.href);
            }
        );
        _constructor.base.show.call(this, exception);
    }

    //  return instance
    return (_self = new _constructor());
})();

/*
Show a dialogue after a delay
Parameters: totalTime (secs) - is the total time before feature expires
dialogTime (secs) - is the time that the dialogue should show 
before expiry of the feature i.e. the dialogue 
displays in totalTime-dialogtime
type - string appended to URL to retrieve dialogue content
    
if a dialogue is already on screen, the timed dialogue will wait 
until it's cleared
    
if the tag id 'timeRemaining' is present in the content, it will be 
replaced by the remaining time in minutes.
*/
TESCO.sites.retail.UI.Dialogue.Countdown = (function() {

    //  constants
    var MILLISINMIN = 60000;

    //  constructor
    function _constructor(totalTime, dialogTime, key) {
        this.cutOff = (new Date()).getTime() + (totalTime * 1000); //  convert to milliseconds
        var _self = this;
        this.timeout = setTimeout(
            function() {
                _self.show()
            }, (totalTime - dialogTime) * 1000);
        _constructor.base.constructor.call(this, new TESCO.sites.retail.UI.Dialogue.Request(key));
        return this;
    }
    _constructor.extend(TESCO.sites.retail.UI.Dialogue.Ajax);

    //  public methods/attributes
    _constructor.prototype.NAME = "TESCO.sites.retail.UI.Dialogue.Countdown";
    
    //  fired on render event to update the content before it hits the screen, augment show
    _constructor.prototype.show = function() {
        // grab the render event ready to update the content
        var _self = this;
        this.addEventListener("render", // when the dialogue is shown, check for a 'time remaining' field and update if found
			function() {
			    var _remNode = document.getElementById('timeRemaining');
			    if (_remNode) {
			        var _now = (new Date()).getTime();
			        //  _remainder is the number of seconds remaining to the end of the feature
			        var _remainder = Math.ceil((this.cutOff - _now) / MILLISINMIN);
			        // stop it from going negative;
			        _remainder = (_remainder < 0) ? 0 : _remainder;
			        // clear the ground
			        TESCO.system.DOM.node.removeChildNodes(_remNode);
			        // insert the new text
			        _remNode.appendChild(TESCO.system.DOM.node.createText(_remainder.toString()));
			    }
			    _self.removeEventListener("render", arguments.callee);  //    only fire once
			}
		);
        _constructor.base.show.call(this);
    }
    
    _constructor.prototype.stop = function() {
		clearTimeout(this.timeout);
    }

    //  return _constructor as function pointer
    return _constructor;
})();
