/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/****************** Get publishing data methods *******************/

// Build an array of all publish site data obtained from prefs
function GetPublishSiteData()
{
  var publishBranch = GetPublishPrefsBranch();
  if (!publishBranch)
    return null;

  // Array of site names - sorted, but don't put default name first
  var siteNameList = GetSiteNameList(true, false);
  if (!siteNameList)
    return null;

  // Array of all site data
  var siteArray = [];

  // We  rewrite siteName prefs to eliminate names if data is bad
  //  and to be sure order is the same as sorted name list
  try {
    publishBranch.deleteBranch("site_name.");
  } catch (e) {}

  // Get publish data using siteName as the key
  var index = 0;
  for (var i = 0; i < siteNameList.length; i++)
  {
    // Associated data uses site name as key
    var publishData = GetPublishData_internal(publishBranch, siteNameList[i]);
    if (publishData)
    {
      siteArray[index] = publishData;
      SetPublishStringPref(publishBranch, "site_name."+index, siteNameList[i]);
      index++;
    }
    else
    {
      try {
        // Remove bad site prefs now
        publishBranch.deleteBranch("site_data." + siteNameList[i] + ".");
      } catch (e) {}
    }
  }

  SavePrefFile();

  if (index == 0) // No Valid pref records found!
    return null;


  return siteArray;
}

function GetDefaultPublishSiteName()
{
  var publishBranch = GetPublishPrefsBranch();
  var name = "";  
  if (publishBranch)
    name = GetPublishStringPref(publishBranch, "default_site");

  return name;
}

// Return object with all info needed to publish
//   from database of sites previously published to.
function CreatePublishDataFromUrl(docUrl)
{
  if (!docUrl || IsUrlAboutBlank(docUrl) || GetScheme(docUrl) == "file")
    return null;

  var pubSiteData = GetPublishSiteData();
  if (pubSiteData)
  {
    var dirObj = {};
    var index = FindSiteIndexAndDocDir(pubSiteData, docUrl, dirObj);
    var publishData;
    if (index != -1)
    {
      publishData = pubSiteData[index];
      publishData.docDir = FormatDirForPublishing(dirObj.value)

      //XXX Problem: OtherDir: How do we decide when to use the dir in 
      //    publishSiteData (default DocDir) or docDir from current filepath?
      publishData.otherDir = FormatDirForPublishing(pubSiteData[index].otherDir);

      publishData.filename = GetFilename(docUrl);
      publishData.notInSiteData = false;
      return publishData;
    }
  }

  // Document wasn't found in publish site database
  // Create data just from URL

  // Extract username and password from docUrl
  var userObj = {};
  var passObj = {};
  var pubUrl = StripUsernamePassword(docUrl, userObj, passObj);

  // Strip off filename
  var lastSlash = pubUrl.lastIndexOf("\/");
  //XXX Look for "?", "=", and "&" ?
  pubUrl = pubUrl.slice(0, lastSlash+1);

  var siteName = CreateSiteNameFromUrl(pubUrl, pubSiteData);

  publishData = { 
    siteName : siteName,
    previousSiteName : siteName,
    filename : GetFilename(docUrl),
    username : userObj.value,
    password : passObj.value,
    savePassword : false,
    publishUrl : pubUrl,
    browseUrl  : pubUrl,
    docDir     : "",
    otherDir   : "",
    publishOtherFiles : true,
    dirList    : [""],
    saveDirs   : false,
    notInSiteData : true
  }

  return publishData;
}

function CreateSiteNameFromUrl(url, publishSiteData)
{
  var host = GetHost(url);
  var schemePostfix = " (" + GetScheme(url) + ")";
  var siteName = host + schemePostfix;

  if (publishSiteData)
  {
    // Look for duplicates. Append "-1"  etc until unique name found
    var i = 1;
    var exists = false;
    do {
      exists = PublishSiteNameExists(siteName, publishSiteData, -1)  
      if (exists)
        siteName = host + "-" + i + schemePostfix;
      i++;
    }
    while (exists);
  }
  return siteName;
}

// Similar to above, but in param is a site profile name
// Note that this is more efficient than getting from a URL,
//   since we don't have to get all the sitedata but can key off of sitename.
// Caller must supply the current docUrl or just a filename
// If doc URL is supplied, we find the publish subdirectory if publishUrl is part of docUrl
function GetPublishDataFromSiteName(siteName, docUrlOrFilename)
{
  var publishBranch = GetPublishPrefsBranch();
  if (!publishBranch)
    return null;

  var siteNameList = GetSiteNameList(false, false);
  if (!siteNameList)
    return null;
  for (var i = 0; i < siteNameList.length; i++)
  {
    if (siteNameList[i] == siteName)
    {
      var publishData = GetPublishData_internal(publishBranch, siteName);
      if (GetScheme(docUrlOrFilename))
        FillInMatchingPublishData(publishData, docUrlOrFilename);
      else
        publishData.filename = docUrlOrFilename;

      return publishData;
    }
  }
  return null;
}

function GetDefaultPublishData()
{
  var publishBranch = GetPublishPrefsBranch();
  if (!publishBranch)
    return null;

  var siteName = GetPublishStringPref(publishBranch, "default_site");
  if (!siteName)
    return null;

  return GetPublishData_internal(publishBranch, siteName);
}

function GetPublishData_internal(publishBranch, siteName)
{
  if (!publishBranch || !siteName)
    return null;

  var prefPrefix = "site_data." + siteName + ".";

  // We must have a publish url, else we ignore this site
  // (siteData and siteNames for sites with incomplete data 
  //  will get deleted by SavePublishSiteDataToPrefs)
  var publishUrl = GetPublishStringPref(publishBranch, prefPrefix+"url");
  if (!publishUrl)
    return null;

  var savePassword = false;
  var publishOtherFiles = true;
  try {
    savePassword = publishBranch.getBoolPref(prefPrefix+"save_password");
    publishOtherFiles = publishBranch.getBoolPref(prefPrefix+"publish_other_files");
  } catch (e) {}

  var publishData = { 
    siteName : siteName,
    previousSiteName : siteName,
    filename : "",
    username : GetPublishStringPref(publishBranch, prefPrefix+"username"),
    savePassword : savePassword,
    publishUrl : publishUrl,
    browseUrl  : GetPublishStringPref(publishBranch, prefPrefix+"browse_url"),
    docDir     : FormatDirForPublishing(GetPublishStringPref(publishBranch, prefPrefix+"doc_dir")),
    otherDir   : FormatDirForPublishing(GetPublishStringPref(publishBranch, prefPrefix+"other_dir")),
    publishOtherFiles : publishOtherFiles,
    saveDirs : false
  }

  // Get password from PasswordManager
  publishData.password = GetSavedPassword(publishData);

  // If password was found, user must have checked "Save password"
  //    checkbox in prompt outside of publishing, so override the pref we stored
  if (publishData.password)
  {
    if (!savePassword)
    {
      try {
        publishPrefsBranch.setBoolPref(prefPrefix+"save_password", true);
      } catch (e) {}
    }
    publishData.savePassword = true;
  }

  // Build history list of directories 
  // Always supply the root dir 
  publishData.dirList = [""];

  // Get the rest from prefs
  var dirCount = {value:0};
  var dirPrefs;
  try {
    dirPrefs = publishBranch.getChildList(prefPrefix+"dir.", dirCount);
  } catch (e) {}

  if (dirPrefs && dirCount.value > 0)
  {
    if (dirCount.value > 1)
      dirPrefs.sort();

    for (var j = 0; j < dirCount.value; j++)
    {
      var dirName = GetPublishStringPref(publishBranch, dirPrefs[j]);
      if (dirName)
        publishData.dirList[j+1] = dirName;
    }
  }

  return publishData;
}

/****************** Save publishing data methods *********************/

// Save the siteArray containing all current publish site data
function SavePublishSiteDataToPrefs(siteArray, defaultName)
{
  var publishBranch = GetPublishPrefsBranch();
  if (!publishBranch)
    return false;

  try {
    if (siteArray)
    {
      var defaultFound = false;

      // Clear existing names and data -- rebuild all site prefs
      publishBranch.deleteBranch("site_name.");
      publishBranch.deleteBranch("site_data.");

      for (var i = 0; i < siteArray.length; i++)
      {
        SavePublishData_Internal(publishBranch, siteArray[i], i);
        if (!defaultFound)
          defaultFound = defaultName == siteArray[i].siteName;
      }
      // Assure that we have a default name
      if (siteArray.length && !defaultFound)
        defaultName = siteArray[0].siteName;
    }

    // Save default site name
    SetPublishStringPref(publishBranch, "default_site", defaultName);
  
    // Force saving to file so next page edited finds these values
    SavePrefFile();
  }
  catch (ex) { return false; }

  return true;
}

// Update prefs if publish site already exists
//  or add prefs for a new site
function SavePublishDataToPrefs(publishData)
{
  if (!publishData || !publishData.publishUrl)
    return false;

  var publishBranch = GetPublishPrefsBranch();
  if (!publishBranch)
    return false;

  // Create name from URL if no site name is provided
  if (!publishData.siteName)
    publishData.siteName = CreateSiteNameFromUrl(publishData.publishUrl, publishData);

  var siteCount = {value:0};
  var siteNamePrefs;
  try {
    siteNamePrefs = publishBranch.getChildList("site_name.", siteCount);
  } catch (e) {}

  if (!siteNamePrefs || siteCount.value == 0)
  {
    // We currently have no site prefs, so create them
    var siteData = [publishData];
    return SavePublishSiteDataToPrefs(siteData, publishData.siteName);
  }

  // Use "previous" name if available in case it was changed
  var previousSiteName =  ("previousSiteName" in publishData && publishData.previousSiteName) ? 
                            publishData.previousSiteName : publishData.siteName;

  // Find site number of existing site or fall through at next available one
  // (Number is arbitrary; needed to construct unique "site_name.x" pref string)
  for (var i = 0; i < siteCount.value; i++)
  {
    var siteName = GetPublishStringPref(publishBranch, "site_name."+i);

    if (siteName == previousSiteName)
    {
      // Delete prefs for an existing site
      try {
        publishBranch.deleteBranch("site_data." + siteName + ".");
      } catch (e) {}
      break;
    }
  }

  // We've taken care of finding old duplicate, so be sure 'previous name' is current
  publishData.previousSiteName = publishData.siteName;

  var ret = SavePublishData_Internal(publishBranch, publishData, i);
  if (ret)
  {
    // Check if siteName was the default and we need to update that
    var defaultSiteName = GetPublishStringPref(publishBranch, "default_site");
    if (previousSiteName == defaultSiteName 
        && publishData.siteName != defaultSiteName)
      SetPublishStringPref(publishBranch, "default_site", publishData.siteName);

    SavePrefFile();

    // Clear signal to save these data
    if ("notInSiteData" in publishData && publishData.notInSiteData)
      publishData.notInSiteData = false;
  }
  return ret;
}

// Save data at a particular site number
function SavePublishData_Internal(publishPrefsBranch, publishData, siteIndex)
{
  if (!publishPrefsBranch || !publishData)
    return false;

  SetPublishStringPref(publishPrefsBranch, "site_name."+siteIndex, publishData.siteName);

  FixupUsernamePasswordInPublishData(publishData);

  var prefPrefix = "site_data." + publishData.siteName + "."

  SetPublishStringPref(publishPrefsBranch, prefPrefix+"url", publishData.publishUrl);
  SetPublishStringPref(publishPrefsBranch, prefPrefix+"browse_url", publishData.browseUrl);
  SetPublishStringPref(publishPrefsBranch, prefPrefix+"username", publishData.username);
  
  try {
    publishPrefsBranch.setBoolPref(prefPrefix+"save_password", publishData.savePassword);
    publishPrefsBranch.setBoolPref(prefPrefix+"publish_other_files", publishData.publishOtherFiles);
  } catch (e) {}

  // Save password using PasswordManager 
  // (If publishData.savePassword = false, this clears existing password)
  SavePassword(publishData);

  SetPublishStringPref(publishPrefsBranch, prefPrefix+"doc_dir", 
                       FormatDirForPublishing(publishData.docDir));

  if (publishData.publishOtherFiles && publishData.otherDir)
    SetPublishStringPref(publishPrefsBranch, prefPrefix+"other_dir",
                         FormatDirForPublishing(publishData.otherDir));

  if ("saveDirs" in publishData && publishData.saveDirs)
  {
    if (publishData.docDir)
      AppendNewDirToList(publishData, publishData.docDir);

    if (publishData.publishOtherFiles && publishData.otherDir 
        && publishData.otherDir != publishData.docDir)
      AppendNewDirToList(publishData, publishData.otherDir);
  }

  // Save array of subdirectories with site
  if (publishData.dirList.length)
  {
    publishData.dirList.sort();
    var dirIndex = 0;
    for (var j = 0; j < publishData.dirList.length; j++)
    {
      var dir = publishData.dirList[j];

      // Don't store the root dir
      if (dir && dir != "/")
      {
        SetPublishStringPref(publishPrefsBranch, prefPrefix + "dir." + dirIndex, dir);
        dirIndex++;
      }
    }
  }

  return true;
}

function AppendNewDirToList(publishData, newDir)
{
  newDir = FormatDirForPublishing(newDir);
  if (!publishData || !newDir)
    return;

  if (!publishData.dirList)
  {
    publishData.dirList = [newDir];
    return;
  }

  // Check if already in the list
  for (var i = 0; i < publishData.dirList.length; i++)
  {
    // Don't add if already in the list
    if (newDir == publishData.dirList[i])
      return;
  }
  // Add to end of list
  publishData.dirList[publishData.dirList.length] = newDir;
}

function RemovePublishSubdirectoryFromPrefs(publishData, removeDir)
{
  removeDir = FormatDirForPublishing(removeDir);
  if (!publishData || !publishData.siteName || !removeDir)
    return false;

  var publishBranch = GetPublishPrefsBranch();
  if (!publishBranch)
    return false;

  var prefPrefix = "site_data." + publishData.siteName + ".";

  // Remove dir from the default dir prefs
  if (publishData.docDir == removeDir)
  {
    publishData.docDir = "";
    SetPublishStringPref(publishBranch, prefPrefix+"doc_dir", "");
  }

  if (publishData.otherDir == removeDir)
  {
    publishData.otherDir = "";
    SetPublishStringPref(publishBranch, prefPrefix+"other_dir", "");
  }

  prefPrefix += "dir.";

  // Delete entire subdir list
  try {
    publishBranch.deleteBranch(prefPrefix);
  } catch (e) {}

  // Rebuild prefs, skipping over site to remove
  if (publishData.dirList.length)
  {
    var dirIndex = 0;
    var docDirInList = false;
    var otherDirInList = false;
    for (var i = 0; i < publishData.dirList.length; i++)
    {
      var dir = publishData.dirList[i];
      if (dir == removeDir)
      {
        // Remove item from the dirList array
        publishData.dirList.splice(i, 1);
        --i;
      }
      else if (dir && dir != "/") // skip empty or root dir
      {
        // Save to prefs
        SetPublishStringPref(publishBranch, prefPrefix + dirIndex, dir);
        dirIndex++;
      }
    }
  }
  SavePrefFile();
  return true;
}

function SetDefaultSiteName(name)
{
  if (name)
  {
    var publishBranch = GetPublishPrefsBranch();
    if (publishBranch)
      SetPublishStringPref(publishBranch, "default_site", name);

    SavePrefFile();
  }
}

function SavePrefFile()
{
  try {
    Services.prefs.savePrefFile(null);
  }
  catch (e) {}
}

/***************** Helper / utility methods ********************/

function GetPublishPrefsBranch()
{
  return Services.prefs.getBranch("editor.publish.");
}

function GetSiteNameList(doSort, defaultFirst)
{
  var publishBranch = GetPublishPrefsBranch();
  if (!publishBranch)
    return null;

  var siteCountObj = {value:0};
  var siteNamePrefs;
  try {
    siteNamePrefs = publishBranch.getChildList("site_name.", siteCountObj);
  } catch (e) {}

  if (!siteNamePrefs || siteCountObj.value == 0)
    return null;

  // Array of site names
  var siteNameList = [];
  var index = 0;
  var defaultName = "";
  if (defaultFirst)
  {
    defaultName = GetPublishStringPref(publishBranch, "default_site");
    // This always sorts to top -- replace with real string below
    siteNameList[0] = "";
    index++;
  }

  for (var i = 0; i < siteCountObj.value; i++)
  {
    var siteName = GetPublishStringPref(publishBranch, siteNamePrefs[i]);
    // Skip if siteName pref is empty or is default name
    if (siteName && siteName != defaultName)
    {
      siteNameList[index] = siteName;
      index++;
    }
  }

  if (siteNameList.length && doSort)
    siteNameList.sort();

  if (defaultName)
  {
    siteNameList[0] = defaultName;
    index++;
  }

  return siteNameList.length? siteNameList : null;
}

function PublishSiteNameExists(name, publishSiteData, skipSiteIndex)
{
  if (!name)
    return false;

  if (!publishSiteData)
  {
    publishSiteData = GetPublishSiteData();
    skipSiteIndex = -1;
  }

  if (!publishSiteData)
    return false;

  // Array of site names - sorted, but don't put default name first
  for (var i = 0; i < publishSiteData.length; i++)
  {
    if (i != skipSiteIndex && name == publishSiteData[i].siteName)
      return true;
  }
  return false;
}

// Find index of a site record in supplied publish site database
// docUrl: Document URL with or without filename
//         (Must end in "/" if no filename)
// dirObj.value =  the directory of the document URL 
//      relative to the base publishing URL, using "" if none
//
// XXX: Currently finds the site with the longest-matching url;
//      should we look for the shortest instead? Or match just the host portion?
function FindSiteIndexAndDocDir(publishSiteData, docUrl, dirObj)
{
  if (dirObj)
    dirObj.value = "";

  if (!publishSiteData || !docUrl || GetScheme(docUrl) == "file")
    return -1;

  var siteIndex = -1;
  var siteUrlLen = 0;
  
  for (var i = 0; i < publishSiteData.length; i++)
  {
    // Site publish or browse url needs to be contained in document URL,
    //  but that may also have a directory after the site base URL
    //  So we must examine all records to find the site URL that best
    //    matches the document URL: the longest-matching substring (XXX is this right?)
    var lenObj = {value:0};
    var tempData = Clone(publishSiteData[i]);

    // Check if this site matches docUrl (returns length of match if found)
    var len = FillInMatchingPublishData(tempData, docUrl);

    if (len > siteUrlLen)
    {
      siteIndex = i;
      siteUrlLen = len;
      if (dirObj)
        dirObj.value = tempData.docDir;

      // Continue to find the site with longest-matching publishUrl
    }
  }
  return siteIndex;
}

// Look for a matching publish url within the document url
// (We need to look at both "publishUrl" and "browseUrl" in case we are editing
//  an http: document but using ftp: to publish.)
// If match is found:
//    Fill in the filename and subirectory based on the docUrl and 
//    return the length of the docUrl with username+password stripped out
function FillInMatchingPublishData(publishData, docUrl)
{
  if (!publishData || !docUrl)
    return 0;

  // Separate docUrl into the base url and filename
  var lastSlash = docUrl.lastIndexOf("\/");
  var baseUrl = docUrl.slice(0, lastSlash+1);
  var filename = docUrl.slice(lastSlash+1);
    
  // Strip username+password from docUrl because these
  //  are stored separately in publishData, never embedded in the publishUrl
  // If both docUrl and publishData contain usernames,
  //   we must match that as well as the url
  var username = {value:""};
  baseUrl = StripUsernamePassword(baseUrl, username); 
  username = username.value;

  var matchedLength = 0;
  let pubUrlFound = publishData.publishUrl && baseUrl.startsWith(publishData.publishUrl);
  let browseUrlFound = publishData.browseUrl && baseUrl.startsWith(publishData.browseUrl);

  if ((pubUrlFound || browseUrlFound) 
      && (!username || !publishData.username || username == publishData.username))
  {
    // We found a match
    matchedLength = pubUrlFound ? publishData.publishUrl.length 
                            : publishData.browseUrl.length;

    if (matchedLength > 0)
    {
      publishData.filename = filename;

      // Subdirectory within the site is what's left in baseUrl after the matched portion
      publishData.docDir = FormatDirForPublishing(baseUrl.slice(matchedLength));
    }
  }
  return matchedLength;
}

// Prefs that don't exist will through an exception,
//  so just return an empty string
function GetPublishStringPref(prefBranch, name)
{
  if (prefBranch && name)
  {
    try {
      return prefBranch.getComplexValue(name, Components.interfaces.nsISupportsString).data;
    } catch (e) {}
  }
  return "";
}

function SetPublishStringPref(prefBranch, name, value)
{
  if (prefBranch && name)
  {
    try {
        var str = Components.classes["@mozilla.org/supports-string;1"]
                            .createInstance(Components.interfaces.nsISupportsString);
        str.data = value;
        prefBranch.setComplexValue(name, Components.interfaces.nsISupportsString, str);
    } catch (e) {}
  }
}

// Assure that a publishing URL ends in "/", "=", "&" or "?"
// Username and password should always be extracted as separate fields
//  and are not allowed to remain embedded in publishing URL
function FormatUrlForPublishing(url)
{
  url = TrimString(StripUsernamePassword(url));
  if (url)
  {
    var lastChar = url.charAt(url.length-1);
    if (lastChar != "/" && lastChar != "=" && lastChar != "&" && lastChar  != "?")
      return (url + "/");
  }
  return url;
}

// Username and password present in publish url are
//  extracted into the separate "username" and "password" fields 
//  of the publishData object
// Returns true if we did change the publishData
function FixupUsernamePasswordInPublishData(publishData)
{
  var ret = false;
  if (publishData && publishData.publishUrl)
  {
    var userObj = {value:""};
    var passObj = {value:""};
    publishData.publishUrl = FormatUrlForPublishing(StripUsernamePassword(publishData.publishUrl, userObj, passObj));
    if (userObj.value)
    {
      publishData.username = userObj.value;
      ret = true;
    }
    if (passObj.value)
    {
      publishData.password = passObj.value;
      ret = true;
    }
    // While we're at it, be sure browse URL is proper format
    publishData.browseUrl = FormatUrlForPublishing(publishData.browseUrl);
  }
  return ret;
}

// Assure that a publishing directory ends with "/" and does not begin with "/"
// Input dir is assumed to be a subdirectory string, not a full URL or pathname
function FormatDirForPublishing(dir)
{
  dir = TrimString(dir);

  // The "//" case is an expected "typo" filter
  //  that simplifies code below!
  if (!dir || dir == "/" || dir == "//")
    return "";

  // Remove leading "/"
  if (dir.startsWith("/"))
    dir = dir.slice(1);

  // Append "/" at the end if necessary
  var dirLen = dir.length;
  var lastChar = dir.charAt(dirLen-1);
  if (dirLen > 1 && ["/", "=", "&", "?"].indexOf(lastChar) == -1)
    dir += "/";

  return dir;
}

function GetSavedPassword(publishData)
{
  if (!publishData || !publishData.publishUrl)
    return "";

  let url = GetUrlForPasswordManager(publishData);
  let logins = Services.logins.findLogins({}, url, null, url);

  for (let i = 0; i < logins.length; i++) {
    if (logins[i].username == publishData.username)
      return logins[i].password;
  }

  return "";
}

function SavePassword(publishData)
{
  if (!publishData || !publishData.publishUrl || !publishData.username)
    return false;

  let url = GetUrlForPasswordManager(publishData);

  // Remove existing entry by finding all logins that match.
  let logins = Services.logins.findLogins({}, url, null, url);

  for (let i = 0; i < logins.length; i++) {
    if (logins[i].username == publishData.username) {
      Services.logins.removeLogin(logins[i]);
      break;
    }
  }

  // If SavePassword is true, add new password.
  if (publishData.savePassword)
  {
    let authInfo = Components.classes["@mozilla.org/login-manager/loginInfo;1"]
                             .createInstance(Components.interfaces.nsILoginInfo);
    authInfo.init(url, null, url, publishData.username, publishData.password,
                  "", "");
    Services.logins.addLogin(authInfo);
  }

  return true;
}

function GetUrlForPasswordManager(publishData)
{
  if (!publishData || !publishData.publishUrl)
    return false;

  let url = Services.io.newURI(publishData.publishUrl, null, null);

  if (url.scheme == "ftp" && publishData.username)
    // Include username in the URL so we can handle multiple users per server
    // in the password manager
    url = url.scheme + "://" + publishData.username + "@" + url.hostPort;
  else
    url = url.scheme + "://" + url.hostPort;

  return url;
}
