//
// The bodyClickHandlerList contains a list of handlers
// to call when the body document is clicked on.
//
var bodyClickHandlerList = [];

var oldBackground = undefined;

// Keep track of the selected tab for the various tab groups.
var selectedTab = new Object();

// Whenever the user clicks on the document...
document.onclick = bodyOnClick;

// Global object to handle the AJAXy bits.
var XMLRequestInProgress;
var XMLRequestDelay = 2000;
var XMLRequestTimer = null;

/**
  Get the server-side context for the form.
  This allows AJAX submission to the right place.
*/
function getContext(formId)
{
  return getForm(formId).elements["@context"].value;
}


/**
  Set the value of the given property on the given form.
  If the value is null or undefined then the value is
  assumed to be the empty string "".
*/
function setField(formId, fieldName, value)
{
  if (value == null)
    value = "";

  var form = getForm(formId);

  var field = form.elements[fieldName];
  if (field != undefined)
    field.value = value;
}

function getField(formId, fieldName)
{
  var form = getForm(formId);

  var element = form.elements[fieldName];

  if (element == null)
    return undefined;

  return element.value;
}

/**
  Encrypt the special characters. Everything else is 
  left alone. We don't wanna mangle UTF8 stuff.
*/
function encode(s)
{
  return encodeURIComponent(s);
}

/**
  Generate a URL-encoded string encapsultaing the form data.
  This method returns NULL iff the form requires submission
  using the multipart-form-data method.
*/
function encodeFormData(formId)
{
  var encodedString = "";
  
  var form = getForm(formId);
  var elementCount = form.elements.length;
  var separator = "";
  for (var element = 0; element < elementCount; element++)
  {
    var formElement = form[element];
    if (formElement.disabled)
      continue;
    
    if (formElement.type == "file")
      return null;

    if ((formElement.type == "radio") && (formElement.checked == false))
      continue;
    
    var elementName = encode(formElement.name);
    var elementValue = encode(formElement.value);
    
    var formPair = elementName + "=" + elementValue;
    encodedString += separator + formPair;
    
    if (separator == "")
      separator = "&";
  }
  
  return encodedString;
}

/**
 * DoAction is a simple action button handler,
 * where the action selected by a user is embedded into the
 * form data, and then the form is submitted to the server
 *
 * If tableId is set then the table is searched for in the parent
 * form of the target IID, and the action is executed using that
 * table's definition.
 */
function doAction(formId, tableId, action, target, event, confirmed)
{
  // Something is already in progress?
  if (XMLRequestInProgress != null)
    return;
  
  if (confirmed == null)
    confirmed = false;
    
  setField(formId, "@action", action);
  setField(formId, "@target", target);
  setField(formId, "@table", tableId);
  setField(formId, "@confirm", confirmed);
    
  var form = getForm(formId);

  if ((getField(formId, "@ajax") != "true") && (getField(formId, "@confirm") == "false"))
  {
    form.submit();
    return;
  }
  
  var formData = encodeFormData(formId);
  if (formData == null)
  {
    // FIXME:
    // JS cannot get enough information about the file fields
    // to be able to build the data required for POST submission,
    // so unfortunately we need to actually submit the form in this
    // case. Note that we are unable to test for confirmation at
    // this time. This is a kinda serious problem but it can be solved
    // by getting QueryServer to return a form that tells the browser
    // to run the selected action after disabling all the file fields
    // to prevent their re-submission. Another alternative may be to
    // create the file input fields as <object>s in the form, and submit
    // them separately prior to uploading the remaining data!!
    setField(formId, "@confirm", true);
    setField(formId, "@ajax", false);
    form.submit();
    return;
  }

  // Comment the following to return to traditional action processing.
  // We add a tiemstamp to the request to defeat caching. This was a big
  // problem in Safari (at least).
  XMLRequestInProgress = BrowserGetXMLHttpRequest();
  XMLRequestInProgress.open("POST", getContext(formId) + "/tang/submit?@ts=" + new Date().valueOf(), true);

  XMLRequestInProgress.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
  formData = formData + "&@ajax=true";
  XMLRequestInProgress.onreadystatechange = handleActionResponse;

  // This needs to be done first or the server might respond
  // before we start the timer. This means that the timer is
  // stopped before it is started, which looks silly.
  startWaitTimer();

  XMLRequestInProgress.send(formData);
  

  BrowserCancelBubble(event, true);
}

//
// Display all the popup forms. These are emitted when there is
// an action which returns a popup form. All current popup forms are sent
// by the server; we don't expect popup forms to be used so much that it's
// worth dealing with this in a more complex way. Mostly this is needed to
// allow a popup list to display a confirmation dialog.
//
function doPopupList(popupListNode)
{
  // Remove all the popups in preparation for re-adding them.
  // If, in the future, we allow users to move windows
  // then we will need to remember the old window positions here, by iid.
  var popupBlock = document.getElementById("popupBlock");
  var knownPopup = {};

  if (popupBlock != null)
  {
    popupBlock.style.display = "none";
    while (popupBlock.firstChild)
    {
      var popupDiv = popupBlock.firstChild;
      knownPopup[popupDiv.instanceId] = { top: popupDiv.style.top, left: popupDiv.style.left };
      popupBlock.removeChild(popupDiv);
    }
  }
  else
  {
    popupBlock = document.createElement("div");
    popupBlock.id = "popupBlock";
    popupBlock.style.top = "0px";
    popupBlock.style.display = "block";
    popupBlock.style.position = "absolute";
    popupBlock.style.top = "0px";
    popupBlock.style.width = "100%";
    popupBlock.style.height = "100px";    
    document.body.appendChild(popupBlock);
  }

  // Manage the placement of the popups.
  var offsetTop = 0;
  var popupLeft = 80;
  
  var popupList = [];

  // The server sends popup messages from the top of the
  // breadcrumb pile (most recent) to the bottom, but we need
  // to display things from bottom to top in order to get a
  // repeatable ordering.
  var sibling = popupListNode.lastChild;
  while (sibling != null)
  {
    if (sibling.tagName == "popup")
      popupList.push(getPopup(popupBlock, sibling, offsetTop, popupLeft));
    
    sibling = sibling.previousSibling;
    
    offsetTop += 10;
    popupLeft += 10;
  }
  
  popupBlock.style.display = "block";
  
  var popupLength = popupList.length;
  var lastPopup = popupLength - 1;
  
  for (var popupIndex = 0; popupIndex < popupLength; popupIndex++)
  {
    var popupDiv = popupList[popupIndex];
    var popupSettings = knownPopup[popupDiv.instanceId];
    if (popupSettings != undefined)
    {
      popupDiv.style.top = popupSettings.top;
      popupDiv.style.left = popupSettings.left;
      popupDiv.style.display = "block";
      popupAddFrame(popupDiv);
    }
    else
      Effect.Appear(popupDiv, { duration: 0.15, delay: 0, afterFinish: popupAddEffectFrame });

    if (popupIndex == lastPopup)
      popupDiv.popupTitle.className = "popupTitleSelected";
    
    new Draggable(popupDiv);
  }
}

function popupAddEffectFrame(effectObject)
{
  popupAddFrame(effectObject.element);
}

function popupAddFrame(popupDiv)
{
  var popupHeight = popupDiv.popupContent.offsetHeight;
  var popupWidth = popupDiv.popupContent.offsetWidth;
  
  popupDiv.popupFrame.style.height = (popupHeight - 4) + "px";
  popupDiv.popupFrame.style.width = (popupWidth - 4) + "px";

  popupDiv.popupShadow.style.height = (popupHeight) + "px";
  popupDiv.popupShadow.style.width = (popupWidth) + "px";
}

function getPopup(popupBlock, popupElement, offsetTop, popupLeft)
{
  var popupTop = getVisibleTop() + offsetTop;

  // Import it into the current document and add it to
  // the message part of the built-in confirm DIV.
  // Remove any old messages while we're there.
  var popupTitleNode = BrowserGetFirstElement(popupElement);
  var popupFormNode = popupTitleNode.nextSibling;
  
  // Get to the first <form>
  while (popupFormNode.nodeType != 1)
    popupFormNode = popupFormNode.nextSibling;
    
  var popupInstanceId = popupElement.getAttribute("id");
  var popupForm = BrowserImportNode(document, popupFormNode);
  
  // Create the object which holds all the bits.
  var popupDiv = document.createElement("div");
  popupDiv.style.display = "none";
  popupDiv.style.position = "absolute";
  popupDiv.className = "popup";
  popupDiv.style.zIndex = 2;
  popupDiv.instanceId = popupInstanceId;
  
  var popupCover = createCoverDiv(null, "popupCover");
  popupCover.style.display = "block";
  popupCover.style.zIndex = 2;
  popupBlock.appendChild(popupCover);
  
  var popupShadow = document.createElement("div");
  popupShadow.className = "popupShadow";
  popupShadow.style.position = "absolute";
  popupShadow.style.top = "5px";
  popupShadow.style.left = "5px";
  popupShadow.style.width = "0px";               // Prevents nasty flashing in FireFox; reset later
  popupShadow.style.height = "0px";              // Prevents nasty flashing in FireFox; reset later
  popupDiv.appendChild(popupShadow);
  
  var popupFrame = document.createElement("iframe");    // Needed to block combo boxes in IE6.
  popupFrame.className = "popup";
  popupFrame.style.position = "absolute";
  popupFrame.style.top = "4px";
  popupFrame.style.left = "4px";
  popupFrame.style.width = "100%";               // Prevents nasty flashing in FireFox; reset later
  popupFrame.style.height = "100%";              // Prevents nasty flashing in FireFox; reset later
  popupFrame.src = "/tang/bug.gif";             // FIXME: Needs the context!!
  popupDiv.appendChild(popupFrame);
  
  var popupBody = document.createElement("div");
  popupBody.className = "popupBody";
  popupBody.appendChild(popupForm);
  
  var popupContent = document.createElement("div");
  popupContent.className = "popupContent";
  popupContent.style.position = "absolute";
  popupContent.style.top = "0px";
  popupContent.style.left = "0px";
  popupDiv.popupTitle = BrowserImportNode(document, popupTitleNode);
  popupContent.appendChild(popupDiv.popupTitle);
  popupContent.appendChild(popupBody);
  popupDiv.appendChild(popupContent);
  
  popupDiv.style.position = "absolute";
  popupDiv.style.top = popupTop + "px";
  popupDiv.style.left = popupLeft + "px";
  
  popupDiv.popupFrame = popupFrame;
  popupDiv.popupContent = popupContent;
  popupDiv.popupShadow = popupShadow;

  popupBlock.appendChild(popupDiv);

  return popupDiv;
}

/**
  Calculate a position which is within the viewable
  area of the screen.
*/
function getVisibleTop()
{
  var top;
  if (BrowserIsSafari())
    top = document.body.scrollTop + 80;
  else
    top = (document.getElementsByTagName("html")[0].scrollTop) + 80;
  return top;
}


function handleActionResponse() {
  var xmlRequest = XMLRequestInProgress;
  
  // only if req shows "loaded"
  if (xmlRequest.readyState != 4)
    return;
  
  // After this, the request is finished regardless
  // of what happened.
  stopWaitTimer();
  XMLRequestInProgress = null;
  
  if (xmlRequest.status != 200)
  {
    if (xmlRequest.statusText == null)
    {
      alert("The server has disconnected");
      return;
    }
      
    // alert("Error: Received invalid response: " + xmlRequest.statusText);
    return;
  }

  // This happens if the server is quit. Quite a pain when you're
  // working on the server, so we don't emit a message in this case.
  if (xmlRequest.responseText == null)
    return;
    
  var xml = xmlRequest.responseXML;
  if (xml == null)
  {
    alert("Got non-XML server response: " + xmlRequest.responseText);
    return;
  }
  
  var doc = xml.documentElement;

  if (doc.tagName == "redirect")
  {
    var url = doc.getAttribute("url");
    url = url.replace(/&#38;/g, "&");
    window.location.href = url;
    return;
  }

  if (doc.tagName == "error")
  {
    alert(doc.firstChild.nodeValue);
    return;
  }
  
  if (doc.tagName == "popupList")
  {
    // alert("got popups: " + xmlRequest.responseText);
    doPopupList(doc);
    return;
  }
  
  alert("Got unknown action response: " + xmlRequest.responseText);
  
  return;
}

function getWindowHeight()
{
  var windowHeight = document.body.offsetHeight;
  if (windowHeight < document.body.scrollHeight)
    windowHeight = document.body.scrollHeight;
  if ((self.innerHeight) && (self.innerHeight > windowHeight))
    windowHeight = self.innerHeight;
  else if (document.documentElement && document.documentElement &&
     (document.documentElement.clientHeight > windowHeight))
    windowHeight = document.documentElement.clientHeight;
  else if (document.body.clientHeight && (document.body.clientHeight > windowHeight))
    windowHeight = document.body.clientHeight;

  return windowHeight;
}

function startWaitTimer()
{
  stopWaitTimer();
  XMLRequestTimer = setTimeout("showWaitBox()", XMLRequestDelay);

  var coverDiv = createCoverDiv("coverDiv", "actionCover");
  coverDiv.style.zIndex = 100;
  document.body.appendChild(coverDiv);
}

/**
  Create a <div> section which covers the whole
  page. This allows us to create layers which stop
  input going to the page, for example during actions
  or when a popup is raised.
*/
function createCoverDiv(id, className)
{
  var coverDiv = document.createElement("div");
  if (id != null)
    coverDiv.id = id;
  coverDiv.className = className;
  coverDiv.style.position = "absolute";
  coverDiv.style.cursor = "wait";
  coverDiv.style.top = "0px";
  coverDiv.style.left = "0px";
  coverDiv.style.height = getWindowHeight() + "px";
  coverDiv.style.width = "100%";
  return coverDiv;
}

function stopWaitTimer()
{
  if (XMLRequestTimer != null)
  {
    clearTimeout(XMLRequestTimer);
    XMLRequestTimer = null;
  }
  
  hideWaitBox();
  
  var coverDiv = document.getElementById("coverDiv");
  if (coverDiv != null)
  {
    coverDiv.style.cursor = "pointer";
    coverDiv.parentNode.removeChild(coverDiv);
  }
}

function showWaitBox()
{
  var waitGraphic = document.createElement("img");
  waitGraphic.id = "waitGraphic";
  waitGraphic.className = "wait";
  waitGraphic.src = "/tang/wait.gif";
  waitGraphic.style.position = "absolute";
  waitGraphic.style.display = "none";
  waitGraphic.style.zIndex = 101;
  waitGraphic.style.left = "220px";
  waitGraphic.style.top = getVisibleTop() + 120 + "px";
  document.body.appendChild(waitGraphic);

  Effect.Appear(waitGraphic);
  
  var coverDiv = document.getElementById("coverDiv");
  coverDiv.style.backgroundColor = "black";
  coverDiv.style.opacity = 0.1;
  coverDiv.style.filter = "alpha(opacity=10)";

  coverDiv.style.backgroundColor = "black";
}

function hideWaitBox()
{
  var waitGraphic = document.getElementById("waitGraphic");
  if (waitGraphic != null)
    waitGraphic.parentNode.removeChild(waitGraphic);
}

/**
 * Call this function if you just want to refresh the page without
 * doing any action at all.
 */
function doReload(formId)
{
  doAction(formId, '', '', null);
}

/**
 * This script is run whenever a page is unloaded.
 * It sets the body background color back to it's default.
 * This is only called when the page is about to be replaced
 * by the new page.
 */
function doUnload(formId)
{
  if (oldBackground != undefined)
    document.body.style.color = oldBackground;
}


/**
  This clears the "action" field so that if the user clicks "back"
  and then submits the form with a submit button/pressing ENTER,
  then the previous action won't be triggered. Without this code,
  if the user presses "back", pressing enter/submit without selecting
  an action will cause the last action to be triggered.

  This also discovers the first active input box and sets
  the keyboard focus to it. You can override this behaviour
  by redefining the "doFirstFocus" method in your form's header.
*/
function doLoad(formId)
{
  doCorners();
  var form = getForm(formId);
  var defaultAction = form.elements["@default"];
  var actionField = form.elements["@action"];
  actionField.value = defaultAction.value;
  var sourceField = form.elements["@source"];
  sourceField.value = "";
  doFirstFocus(form);
}

/**
  Make the round-cornered DIV elements.
*/
function doCorners()
{
  var cornerSettings = {
    tl: { radius: 6 },
    tr: { radius: 6 },
    bl: { radius: 6 },
    br: { radius: 6 },
    antiAlias: true,
    autoPad: true
  };

//  var divObj = document.getElementById("main"); 

/*
  var cornersObj = new curvyCorners(cornerSettings, "main");
  cornersObj.applyCornersToAll();

  var cornersObj = new curvyCorners(cornerSettings, "menu");
  cornersObj.applyCornersToAll();
*/
}

function doFirstFocus(form)
{
  // Find the entry to focus on.
  for (var index = 0; index < form.elements.length; index++)
  {
    var element = form.elements[index];
    if (((element.type == "text") || (element.type == "textarea")) && (!element.disabled))
    {
      try {
        element.focus();
        break;
      }
      catch (e)
      {
        // This can't accept focus, so keep trying.
      }
    }
  }
}

/**
 * This function is called by forms that are created as part
 * of tables.
 */
function doSubmit(formId)
{
  var form = getForm(formId);
  form.submit();
}

function doDefaultAction(formId, event)
{
  var e = BrowserGetEvent(event);
  if (e.keyCode == 13)
  {
    var form = getForm(formId);
    var defaultAction = form.elements["@default"].value;
    doAction(formId, null, defaultAction, form.elements["@id"].value, event, false);
    return false;
  }
  
  // If there's a tip element, and if the tip hasn't
  // appeared, reset the counter. If the tip has appeared,
  // then pressing escape will hide it.
  if (e.keyCode == 27)
    TooltipCancel();
  else
    TooltipResetCounter();
  
  return true;
}

/**
 * HTML forms returns only the name of a checked checkbox. This
 * is not sufficient for our needs because it means we have no
 * way to know what has been changed to "false" (as opposed to
 * not being changed at all). So, we keep a hidden form field
 * which we update with the checked status of a checkbox whenever
 * the checkbox is clicked.
 */
function toggleCheckbox(checkboxId, valueId, refreshFormId)
{
  var element = document.getElementById(checkboxId);
  var value = document.getElementById(valueId);
  value.value = element.checked;

  if (refreshFormId != null)
    doRefresh(refreshFormId);
}

/**
  Refresh the form by submitting an empty action.
*/
function doRefresh(formId)
{
  if (oldBackground === undefined)
    oldBackground = document.body.style.color;
  document.body.style.color = "gray";
  doAction(formId, "", "", null);
}

function setDateRange(formId, range, from, to)
{
  var form = getForm(formId);
  var fromElement = document.getElementById(from);
  var toElement = document.getElementById(to);
  var rangeElement = document.getElementById(range);
  
  rangeElement.value = fromElement.value + " " + toElement.value;
}

function changeTablePage(formId, tableId, direction)
{
  var input = document.getElementById(tableId);
  input.value = Number(input.value) + direction;
  doReload(formId);
}

function setTablePage(formId, tableId, page)
{
  var input = document.getElementById(tableId);
  input.value = Number(page);
  doReload(formId);
}

//
// This is the function for the new u-beaut Java-based HTML
// there are much less elements in this version.
//
function selectGroup2(formId, groupId, tabId, refresh)
{
  // var input = document.getElementById(groupId);
  var input = getForm(formId).elements[groupId];
  if (input == null)
    return;             // this happens when a page is not fully loaded.

  input.value = tabId;

  if (refresh)
  {
    doReload(formId);
    return;
  }

  var oldTabId = selectedTab[groupId];

  if (oldTabId != undefined)
  {
    var body = document.getElementById(groupId + "Body" + oldTabId);
    var tab = document.getElementById(groupId + "Tab" + oldTabId);
    var tabLabel = document.getElementById(groupId + "TabLabel" + oldTabId);
    var tabLeft = document.getElementById(groupId + "TabLeft" + oldTabId);
    var tabRight = document.getElementById(groupId + "TabRight" + oldTabId); 

    if ((body == null) || (tab == null))
      alert("Unable to find old group tab/body, groupId=" + groupId + ", tabId=" + oldTabId);
    else
    {
      body.className = "tabBodyUnselected";
      tab.className = "tabUnselected";
      tabLabel.className = "tabUnselected";
      tabLeft.className = "tabUnselectedLeft";
      tabRight.className = "tabUnselectedRight";
    }
  }

  var body = document.getElementById(groupId + "Body" + tabId);
    if (body == null) { alert("Unknown body for tab;  ID=" + groupId + "Body" + tabId); return; }

  var tab = document.getElementById(groupId + "Tab" + tabId);
    if (tab == null) { alert("Unknown tab for tab;  ID=" + groupId + "Tab" + tabId); return; }

  var tabLabel = document.getElementById(groupId + "TabLabel" + tabId);
  var tabLeft = document.getElementById(groupId + "TabLeft" + tabId);
  var tabRight = document.getElementById(groupId + "TabRight" + tabId);

  body.className = "tabBodySelected";
  tab.className = "tabSelected";
  tabLabel.className = "tabSelected";
  tabLeft.className = "tabSelectedLeft";
  tabRight.className = "tabSelectedRight";

  selectedTab[groupId] = tabId;
  
  growArea("main", "menubar");
  
  BrowserLayoutChanged();
}

function setSelectedTab(groupId, tabId)
{
  selectedTab[groupId] = tabId;
}

function toggleVisibility(id)
{
  var element = document.getElementById(id);
  if (element.isVisible)
  {
    element.style.display = "none";
    element.isVisible = false;
  }
  else
  {
    element.style.display = "block";
    element.isVisible = true;
  }
}

function bodyOnClick(event)
{
  for (var index = 0; index < bodyClickHandlerList.length; index++)
    bodyClickHandlerList[index](event);
}

function addBodyHandler(handler)
{
  bodyClickHandlerList.push(handler);
}

function removeBodyHandler(handler)
{
  for (var index = 0; index < bodyClickHandlerList.length; index++)
  {
    if (bodyClickHandlerList[index] == handler)
    {
      bodyClickHandlerList.splice(index, 1);
      return;
    }
  }
}

function getForm(formId)
{
  return document.getElementById(formId);
}


//
// Magically make div1 the same height as div2, if it's smaller
// In practice this lets us make the form the same height as the menu,
// stretching the page if necessary and looking much nicer.
//
function growArea(div1id, div2id)
{
  var div1 = document.getElementById(div1id);
  var div2 = document.getElementById(div2id);

  // Don't make a fuss if they don't exist.
  if ((div1 == null) || (div2 == null))
  {
    alert("growArea called with bad div ID; div1=" + div1id + ", div2=" + div2id);
    return;
  }

  div1.style.height = "auto";
  
  var h1 = div1.scrollHeight;
  var h2 = div2.scrollHeight;

  if (h2 > h1)
    div1.style.height = div2.scrollHeight + "px";
}
