/* Copyright (c) 2011, Geert Bergman (geert@scrivo.nl)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of "Scrivo" nor the names of its contributors may be
 *    used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: Window.js 783 2013-08-09 10:17:59Z geert $
 */

"use strict";

SUI.Window = SUI.defineClass(
	/** @lends SUI.Window.prototype */{

	/** @ignore */ baseClass: SUI.AnchorLayout,

	/**
	 * @class
	 * SUI.Window is a component to create dialog boxes. Dialog boxes are
	 * container boxes that are displayed on top of the user interface. They
	 * can be either modal (disable the current interface) or modeless (still
	 * keeping the current user interface active). The windows are always
	 * movable and by default resizeable too.
	 *
	 * @augments SUI.AnchorLayout
	 *
	 * @description
	 * Create a window, use show to show it in modal mode or draw to display
	 * as it modeless window.
	 *
	 * @constructs
	 * @param see base class
	 * @param {boolean} arg.resizable Create a resizable window.
	 */
	initializer: function(arg) {

		// the window stack is a static list of currently open windows, if
		// it is not yet craeted, create it now.
		if (!SUI.Window.windowStack) {
			this._initWindowStack();
		}

		// set the default minimum size for a window
		arg.minWidth = arg.minWidth || 200;
		arg.minHeight = arg.minHeight || 100;

		SUI.Window.initializeBase(this, arg);

		// use visible overflow to allow the resizer element to become larger
		// than the windows
		this.el().style.overflow = "visible";

		// if the top position was not explicitly set center the window
		if (!arg.top) {
			this.center();
		}

		// create a resizable window (default true) ?
		this._resizable = arg.resizable ? true : false;

		// create the boxes for the window and add the event handlers
		this._buildControl(arg);
	},

	/**
	 * The inner border width.
	 */
	BORDER_WIDTH: 3,

	/**
	 * Right offset of the close icon.
	 */
	CLOSE_RIGHT: 2,

	/**
	 * Close icons size (width and height).
	 */
	CLOSE_SIZE: 20,

	/**
	 * Top offset of the close icon.
	 */
	CLOSE_TOP: 1,

	/**
	 * Size of the corner draggers.
	 */
	CORNER_SIZE: 16,

	/**
	 * Border width of border around the the inner window (without the
	 * caption bar).
	 */
	INNER_BORDER_OUTLINE: 1,

	/**
	 * Border width of border around the whole window.
	 */
	OUTER_BORDER_OUTLINE: 1,

	/**
	 * Height of the caption bar (excluding borders).
	 */
	CAPTION_HEIGHT: 23,

	/**
	 * Padding top of the caption text in the caption bar.
	 */
	CAPTION_PADDING_TOP: 3,

	/**
	 * Padding left of the caption text in the caption bar.
	 */
	CAPTION_PADDING_LEFT: 5,

	/**
	 * Add a box component to the window.
	 * @param {SUI.Box} child Child box to add
	 */
	add: function(child) {
		SUI.Window.parentMethod(this, "add", child, this.body);
	},

	/**
	 * Set or get the caption text.
	 * @param {String} text The new caption text (null to use the method as
	 *   getter)
	 * @return {String} The caption text (null if the method was used as
	 *   setter)
	 */
	caption: function(text) {
		if (text === undefined) {
			return this._caption.el().innerHTML;
		}
		this._caption.el().innerHTML = text;
		return null;
	},

	/**
	 * Set the top and left so that the window will be centered when it will
	 * be drawn.
	 */
	center: function() {

		// set the top and left so that the window will be centered
		// horizontally and vertically with slight offset to the top
		this.top(SUI.browser.viewportScrollY() +
			((SUI.browser.viewportHeight - this.height()) / 3 | 0));
		this.left(SUI.browser.viewportScrollX() +
			((SUI.browser.viewportWidth - this.width()) / 2 | 0));

		// correct top and/or left if the window top and/or left are outside
		// of the browser screen
		if (this.top() < 0) {
			this.top(0);
		}
		if (this.left() < 0) {
			this.left(0);
		}
	},

	/**
	 * Close a modal window that was created buy calling 'show'
	 */
	close: function() {

		// This is an ie cursor patch. The is the cursor is in and
		// contenteditable div on this window and this window is closed
		// by the close icon causes problems with placing the cursor in
		// other input fields. Selecting the current range when closing the
		// window seems to 'free' the cursor.
// Moved this to the onfocus of editable elements
//        if (SUI.browser.isIE) {
//            document.selection.createRange().select();
//        }

		// remove the overlay from the DOM tree ...
		document.body.removeChild(this._overlay);
		// ... and the window itself ...
		this.removeBox();
		// ... and pop it of the stack
		SUI.Window.windowStack.pop();

		// if there are still windows on the stack ...
		if (SUI.Window.windowStack.length) {
			// ... set the previously removed style back on the overlay
			var topI = SUI.Window.windowStack.length-1;
			SUI.style.addClass(SUI.Window.windowStack[topI]._overlay,
				"sui-overlay-disable");
		}

		// set the tab index back of all elements where it was removed
		for (var i=0; i<this._disabledElements.length; i++) {
			this._disabledElements[i].el.tabIndex =
				this._disabledElements[i].ti.tabIndex;
		}
		// and clear the list
		this._disabledElements = null;

	},

	/**
	 * Get the top, left, right and bottom offset of the client area
	 * relative to the outer dimensions of the window.
	 */
	clientAreaPosition: function() {
		var w = this._draggerBorderWidth();
		return { top: w+this.CAPTION_HEIGHT, left: w, right: w, bottom: w };
	},

	/**
	 * Display the window control. Set the CSS size and position of the
	 * window's boxes.
	 */
	display: function() {

		// set the CSS dimensions of outer box
		this.setDim();
		// set the CSS dimensions of the header
		this._caption.setDim();
		// set the CSS dimensions of the client area
		this._mainArea.setDim();
		this.body.setDim();

		// If the window is resizable ...
		if (this._resizable) {
			// ... set the CSS dimensions of the side draggers ...
			this._n.setDim();
			this._e.setDim();
			this._s.setDim();
			this._w.setDim();
			// ... and of the corner draggers
			this._nw.setDim();
			this._ne.setDim();
			this._se.setDim();
			this._sw.setDim();
		}

		// display all the child controls
		SUI.Window.parentMethod(this, "display");
	},

	/**
	 * Lay out the the window. Calculate the sizes and positions of all
	 * the window's elements.
	 */
	layOut: function() {

		// set the size and position of the caption bar and icon
		this._caption.setRect(0, 0, this.clientWidth(), this.CAPTION_HEIGHT);
		SUI.style.setRect(this._closeIcon, this.CLOSE_TOP,
			this.clientWidth() - this.CLOSE_SIZE - this.CLOSE_RIGHT,
			this.CLOSE_SIZE, this.CLOSE_SIZE);

		// set the size and position of the client area: note that 'body' is
		// no child of '_mainArea'
		this._mainArea.setRect(this.CAPTION_HEIGHT, 0, this.clientWidth(),
			this.clientHeight() - this.CAPTION_HEIGHT);
		this.body.setRect(
			   this._mainArea.top() + this._mainArea.border().top
				   + this._mainArea.padding().top,
			   this._mainArea.left() + this._mainArea.border().left
				   + this._mainArea.padding().left,
			this._mainArea.clientWidth(), this._mainArea.clientHeight());

		// If the window is resizable ...
		if (this._resizable) {

			var obo = this.OUTER_BORDER_OUTLINE;
			var bw = this._draggerBorderWidth();

			// ... set the size and positions of the side draggers ...
			this._n.setRect(-obo, -obo, this.width(), bw);
			this._e.setRect(-obo, this.width() - bw - obo, bw, this.height());
			this._s.setRect(this.height() - bw - obo, -obo, this.width(), bw);
			this._w.setRect(-obo, -obo, bw, this.height());

			// ... and corner draggers
			this._nw.setRect(-obo, -obo, this.CORNER_SIZE, this.CORNER_SIZE);
			this._ne.setRect(-obo, this.clientWidth() + obo - this.CORNER_SIZE,
				this.CORNER_SIZE, this.CORNER_SIZE);
			this._se.setRect(this.clientHeight() + obo - this.CORNER_SIZE,
				this.clientWidth() + obo - this.CORNER_SIZE, this.CORNER_SIZE,
				this.CORNER_SIZE);
			this._sw.setRect(this.clientHeight() + obo - this.CORNER_SIZE,
				-obo, this.CORNER_SIZE, this.CORNER_SIZE);
		}

		// lay out all child boxes
		SUI.Window.parentMethod(this, "layOut");
	},

	/**
	 * onClose event handler. This event handler is called when the
	 * user clicks on the 'close' button on the window's caption.
	 * @event
	 */
	onClose: function() {
	},

	/**
	 * Show a modal window. Before creating the window an overlay will be
	 * placed over the current window to disallow access to the other elements
	 * of the interface.
	 */
	show: function() {

		// check if the window is already shown
		if (this._disabledElements) {
			return;
		}

		// start a new list ...
		this._disabledElements = [];
		// .. get all the elements of the DOM tree ...
		var l = document.getElementsByTagName("*");
		// ... and loop through them ...
		for(var i; i<l.length; i++) {
			// ... if the tab index was set ...
			if (l[i].tabIndex !== undefined && l[i].tabIndex != -1) {
				// ... store it
				this._disabledElements.push({ti: l[i].tabIndex, el: l[i]});
				l[i].tabIndex = -1;
			}
		}

		// if there is an other overlay active ...
		if (SUI.Window.windowStack.length) {
			// ... remove the class so it not visible any more
			var topI = SUI.Window.windowStack.length-1;
			SUI.style.removeClass(SUI.Window.windowStack[topI]._overlay,
				"sui-overlay-disable");
		}

		// create a new overlay for this window
		this._overlay = SUI.browser.createElement();
		SUI.style.setRect(this._overlay,
			0, 0, SUI.browser.viewportWidth, SUI.browser.viewportHeight);
		if (true) {
		SUI.style.addClass(this._overlay, "sui-overlay-disable");
		} else {
			// for taking screenshots:
			this._overlay.style.backgroundColor = "white";
			this.el().style.webkitBoxShadow = "none";
			this.el().style.boxShadow = "none";
		}

		this._overlay.style.position = "fixed";

		// append the overlay to the document body
		document.body.appendChild(this._overlay);

		// add the window to the window stack ...
		SUI.Window.windowStack.push(this);
		// ... and append it to the document tree
		this.parent({el: function() { return document.body; }});

		// now draw the window
		this.draw();
	},

	// storage for disabled elements under the window (we need to enable them
	// later on)
	_disabledElements: null,

	// the overlay window that obscures the rest of the UI when the window
	// is shown
	_overlay: null,

	// if it is a resizable window or not
	_resizable: true,

	/* Add _startDragBorder event handler on the onmousedown event of the
	 * element.
	 */
	_addResizeHandler: function(element, dir) {
		var that = this;
		// 'that' and 'dir' are two closure variables
		SUI.browser.addEventListener(element, "mousedown",
			function(e) {
				if (!that._startDragBorder(new SUI.Event(this, e), dir)) {
					SUI.browser.noPropagation(e);
				}
			}
		);
	},

	/* And the event handlers for the close button and to move the window.
	 */
	_addWindowEvents: function() {

		var that = this;

		// add the 'move window' event handler on the onmousedown event of
		// the caption bar
		SUI.browser.addEventListener(this._caption.el(), "mousedown",
			function(e) {
				if (!that._startDragWindow(new SUI.Event(this, e))) {
					SUI.browser.noPropagation(e);
				}
			}
		);

		// close the window on the click event of the close icon
		SUI.browser.addEventListener(this._closeIcon, "click",
			function(e) {
				// call the onclose listener before closing the form
				that.callListener("onClose");
				if (!that.close()) {
					SUI.browser.noPropagation(e);
				}
			}
		);

	},

	/* Make all required boxes for the control and set the event handlers.
	 */
	_buildControl: function(arg) {

		// set the window's main style
		this.addClass("sui-window-border");
		this.border(new SUI.Border(this.OUTER_BORDER_OUTLINE));

		// create the main area for the window
		this._mainArea = new SUI.Box({parent: this});
		this._mainArea.addClass("sui-window");
		this._mainArea.border(
			new SUI.Border(this.INNER_BORDER_OUTLINE));
		this._mainArea.padding(new SUI.Padding(this.BORDER_WIDTH));

		// create the caption bar
		this._caption = new SUI.Box({parent: this});
		this._caption.addClass("sui-window-caption sui-window-border");
		this._caption.el().innerHTML = arg.caption || SUI.i18n.captionWindow;
		this._caption.padding(new SUI.Padding(this.CAPTION_PADDING_TOP, 0, 0,
		 this.CAPTION_PADDING_LEFT));
		this._caption.el().style.cursor = "move";

		// if the window is resizable create the resize handlers
		if (this._resizable) {

			// create the the boxes for the side handles
			this._n = new SUI.Box({parent: this});
			this._e = new SUI.Box({parent: this});
			this._s = new SUI.Box({parent: this});
			this._w = new SUI.Box({parent: this});

			// create the the boxes for the corner handles
			this._nw = new SUI.Box({parent: this});
			this._ne = new SUI.Box({parent: this});
			this._se = new SUI.Box({parent: this});
			this._sw = new SUI.Box({parent: this});

			// set the cursors and resize event handlers for the handles
			var arr = [this._n, this._e, this._s, this._w, this._nw,
				this._ne, this._se, this._sw];
			var curs = ["n", "e", "s", "w", "nw", "ne", "se", "sw"];
			for (var i=0; i<arr.length; i++) {
				arr[i].el().style.cursor = curs[i] + "-resize";
				this._addResizeHandler(arr[i].el(), curs[i]);
			}
		}

		// create the close icon
		this._closeIcon = document.createElement("INPUT");
		this._closeIcon.type = "image";
		this._closeIcon.src = SUI.imgDir+"/"+SUI.resource.wnClose;
		this._closeIcon.style.position = "absolute";
		this.el().appendChild(this._closeIcon);

		// create the client area of the window (don't add it to _mainArea
		// because it has to lay over the corner draggers
		this.body = new SUI.Box({parent: this});
		this.body.addClass("body sui-window-body");
		if (arg.padding) {
			this.body.padding(arg.padding);
		}

		// add the event handlers
		this._addWindowEvents();
	},

	/* The draggable border width equals the width of all the borders.
	 */
	_draggerBorderWidth: function() {
		return this.OUTER_BORDER_OUTLINE + this.INNER_BORDER_OUTLINE +
			this.BORDER_WIDTH;
	},

	/* End the dragging motion, set the window's size and/or position to
	 * the ones of the dragger.
	 */
	_endDrag: function(dragger) {

		// set the window's size and/or position
		this.setRect(this.top() + dragger.top(), this.left() + dragger.left(),
			dragger.width(), dragger.height());

		// remove the dragger from the document tree
		dragger.removeBox();

		// redraw the window
		this.draw();
	},

	/* Initalize this static window stack.
	 */
	_initWindowStack: function() {

		// if there is no static windowStack, create it. The windowStack is
		// system global list of displayed modal windows.
		if (!SUI.Window.windowStack) {

			SUI.Window.windowStack = [];

			// add an event handler to the window that resizes all overlays
			// on the window resize event
			SUI.browser.addEventListener(window, "resize",
				function(event){
					for (var i=0; i<SUI.Window.windowStack.length; i++) {
						SUI.style.setRect(SUI.Window.windowStack[i]._overlay,
							0, 0, SUI.browser.viewportWidth-0,
							SUI.browser.viewportHeight-0);
					}
					SUI.browser.noPropagation(event);
				}
			);

			// add an event handler to the window that catches some keycodes
			// to close the window
			// TODO
			SUI.browser.addEventListener(window.document, "keydown",
				function(event) {
					var e = new SUI.Event(this, event);
					if (SUI.Window.windowStack.length) {
						var topI = SUI.Window.windowStack.length-1;
						var win = SUI.Window.windowStack[topI];
						// handle the esc key
						if (e.event.keyCode == 27 ) {
							// call the onclose listener before closing form
							win.callListener("onClose");
							win.close();
						}
						// handle the enter key
						//if (e.event.keyCode == 13 ) {
							//win.handleEnter(new SUI.Event(this, e));
						//}
						SUI.browser.noPropagation(event);
					}
				}
			);

		}

	},

	/* Start dragging of one of the borders of the window.
	 */
	_startDragBorder: function(event, dir) {

		// create a dragger
		var dragger = new SUI.Dragger({
			parent: this,
			width: this.width(),
			height: this.height(),
			border: new SUI.Border(this._draggerBorderWidth())
		});

		// set the style of the dragger
		dragger.addClass("sui-window-dragger");
		dragger.el().style.cursor = event.elListener.style.cursor;

		// set the direction of the dragger
		dragger.direction(dir);

		// get the boundaries and the dragger
		var xMax, yMax;
		if (dir.indexOf("w") != -1 || dir.indexOf("n") != -1) {
			xMax = this.left() + this.width();
			yMax = this.top() + this.height();
		} else {
			xMax = SUI.browser.viewportWidth - this.left() - 1;
			yMax = SUI.browser.viewportHeight - this.top() - 1;
		}

		if (xMax > this.maxWidth()) {
			xMax = this.maxWidth();
		}
		if (yMax > this.maxHeight()) {
			yMax = this.maxHeight();
		}

		// set the boundaries and direction of the dragger
		dragger.xMin(this.minWidth());
		dragger.yMin(this.minHeight());
		dragger.xMax(xMax);
		dragger.yMax(yMax);

		// set CSS dimensions of the dragger
		dragger.setDim();

		var that = this;
		// 'this' and 'dragger' are closure variables
		dragger.addListener("onEndDrag",
			function() {
				that._endDrag(dragger);
			}
		);

		// and start dragging
		dragger.start(event, this);
	},

	/* Start dragging the window.
	 */
	_startDragWindow: function(event) {

		// create a dragger ...
		var dragger = new SUI.Dragger({
			parent: this,
			width: this.width(),
			height: this.height(),
			border: new SUI.Border(this._draggerBorderWidth())
		});

		// ... set the style ...
		dragger.addClass("sui-window-dragger");
		dragger.el().style.cursor = this._caption.el().style.cursor;
		// ... and direction
		dragger.direction(dragger.HORIZONTAL + dragger.VERTICAL);

		// set the dragging boundaries
		dragger.xMin(-this.left() - this.OUTER_BORDER_OUTLINE);
		dragger.xMax(SUI.browser.viewportWidth - this.width() - this.left()
			- this.OUTER_BORDER_OUTLINE);
		dragger.yMin(-this.top() - this.OUTER_BORDER_OUTLINE);
		dragger.yMax(SUI.browser.viewportHeight - this.height() - this.top()
			- this.OUTER_BORDER_OUTLINE);

		// set CSS dimensions of the dragger
		dragger.setDim();

		var that = this;
		// 'this' and 'dragger' are closure variables
		dragger.addListener("onEndDrag",
			function() {
				that._endDrag(dragger);
			}
		);

		// and start dragging
		dragger.start(event, this);
	}

});