/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Mozilla Communicator client code, released
 * March 31, 1998.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998-1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/*  This file contains the js functions necessary to implement view navigation within the 3 pane. */

//NOTE: gMessengerBundle must be defined and set or this Overlay won't work

function GetSubFoldersInFolderPaneOrder(folder)
{
  var subFolders = folder.subFolders;
  var msgFolders = Array();

  // get all the subfolders
  while (subFolders.hasMoreElements()) {
    msgFolders[msgFolders.length] =
      subFolders.getNext().QueryInterface(Components.interfaces.nsIMsgFolder);
  }

  function compareFolderSortKey(folder1, folder2) {
    return folder1.compareSortKeys(folder2);
  }

  // sort the subfolders
  msgFolders.sort(compareFolderSortKey);
  return msgFolders;
}

function FindNextChildFolder(aParent, aAfter)
{
  // Search the child folders of aParent for unread messages
  // but in the case that we are working up from the current folder
  // we need to skip up to and including the current folder
  // we skip the current folder in case a mail view is hiding unread messages
  if (aParent.getNumUnread(true) > 0) {
    var subFolders = GetSubFoldersInFolderPaneOrder(aParent);
    var i = 0;
    var folder = null;

    // Skip folders until after the specified child
    while (folder != aAfter)
      folder = subFolders[i++];

    const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
    let ignoreFlags = nsMsgFolderFlags.Trash | nsMsgFolderFlags.SentMail |
                      nsMsgFolderFlags.Drafts | nsMsgFolderFlags.Queue |
                      nsMsgFolderFlags.Templates | nsMsgFolderFlags.Junk;
    while (i < subFolders.length) {
      folder = subFolders[i++];
      // if there is unread mail in the trash, sent, drafts, unsent messages
      // templates or junk special folder, 
      // we ignore it when doing cross folder "next" navigation
      if (!folder.isSpecialFolder(ignoreFlags, true)) {
        if (folder.getNumUnread(false) > 0)
          return folder;

        folder = FindNextChildFolder(folder, null);
        if (folder)
          return folder;
      }
    }
  }

  return null;
}

function FindNextFolder()
{
  // look for the next folder, this will only look on the current account
  // and below us, in the folder pane
  // note use of gDBView restricts this function to message folders
  // otherwise you could go next unread from a server
  var folder = FindNextChildFolder(gDBView.msgFolder, null);
  if (folder)
    return folder;

  // didn't find folder in children
  // go up to the parent, and start at the folder after the current one
  // unless we are at a server, in which case bail out.
  for (folder = gDBView.msgFolder; !folder.isServer; ) {

    var parent = folder.parent;
    folder = FindNextChildFolder(parent, folder);
    if (folder)
      return folder;
 
    // none at this level after the current folder.  go up.
    folder = parent;
  }

  // nothing in the current account, start with the next account (below)
  // and try until we hit the bottom of the folder pane

  // start at the account after the current account
  var rootFolders = GetRootFoldersInFolderPaneOrder();
  for (var i = 0; i < rootFolders.length; i++) {
    if (rootFolders[i].URI == gDBView.msgFolder.server.serverURI)
      break;
  }
  
  for (var j = i + 1; j < rootFolders.length; j++) {
    folder = FindNextChildFolder(rootFolders[j], null);
    if (folder)
      return folder;
  }
  
  // if nothing from the current account down to the bottom
  // (of the folder pane), start again at the top.
  for (j = 0; j <= i; j++) {
    folder = FindNextChildFolder(rootFolders[j], null);
    if (folder)
      return folder;
  }
  return null;
}

function GetRootFoldersInFolderPaneOrder()
{
  var acctMgr = Components.classes["@mozilla.org/messenger/account-manager;1"].
                getService(Components.interfaces.nsIMsgAccountManager);
  var acctEnum = acctMgr.accounts;
  var count = acctEnum.Count();

  var accounts = new Array();
  for (var i = 0; i < count; i++) {
    var acct = acctEnum.GetElementAt(i)
                       .QueryInterface(Components.interfaces.nsIMsgAccount);

    // This is a HACK to work around bug 41133. If we have one of the
    // dummy "news" accounts there, that account won't have an
    // incomingServer attached to it, and everything will blow up.
    if (acct.incomingServer)
      accounts.push(acct);
  }

  /**
   * This is our actual function for sorting accounts.  Accounts go in the
   * following order: (1) default account (2) other mail accounts (3) Local
   * Folders (4) news
   */
  function accountCompare(a, b) {
    if (a.key == acctMgr.defaultAccount.key)
      return -1;
    if (b.key == acctMgr.defaultAccount.key)
      return 1;
    var aIsNews = a.incomingServer.type == "nntp";
    var bIsNews = b.incomingServer.type == "nntp";
    if (aIsNews && !bIsNews)
      return 1;
    if (bIsNews && !aIsNews)
      return -1;

    var aIsLocal = a.incomingServer.type == "none";
    var bIsLocal = b.incomingServer.type == "none";
    if (aIsLocal && !bIsLocal)
      return 1;
    if (bIsLocal && !aIsLocal)
      return -1;
    return 0;
  }

  // sort accounts, so they are in the same order as folder pane
  accounts.sort(accountCompare)

  var serversMsgFolders = new Array();
  for each (var acct in accounts)
    serversMsgFolders.push(acct.incomingServer.rootMsgFolder);

  return serversMsgFolders;
}

function CrossFolderNavigation(type)
{
  // do cross folder navigation for next unread message/thread and message history
  if (type != nsMsgNavigationType.nextUnreadMessage &&
      type != nsMsgNavigationType.nextUnreadThread &&
      type != nsMsgNavigationType.forward &&
      type != nsMsgNavigationType.back)
    return;

  if (type == nsMsgNavigationType.nextUnreadMessage ||
      type == nsMsgNavigationType.nextUnreadThread)
  {
    
    var nextMode = pref.getIntPref("mailnews.nav_crosses_folders");
    // 0: "next" goes to the next folder, without prompting
    // 1: "next" goes to the next folder, and prompts (the default)
    // 2: "next" does nothing when there are no unread messages

    // not crossing folders, don't find next
    if (nextMode == 2)
      return;

    var folder = FindNextFolder();
    if (folder && (gDBView.msgFolder.URI != folder.URI)) 
    {
      switch (nextMode) 
      {
        case 0:
          // do this unconditionally
          gNextMessageAfterLoad = type;
          SelectFolder(folder.URI);
          break;
        case 1:
        default:
          var promptText = gMessengerBundle.getFormattedString("advanceNextPrompt", [ folder.name ], 1); 
          if (Services.prompt.confirmEx(window, null, promptText, 
                                        Services.prompt.STD_YES_NO_BUTTONS, 
                                        null, null, null, null, {}) == 0)
          {
            gNextMessageAfterLoad = type;
            SelectFolder(folder.URI);
          }
          break;
      }
    }
  }
  else
  {
    // if no message is loaded, relPos should be 0, to
    // go back to the previously loaded message
    var relPos = (type == nsMsgNavigationType.forward)
      ? 1 : ((GetLoadedMessage()) ? -1 : 0);
    var folderUri = messenger.getFolderUriAtNavigatePos(relPos);
    var msgHdr = messenger.msgHdrFromURI(messenger.getMsgUriAtNavigatePos(relPos));
    gStartMsgKey = msgHdr.messageKey;
    var curPos = messenger.navigatePos;
    curPos += relPos;
    messenger.navigatePos = curPos;
    SelectFolder(folderUri);
  }
}


function ScrollToMessage(type, wrap, selectMessage)
{
  try {
    var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
    var treeSelection = treeView.selection;
    var currentIndex = treeSelection.currentIndex;

    var resultId = new Object;
    var resultIndex = new Object;
    var threadIndex = new Object;

    let elidedFlag = Components.interfaces.nsMsgMessageFlags.Elided;
    let summarizeSelection =
      gPrefBranch.getBoolPref("mail.operate_on_msgs_in_collapsed_threads");

    // if we're doing next unread, and a collapsed thread is selected, and
    // the top level message is unread, just set the result manually to
    // the top level message, without using gDBView.viewNavigate.
    if (summarizeSelection && type == nsMsgNavigationType.nextUnreadMessage &&
        currentIndex != -1 &&
        gDBView.getFlagsAt(currentIndex) & elidedFlag &&
        gDBView.isContainer(currentIndex) &&
        ! (gDBView.getFlagsAt(currentIndex) &
           Components.interfaces.nsMsgMessageFlags.Read)) {
      resultIndex.value = currentIndex;
      resultId.value = gDBView.getKeyAt(currentIndex);
    } else {
      gDBView.viewNavigate(type, resultId, resultIndex, threadIndex, true /* wrap */);
    }

    // only scroll and select if we found something
    if ((resultId.value != nsMsgViewIndex_None) && (resultIndex.value != nsMsgViewIndex_None)) {
      if (gDBView.getFlagsAt(resultIndex.value) & elidedFlag &&
          summarizeSelection)
        gDBView.toggleOpenState(resultIndex.value);

        if (selectMessage){
            treeSelection.select(resultIndex.value);
        }
        EnsureRowInThreadTreeIsVisible(resultIndex.value);
        return true;
    }
    else {
        return false;
    }
  }
  catch (ex) {
    return false;
  }
}

function GoNextMessage(type, startFromBeginning)
{
  if (!ScrollToMessage(type, startFromBeginning, true))
    CrossFolderNavigation(type);

  SetFocusThreadPaneIfNotOnMessagePane();
}

