/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */
/* Bug 651942 */

// Reference to the Scratchpad object.
var gScratchpad;

// References to the temporary nsIFiles.
var gFile01;
var gFile02;
var gFile03;
var gFile04;

// lists of recent files.
var lists = {
  recentFiles01: null,
  recentFiles02: null,
  recentFiles03: null,
  recentFiles04: null,
};

// Temporary file names.
var gFileName01 = "file01_ForBug651942.tmp";
var gFileName02 = "☕"; // See bug 783858 for more information
var gFileName03 = "file03_ForBug651942.tmp";
var gFileName04 = "file04_ForBug651942.tmp";

// Content for the temporary files.
var gFileContent01 = "hello.world.01('bug651942');";
var gFileContent02 = "hello.world.02('bug651942');";
var gFileContent03 = "hello.world.03('bug651942');";
var gFileContent04 = "hello.world.04('bug651942');";

async function startTest() {
  gScratchpad = gScratchpadWindow.Scratchpad;

  gFile01 = await createAndLoadTemporaryFile(gFileName01, gFileContent01);
  gFile02 = await createAndLoadTemporaryFile(gFileName02, gFileContent02);
  gFile03 = await createAndLoadTemporaryFile(gFileName03, gFileContent03);
}

// Test to see if the three files we created in the 'startTest()'-method have
// been added to the list of recent files.
async function testAddedToRecent() {
  lists.recentFiles01 = gScratchpad.getRecentFiles();

  is(lists.recentFiles01.length, 3,
     "Temporary files created successfully and added to list of recent files.");

  // Create a 4th file, this should clear the oldest file.
  gFile04 = await createAndLoadTemporaryFile(gFileName04, gFileContent04);
}

// We have opened a 4th file. Test to see if the oldest recent file was removed,
// and that the other files were reordered successfully.
function testOverwriteRecent() {
  lists.recentFiles02 = gScratchpad.getRecentFiles();

  is(lists.recentFiles02[0], lists.recentFiles01[1],
     "File02 was reordered successfully in the 'recent files'-list.");
  is(lists.recentFiles02[1], lists.recentFiles01[2],
     "File03 was reordered successfully in the 'recent files'-list.");
  isnot(lists.recentFiles02[2], lists.recentFiles01[2],
        "File04: was added successfully.");

  // Open the oldest recent file.
  gScratchpad.openFile(0);
}

// We have opened the "oldest"-recent file. Test to see if it is now the most
// recent file, and that the other files were reordered successfully.
function testOpenOldestRecent() {
  lists.recentFiles03 = gScratchpad.getRecentFiles();

  is(lists.recentFiles02[0], lists.recentFiles03[2],
     "File04 was reordered successfully in the 'recent files'-list.");
  is(lists.recentFiles02[1], lists.recentFiles03[0],
     "File03 was reordered successfully in the 'recent files'-list.");
  is(lists.recentFiles02[2], lists.recentFiles03[1],
     "File02 was reordered successfully in the 'recent files'-list.");

  Services.prefs.setIntPref("devtools.scratchpad.recentFilesMax", 0);
}

// The "devtools.scratchpad.recentFilesMax"-preference was set to zero (0).
// This should disable the "Open Recent"-menu by hiding it (this should not
// remove any files from the list). Test to see if it's been hidden.
function testHideMenu() {
  const menu = gScratchpadWindow.document.getElementById("sp-open_recent-menu");
  ok(menu.hasAttribute("hidden"), "The menu was hidden successfully.");

  Services.prefs.setIntPref("devtools.scratchpad.recentFilesMax", 2);
}

// We have set the recentFilesMax-pref to one (1), this enables the feature,
// removes the two oldest files, rebuilds the menu and removes the
// "hidden"-attribute from it. Test to see if this works.
function testChangedMaxRecent() {
  const menu = gScratchpadWindow.document.getElementById("sp-open_recent-menu");
  ok(!menu.hasAttribute("hidden"), "The menu is visible. \\o/");

  lists.recentFiles04 = gScratchpad.getRecentFiles();

  is(lists.recentFiles04.length, 2,
     "Two recent files were successfully removed from the 'recent files'-list");

  const doc = gScratchpadWindow.document;
  const popup = doc.getElementById("sp-menu-open_recentPopup");

  const menuitemLabel = popup.children[0].getAttribute("label");
  let correctMenuItem = false;
  if (menuitemLabel === lists.recentFiles03[2] &&
      menuitemLabel === lists.recentFiles04[1]) {
    correctMenuItem = true;
  }

  is(correctMenuItem, true,
     "Two recent files were successfully removed from the 'Open Recent'-menu");

  // We now remove one file from the harddrive and use the recent-menuitem for
  // it to make sure the user is notified that the file no longer exists.
  // This is tested in testOpenDeletedFile().
  gFile04.remove(false);

  // Make sure the file has been deleted before continuing to avoid
  // intermittent oranges.
  waitForFileDeletion();
}

function waitForFileDeletion() {
  if (gFile04.exists()) {
    executeSoon(waitForFileDeletion);
    return;
  }

  gFile04 = null;
  gScratchpad.openFile(0);
}

// By now we should have two recent files stored in the list but one of the
// files should be missing on the harddrive.
function testOpenDeletedFile() {
  const doc = gScratchpadWindow.document;
  const popup = doc.getElementById("sp-menu-open_recentPopup");

  is(gScratchpad.getRecentFiles().length, 1,
     "The missing file was successfully removed from the list.");
  // The number of recent files stored, plus the separator and the
  // clearRecentMenuItems-item.
  is(popup.children.length, 3,
     "The missing file was successfully removed from the menu.");
  ok(gScratchpad.notificationBox.currentNotification,
     "The notification was successfully displayed.");
  is(gScratchpad.notificationBox.currentNotification.label,
     gScratchpad.strings.GetStringFromName("fileNoLongerExists.notification"),
     "The notification label is correct.");

  gScratchpad.clearRecentFiles();
}

// We have cleared the last file. Test to see if the last file was removed,
// the menu is empty and was disabled successfully.
function testClearedAll() {
  const doc = gScratchpadWindow.document;
  const menu = doc.getElementById("sp-open_recent-menu");
  const popup = doc.getElementById("sp-menu-open_recentPopup");

  is(gScratchpad.getRecentFiles().length, 0,
     "All recent files removed successfully.");
  is(popup.children.length, 0, "All menuitems removed successfully.");
  ok(menu.hasAttribute("disabled"),
     "No files in the menu, it was disabled successfully.");

  finishTest();
}

async function createAndLoadTemporaryFile(aFileName, aFileContent) {
  info(`Create file: ${aFileName}`);

  // Create a temporary file.
  const aFile = FileUtils.getFile("TmpD", [aFileName]);
  aFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);

  // Write the temporary file.
  await OS.File.writeAtomic(aFile.path, aFileContent);

  gScratchpad.setFilename(aFile.path);
  let [status] = await gScratchpad.importFromFile(aFile.QueryInterface(Ci.nsIFile), true);
  ok(Components.isSuccessCode(status),
     "the temporary file was imported successfully with Scratchpad");
  status = await gScratchpad.saveFile();
  ok(Components.isSuccessCode(status),
     "the temporary file was saved successfully with Scratchpad");
  checkIfMenuIsPopulated();

  info(`File created: ${aFileName}`);
  return aFile;
}

function checkIfMenuIsPopulated() {
  const doc = gScratchpadWindow.document;
  const expectedMenuitemCount = doc.getElementById("sp-menu-open_recentPopup")
                              .children.length;
  // The number of recent files stored, plus the separator and the
  // clearRecentMenuItems-item.
  const recentFilesPlusExtra = gScratchpad.getRecentFiles().length + 2;

  if (expectedMenuitemCount > 2) {
    is(expectedMenuitemCount, recentFilesPlusExtra,
       "the recent files menu was populated successfully.");
  }
}

/**
 * The PreferenceObserver listens for preference changes while Scratchpad is
 * running.
 */
var PreferenceObserver = {
  _initialized: false,

  _timesFired: 0,
  set timesFired(aNewValue) {
    this._timesFired = aNewValue;
  },
  get timesFired() {
    return this._timesFired;
  },

  init: function PO_init() {
    if (this._initialized) {
      return;
    }

    this.branch = Services.prefs.getBranch("devtools.scratchpad.");
    this.branch.addObserver("", this);
    this._initialized = true;
  },

  observe: async function PO_observe(aMessage, aTopic, aData) {
    if (aTopic != "nsPref:changed") {
      return;
    }

    if (this._inProgress) {
      await this._inProgress;
    }

    this._inProgress = new Promise(async resolve => {
      info(`Times fired: ${this.timesFired}`);
      switch (this.timesFired) {
        case 0:
          this.timesFired = 1;
          break;
        case 1:
          this.timesFired = 2;
          break;
        case 2:
          this.timesFired = 3;
          await testAddedToRecent();
          break;
        case 3:
          this.timesFired = 4;
          testOverwriteRecent();
          break;
        case 4:
          this.timesFired = 5;
          testOpenOldestRecent();
          break;
        case 5:
          this.timesFired = 6;
          testHideMenu();
          break;
        case 6:
          this.timesFired = 7;
          testChangedMaxRecent();
          break;
        case 7:
          this.timesFired = 8;
          testOpenDeletedFile();
          break;
        case 8:
          this.timesFired = 9;
          testClearedAll();
          break;
      }

      this._inProgress = null;
      resolve();
    });
  },

  uninit: function PO_uninit() {
    this.branch.removeObserver("", this);
  }
};

function test() {
  waitForExplicitFinish();

  registerCleanupFunction(function() {
    gFile01.remove(false);
    gFile01 = null;
    gFile02.remove(false);
    gFile02 = null;
    gFile03.remove(false);
    gFile03 = null;
    // gFile04 was removed earlier.
    lists.recentFiles01 = null;
    lists.recentFiles02 = null;
    lists.recentFiles03 = null;
    lists.recentFiles04 = null;
    gScratchpad = null;

    PreferenceObserver.uninit();
    Services.prefs.clearUserPref("devtools.scratchpad.recentFilesMax");
  });

  Services.prefs.setIntPref("devtools.scratchpad.recentFilesMax", 3);

  // Initiate the preference observer after we have set the temporary recent
  // files max for this test.
  PreferenceObserver.init();

  gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(function() {
    openScratchpad(startTest);
  });

  gBrowser.loadURI("data:text/html,<p>test recent files in Scratchpad");
}

function finishTest() {
  finish();
}
