q = {

  getBaseWindow : function() {
    parentWindow = window;
    while (parentWindow.parent && parentWindow.parent != parentWindow) parentWindow = parentWindow.parent;
    return parentWindow;
  },

  getEventTarget : function(e) {
	  e = e || window.event;
	  return e.target || e.srcElement;
	},

  Popup : function(id) {
    var _id = id;
    var _isInitialised = false;
    var _width = 400;
    var _height = 300;
    var _popupPlaceholder;
    var _showTitleBar = true;
    var _title = '';
    var _showCloseButton = true;
    var _titleField;
    var _content;
    
    // TODO: find a better way to get reference to self instead of popupReference parameter (perhaps bind()?)
    var _initialise = function(popupReference) {
      if (_isInitialised) return;
      var table = '<table id="popupPlaceholder' + _id + '" class="popupPlaceholder" cellpadding="0" cellspacing="0" style="z-index: ' + (1000 + (_id * 2)) + '; display: none">';
      if (_showTitleBar) table += _createTitleBar(popupReference);
      table += '<tbody><tr><td colspan="2" class="popupContent"></td></tr></tbody></table>';
      $$('body')[0].insert(table);
      _popupPlaceholder = $('popupPlaceholder' + _id);
      _content = $('popupPlaceholder' + _id).select('.popupContent').first();
      $('popupCloseButton' + _id).onclick = function() {
        setTimeout(popupReference.close.bind(popupReference), 0);
      };
      _isInitialised = true;
    }
    
    var _createTitleBar = function(popupReference) {
      return '<thead><tr class="popupTitleBar"><th class="popupTitle">' +
          '<strong>' + _title + '</strong></th><th class="popupCloseButton">' +
          '<img id="popupCloseButton' + _id + '" src="/q/images/close.png"/></th></tr></thead>';
    }

    var _createIframeWithUrl = function(url) {
      var iframe = document.createElement('iframe');
      iframe.id = 'popupIframe' + _id;
      iframe.setAttribute('class', 'popupIframe');
      iframe.style.width = '100%';
      iframe.style.height = '100%';
      iframe.style.border = 'none';
      iframe.style.display = 'none';
      iframe.src = url;
      return iframe;
    }
    
    var _sizeAndPositionWindow = function() {
      _popupPlaceholder.style.width = _width + 'px';
      _popupPlaceholder.style.height = _height + 'px';
      var windowWidth = (typeof(window.innerWidth) == 'number') ? window.innerWidth : document.documentElement.clientWidth;
      var windowHeight = (typeof(window.innerHeight) == 'number') ? window.innerHeight : document.documentElement.clientHeight;
      _popupPlaceholder.style.left = (windowWidth - _width) / 2 + 'px';
      _popupPlaceholder.style.top = (windowHeight - _height) / 2 + 'px';
      
      // Fix for IE6
     	if (/msie|MSIE 6/.test(navigator.userAgent)) {
        _popupPlaceholder.style.height = windowHeight - 200 + 'px';
        _popupPlaceholder.style.top = 100 + 'px';
      }
    }
    
    return {
      setDimensions : function(width, height) {
        _width = (width == null) ? window.innerWidth - 120 : width;
        _height = (height == null) ? window.innerHeight - 120 : height;
        if (this.isOpen()) _sizeAndPositionWindow();
        return this;
      },
      
      setTitle : function(title) {
        _title = urldecode(title);
        return this;
      },
      
      showTitleBar : function(showTitleBar) {
        _showTitleBar = showTitleBar;
        return this;
      },
      
      showCloseButton : function(showCloseButton) {
        _showCloseButton = showCloseButton;
        return this;
      },
      
      displayUrl : function(url) {
        return this.displayElement(_createIframeWithUrl(url));
      },
      
      displayJson : function(url, parameters, evaluateScripts) {
        if (evaluateScripts == undefined) evaluateScripts = true;
        var ajax = new q.AjaxRequest();
        ajax.setParameters(parameters).evaluateScripts(false).setErrorMessage(true);
        ajax.onSuccess(function(response) {
          this.displayElement(response);
          if (evaluateScripts) response.evalScripts();
        }.bind(this));
        ajax.onError(function(response) {
          q.Popup.closeAll();
        });
        ajax.send(url);
      },
      
      displayElement : function(element) {
        _initialise(this);
        _popupPlaceholder.elementParent = element.parentNode;
        _popupPlaceholder.element = element;
        _content.update('');
        _sizeAndPositionWindow();
        if (typeof element == 'string') _content.update(element);
        else {
          _content.appendChild(element);
          element.style.display = 'block';
        }
        _popupPlaceholder.style.display = '';
        q.Popup.popups.push(this);
        q.Popup.showMask();
      },
      
      close : function() {
        if (_popupPlaceholder) {
		      if (typeof _popupPlaceholder.element != 'string') _popupPlaceholder.element.style.display = 'none';
				  if (_popupPlaceholder.elementParent) _popupPlaceholder.elementParent.appendChild(_popupPlaceholder.element);
				  _popupPlaceholder.parentNode.removeChild(_popupPlaceholder);
				  _isInitialised = false;
				}
			  q.Popup.close(this);
			},
			
			isVisible : function() {
        var popupIndex = parseInt(q.Popup.mask.style.zIndex) || 0;
			  return (_isInitialised && (_popupPlaceholder.style.zIndex > popupIndex));
			},
			
			isOpen : function() {
         return _isInitialised;
			},
			
			id : function() {
        return _id;
			}
    }
  },

  addMessage : function(message, type, defaultToAlert) {
    if ($('messages')) {
	    var messageRow = '<li id="newMessage" class="';
	    if (type == 'ERROR') messageRow += 'errorMessage';
	    else if (type == 'WARNING') messageRow += 'warningMessage';
	    else messageRow += 'successMessage';
	    messageRow += '" style="display: none"><div>' + message + '</div></li>';
	    $('messages').insert(messageRow);
	    messageRow = $('newMessage');
	    Effect.ScrollTo('top', {duration: 0.2});
	    Effect.SlideDown(messageRow, {duration: 0.3});
	    setTimeout(function() {new Effect.SlideUp(messageRow, {duration: 0.3})}, 3500);
	    setTimeout(function() {$('messages').removeChild(messageRow)}, 4800);
	  }
	  else if (defaultToAlert) alert(message);
  },
  
  login : function() {
    var showPasswordField = function(passwordField) {
      passwordField.type = 'password';
      passwordField.value = '';
    }
    
    var hidePasswordField = function(passwordField) {
      if (passwordField.value == '') {
        passwordField.type = 'text';
        passwordField.value = 'password';
      }
    }
    
    var doLogin = function() {
      var ajax = new q.AjaxRequest();
      var password = ($('password').type == 'text') ? '' : $F('password');
      ajax.setParameters({'signin[username]': $F('username'), 'signin[password]': password});
      ajax.onSuccess(function(response) {
        ajax.executeLoginInterruptedAjax();
        $('username').value = $('password').value = '';
        $('username').onblur();
        hidePasswordField($('password'));
      });
      ajax.onError(function(response) {
        Effect.SlideDown($('loginErrorBar'), {duration: 0.3});
        setTimeout(function() {new Effect.SlideUp($('loginErrorBar'), {duration: 0.3})}, 3500);
      });
      ajax.send(loginRoute);
    }
      
	  var createLoginPopup = function() {
	    $$('body')[0].insert('<div id="login">' +
	        '<div class="titleBar">You must be logged in to perform this task</div>' +
	        '<div id="loginErrorBar"><div>The username and/or password is invalid</div></div>' +
	        '<div class="loginFields">' +
	        '<input type="text" value="username" id="username" onfocus="if (this.value == \'username\') this.value = \'\'" onblur="if (this.value == \'\') this.value = \'username\'"/>' +
	        '<input type="text" id="password" value="password"/>' +
	        '<div><input class="loginButton" type="button" value="Log-in"/>' +
	        '<a class="nav_link" href="/" title="I forgot my password">I forgot my password</a></div></div></div>');
	    $('password').onfocus = function(){showPasswordField(this)};
	    $('password').onblur = function(){hidePasswordField(this)};
	    $$('#login div.loginFields input.loginButton').first().onclick= doLogin;
	  }
	  
	  return {
	    getPopup : function() {
	      if (!$('login')) createLoginPopup();
	      return $('login');
	    }
	  }
	}(),

  AjaxRequest : function() {
    var _asynchronous = false;
    var _method = 'post';
    var _onSuccess = null;
    var _successMessage = null;
    var _onError = null;
    var _errorMessage = null;
    var _warningMessage = null;
    var _onComplete = null;
    var _updateElement = null;
    var _evaluateScripts = true;
    var _onFailure = function(response) {
      q.addMessage('A critical error has occurred. Please contact support.', 'ERROR');
    };
    var _onException = function(request, exception) {
      q.addMessage(exception.message, 'ERROR');
    };
    var _parameters = {};
    
    return {
      onSuccess : function(onSuccess) {
        _onSuccess = onSuccess;
        return this;
      },
      
      onError : function(onError) {
        _onError = onError;
        return this;
      },
      
      onComplete : function(onComplete) {
        _onComplete = onComplete;
        return this;
      },
      
      onFailure : function(onFailure) {
        _onFailure = onFailure;
        return this;
      },
      
      onException : function(onException) {
        _onException = onException;
        return this;
      },
      
      setParameters : function(parameters) {
        _parameters = parameters;
        return this;
      },
      
      setSuccessMessage : function(successMessage) {
        _successMessage = successMessage;
        return this;      
      },
      
      setErrorMessage : function(errorMessage) {
        _errorMessage = errorMessage;
        return this;
      },
      
      setWarningMessage : function(warningMessage) {
        _warningMessage  = warningMessage;
        return this;
      },
      
      // NOTE: if updating an element eval scripts is set to true due to prototype's update() call
      updateElement : function(updateElement) {
        _updateElement = updateElement;
        return this;
      },
      
      evaluateScripts : function(evaluateScripts) {
        _evaluateScripts = evaluateScripts;
        return this;
      },
      
      setMethod : function(method) {
        _method = method;
        return this;
      },
      
      send : function(url) {
        options = {
          asynchronous : _asynchronous,
          method : _method,
          parameters : _parameters,
          evalJS : (_updateElement) ? false : _evaluateScripts, // if updating elements eval scripts after element is updated
          onSuccess : function(transport) {
            var success = function() {
	            if (transport.responseJSON) {
		            if (transport.responseJSON.result == 'SUCCESS' || transport.responseJSON.result == 'WARNING') {
		              if (_successMessage != null && transport.responseJSON.result == 'SUCCESS') q.addMessage(_successMessage);
		              else if (transport.responseJSON.result == 'WARNING' && _warningMessage === true) q.addMessage(transport.responseJSON.contents, 'WARNING');
		              if (_updateElement) _updateElement.update(transport.responseJSON.contents);
		              if (_onSuccess) _onSuccess(transport.responseJSON.contents);
		            }
		            else if (transport.responseJSON.result == 'ERROR') {
		              if (_errorMessage === true) q.addMessage(transport.responseJSON.contents, 'ERROR');
		              else if (_errorMessage != null) q.addMessage(_errorMessage, 'ERROR');
		              if (_onError) _onError(transport.responseJSON.contents);
		            }
		            if (_onComplete) _onComplete(transport.responseJSON.contents);
		          }
	            else {
	              if (_updateElement) _updateElement.update(transport.responseText);
	              if (_onComplete) _onComplete(transport);
	            }
	          }
	          // ensure success function gets run outside the context of the ajax request on the outermost parent
	          q.getBaseWindow().setTimeout(success, 0);
          }
        }
        options.on401 = function(transport) {
          var baseWindow = q.getBaseWindow();
          baseWindow.setTimeout(function() {
            baseWindow.loginInterruptedAjax = {url: url, options: options, loginPopup: q.Popup.create()};
	          baseWindow.loginInterruptedAjax.loginPopup.setDimensions(330, 160).displayElement(baseWindow.q.login.getPopup());
	          baseWindow.document.getElementById('loginErrorBar').style.display = 'none';
          }, 0);
        }
        options.on403 = function(transport) {
          q.addMessage("You don't have the required permission to access this page", 'ERROR');
        }
        return new Ajax.Request(url, options);
      },
      
      executeLoginInterruptedAjax : function() {
        var baseWindow = q.getBaseWindow();
        baseWindow.loginInterruptedAjax.loginPopup.close();
        return new Ajax.Request(baseWindow.loginInterruptedAjax.url, baseWindow.loginInterruptedAjax.options);
      }
    }
  }
}

q.AjaxRequest.create = function() {
  return new q.AjaxRequest();
}

q.Popup.mask = null;
q.Popup.showMask = function() {
  if (this.mask == null) {
    var pageSize = this.getPageSize();
    $$('body')[0].insert('<div id="popupMask" style="width: ' + pageSize[0] + 'px; height: ' + pageSize[1] + 'px"></div>');
    this.mask = $('popupMask');
    new Effect.Appear(this.mask, {duration: 0.5, from: 0.0, to: 0.8});
  } 
  q.Popup.moveMaskBehindLastPopup();
}

q.Popup.moveMaskBehindLastPopup = function() {
  $('popupMask').style.zIndex = 999 + (this.popups.last().id() * 2);
}

q.Popup.removeMask = function() {
  if (this.mask !== null) {
	  new Effect.Appear(this.mask, {duration: 0.5, from: 0.8, to: 0.0});
	  setTimeout(function() {
	    document.body.removeChild(this.mask);
	    this.mask = null;
	  }.bind(this), 500);
	}
}

q.Popup.getPageSize = function() {    
  var xScroll, yScroll;
  if (window.innerHeight && window.scrollMaxY) {  
    xScroll = window.innerWidth + window.scrollMaxX;
    yScroll = window.innerHeight + window.scrollMaxY;
  }
  else if (document.body.scrollHeight > document.body.offsetHeight) {
    xScroll = document.body.scrollWidth;
    yScroll = document.body.scrollHeight;
  }
  else {
    xScroll = document.body.offsetWidth;
    yScroll = document.body.offsetHeight;
  }
  var windowWidth, windowHeight;
  if (self.innerHeight) {
    if(document.documentElement.clientWidth) windowWidth = document.documentElement.clientWidth; 
    else windowWidth = self.innerWidth;
    windowHeight = self.innerHeight;
  }
  else if (document.documentElement && document.documentElement.clientHeight) {
    windowWidth = document.documentElement.clientWidth;
    windowHeight = document.documentElement.clientHeight;
  }
  else if (document.body) {
    windowWidth = document.body.clientWidth;
    windowHeight = document.body.clientHeight;
  } 
  return [(xScroll < windowWidth) ? xScroll : windowWidth, (yScroll < windowHeight) ? windowHeight : yScroll];
}

q.Popup.popupsCreated = 0;
q.Popup.popups = [];

q.Popup.create = function() {
  return new q.getBaseWindow().q.Popup(q.getBaseWindow().q.Popup.popupsCreated++);
}

q.Popup.close = function(popup) {
  this.popups = this.popups.without(popup);
  if (this.hasOpenPopup()) {
    this.moveMaskBehindLastPopup();
  }
  else this.removeMask();
}

q.Popup.hasOpenPopup = function() {
  return (this.popups.length > 0);
}

q.Popup.closeAll = function() {
  if (this.hasOpenPopup()) {
	  this.popups.each(function(popup){popup.close()});
	  this.popups = [];
	  this.removeMask();
  }
}

q.Popup.activePopup = function() {
  if (this.hasOpenPopup()) return this.popups.last();
  return false;
}


function urldecode(str) {
  return unescape(str.replace(/\+/g, ' '));
}
