/* 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/. */

const {Cc, Ci} = require("chrome");
const { setTimeout } = require("timer");
const { Loader } = require('test-harness/loader');
const wm = Cc["@mozilla.org/appshell/window-mediator;1"].
           getService(Ci.nsIWindowMediator);
const privateBrowsing = require('private-browsing');
const pbUtils = require('api-utils/private-browsing/utils');
let browserWindows;

function getTestRunnerWindow() wm.getMostRecentWindow("test:runner");

exports.testOpenAndCloseWindow = function(test) {
  test.waitUntilDone();

  test.assertEqual(browserWindows.length, 1, "Only one window open");

  browserWindows.open({
    url: "data:text/html;charset=utf-8,<title>windows API test</title>",
    onOpen: function(window) {
      test.assertEqual(this, browserWindows,
                       "The 'this' object is the windows object.");
      test.assertEqual(window.tabs.length, 1, "Only one tab open");
      test.assertEqual(browserWindows.length, 2, "Two windows open");
      window.tabs.activeTab.on('ready', function onReady(tab) {
        tab.removeListener('ready', onReady);
        test.assert(window.title.indexOf("windows API test") != -1,
                    "URL correctly loaded");
        window.close();
      });
    },
    onClose: function(window) {
      test.assertEqual(window.tabs.length, 0, "Tabs were cleared");
      test.assertEqual(browserWindows.length, 1, "Only one window open");
      test.done();
    }
  });
};

exports.testPerWindowPrivateBrowsing_getter = function(test) {
  let activeWindow =  wm.getMostRecentWindow("navigator:browser");

  // is per-window PB implemented?
  if (pbUtils.isWindowPBEnabled(activeWindow)) {
    let currentState = activeWindow.gPrivateBrowsingUI.privateWindow;

    pbUtils.setMode(false, activeWindow);

    test.assertEqual(activeWindow.gPrivateBrowsingUI.privateWindow,
                     browserWindows.activeWindow.isPrivateBrowsing,
                     "Active window is not in PB mode");

    pbUtils.setMode(true, activeWindow);

    test.assertEqual(activeWindow.gPrivateBrowsingUI.privateWindow,
                     browserWindows.activeWindow.isPrivateBrowsing,
                     "Active window is in PB mode");

    pbUtils.setMode(currentState, activeWindow);
  }
  else {
    test.assertEqual(require('private-browsing').isActive,
                browserWindows.activeWindow.isPrivateBrowsing,
                "Active window PB mode is the same value as the mode returned " +
                "by private-browsing module");
  }
};

exports.testPerWindowPrivateBrowsing_events = function(test) {
  test.waitUntilDone();

  let chromeWin;

  let setPBMode = function(mode) pbUtils.setMode(mode, chromeWin);

  let windows = browserWindows;
  let windowCount = browserWindows.length;
  let testWindow;

  let globalCount = 0;
  windows.on('private-browsing', function onGlobalPB(window) {
    if (testWindow != window) return; // ignore windows not involved in the test (if any)
    if (++globalCount == 2) // stop listening after 2 PB state changes
      windows.removeListener('private-browsing', onGlobalPB);
  });

  testWindow = browserWindows.open({
    url: "data:text/html;charset=utf-8,foo",
    onOpen: function(window) {
      test.assertEqual(window.isPrivateBrowsing, false, 'newly opened window is not in PB mode');

      chromeWin = wm.getMostRecentWindow("navigator:browser");

      let count = 0;
      window.on('private-browsing', function onPB(window) {
        if (++count == 1) {
          test.assertEqual(window.isPrivateBrowsing, true, 'window is in PB mode');
          setPBMode(false);
        }
        else if (count == 2) {
          test.assertEqual(privateBrowsing.isActive, false, 'private browsing is disabled');
          test.assertEqual(window.isPrivateBrowsing, false, 'window is in not PB mode');
          window.removeListener('private-browsing', onPB);

          let (counter = 0) {
            window.on('private-browsing', function onPB2(window) {
              if (++counter != 2) return;
              window.removeListener('private-browsing', onPB2);

              // close the window
              window.close(function() {
                test.assertEqual(window.isPrivateBrowsing, false, 'window is in not PB mode');
                test.assertEqual(count, 2, 'window private browsing listener was not removed');
                test.assertEqual(globalCount, 2, 'window private browsing listener was not removed');
                test.assertEqual(windowCount, browserWindows.length, 'window count is unchanged');

                // end test
                test.done();
              });
            });

            // our listener is now off, so let's test that worked..
            setPBMode(true);
            setPBMode(false);
          }
        }
        else {
          test.fail('window private browsing listener was not removed');
        }
      });

      setPBMode(true);
    }
  });
};

exports.testPerWindowPrivateBrowsing_eventsUnload = function(test) {
  test.waitUntilDone();

  let chromeWin;
  let setPBMode = function(mode) pbUtils.setMode(mode, chromeWin);
  let loader = Loader(module);
  let {browserWindows: windows} = loader.require('windows');
  let testWindow;
  let count = 0;
  let globalCount = 0;

  browserWindows.on('private-browsing', function onGlobalPB(window) {
    if (testWindow != window) return; // ignore windows not involved in the test (if any)
    if (++globalCount == 2) { // stop listening after 2 PB state changes
      browserWindows.removeListener('private-browsing', onGlobalPB);

      test.assertEqual(window.isPrivateBrowsing, false, 'PB mode is off');
      test.assertEqual(globalCount, 2, 'total calls to setPBMode is correct');
      test.assertEqual(count, 1, 'unload of PB listener worked');

      window.close(function() {

        // end test
        test.done();
      });
    }
    else {
      setPBMode(!window.isPrivateBrowsing);
    }
  });

  testWindow = windows.open({
    url: "data:text/html;charset=utf-8,foo",
    onOpen: function(window) {
      test.assertEqual(window.isPrivateBrowsing, false, 'newly opened window is not in PB mode');
      testWindow = browserWindows.activeWindow;

      chromeWin = wm.getMostRecentWindow("navigator:browser");

      window.on('private-browsing', function() {
        count++;
        loader.unload();
      });

      setPBMode(true);
    }
  });
};

exports.testAutomaticDestroy = function(test) {
  test.waitUntilDone();
  let windows = browserWindows;

  // Create a second windows instance that we will unload
  let called = false;
  let loader = Loader(module);
  let windows2 = loader.require("windows").browserWindows;
  windows2.on("open", function() {
    called = true;
  });

  loader.unload();

  // Fire a windows event and check that this unloaded instance is inactive
  windows.open({
    url: "data:text/html;charset=utf-8,foo",
    onOpen: function(window) {
      setTimeout(function () {
        test.assert(!called,
          "Unloaded windows instance is destroyed and inactive");
        window.close(function () {
          test.done();
        });
      });
    }
  });

};

exports.testOnOpenOnCloseListeners = function(test) {
  test.waitUntilDone();
  let windows = browserWindows;

  test.assertEqual(browserWindows.length, 1, "Only one window open");

  let received = {
    listener1: false,
    listener2: false,
    listener3: false,
    listener4: false
  }

   function listener1() {
    test.assertEqual(this, windows, "The 'this' object is the windows object.");
    if (received.listener1)
      test.fail("Event received twice");
    received.listener1 = true;
  }

  function listener2() {
    if (received.listener2)
      test.fail("Event received twice");
    received.listener2 = true;
  }

  function listener3() {
    test.assertEqual(this, windows, "The 'this' object is the windows object.");
    if (received.listener3)
      test.fail("Event received twice");
    received.listener3 = true;
  }

  function listener4() {
    if (received.listener4)
      test.fail("Event received twice");
    received.listener4 = true;
  }

  windows.on('open', listener1);
  windows.on('open', listener2);
  windows.on('close', listener3);
  windows.on('close', listener4);

  function verify() {
    test.assert(received.listener1, "onOpen handler called");
    test.assert(received.listener2, "onOpen handler called");
    test.assert(received.listener3, "onClose handler called");
    test.assert(received.listener4, "onClose handler called");

    windows.removeListener('open', listener1);
    windows.removeListener('open', listener2);
    windows.removeListener('close', listener3);
    windows.removeListener('close', listener4);
    test.done();
  }


  windows.open({
    url: "data:text/html;charset=utf-8,foo",
    onOpen: function(window) {
      window.close(verify);
    }
  });
};

exports.testWindowTabsObject = function(test) {
  test.waitUntilDone();

  browserWindows.open({
    url: "data:text/html;charset=utf-8,<title>tab 1</title>",
    onOpen: function onOpen(window) {
      test.assertEqual(window.tabs.length, 1, "Only 1 tab open");

      window.tabs.open({
        url: "data:text/html;charset=utf-8,<title>tab 2</title>",
        inBackground: true,
        onReady: function onReady(newTab) {
          test.assertEqual(window.tabs.length, 2, "New tab open");
          test.assertEqual(newTab.title, "tab 2", "Correct new tab title");
          test.assertEqual(window.tabs.activeTab.title, "tab 1", "Correct active tab");

          let i = 1;
          for each (let tab in window.tabs)
            test.assertEqual(tab.title, "tab " + i++, "Correct title");

          window.close();
        }
      });
    },
    onClose: function onClose(window) {
      test.assertEqual(window.tabs.length, 0, "No more tabs on closed window");
      test.done();
    }
  });
};

exports.testActiveWindow = function(test) {
  const xulApp = require("xul-app");
  if (xulApp.versionInRange(xulApp.platformVersion, "1.9.2", "1.9.2.*")) {
    test.pass("This test is disabled on 3.6. For more information, see bug 598525");
    return;
  }

  let windows = browserWindows;

  // API window objects
  let window2, window3;

  // Raw window objects
  let nonBrowserWindow = getTestRunnerWindow(), rawWindow2, rawWindow3;

  test.waitUntilDone();

  let testSteps = [
    function() {
      test.assertEqual(windows.length, 3, "Correct number of browser windows");
      let count = 0;
      for (let window in windows)
        count++;
      test.assertEqual(count, 3, "Correct number of windows returned by iterator");

      rawWindow2.focus();
      continueAfterFocus(rawWindow2);
    },
    function() {
      nonBrowserWindow.focus();
      continueAfterFocus(nonBrowserWindow);
    },
    function() {
      /**
       * Bug 614079: This test fails intermittently on some specific linux
       *             environnements, without being able to reproduce it in same
       *             distribution with same window manager.
       *             Disable it until being able to reproduce it easily.

      // On linux, focus is not consistent, so we can't be sure
      // what window will be on top.
      // Here when we focus "non-browser" window,
      // Any Browser window may be selected as "active".
      test.assert(windows.activeWindow == window2 || windows.activeWindow == window3,
        "Non-browser windows aren't handled by this module");
      */
      window2.activate();
      continueAfterFocus(rawWindow2);
    },
    function() {
      test.assertEqual(windows.activeWindow.title, window2.title, "Correct active window - 2");
      window3.activate();
      continueAfterFocus(rawWindow3);
    },
    function() {
      test.assertEqual(windows.activeWindow.title, window3.title, "Correct active window - 3");
      nonBrowserWindow.focus();
      finishTest();
    }
  ];

  windows.open({
    url: "data:text/html;charset=utf-8,<title>window 2</title>",
    onOpen: function(window) {
      window2 = window;
      rawWindow2 = wm.getMostRecentWindow("navigator:browser");

      windows.open({
        url: "data:text/html;charset=utf-8,<title>window 3</title>",
        onOpen: function(window) {
          window.tabs.activeTab.on('ready', function onReady() {
            window3 = window;
            rawWindow3 = wm.getMostRecentWindow("navigator:browser");
            nextStep()
          });
        }
      });
    }
  });

  function nextStep() {
    if (testSteps.length > 0)
      testSteps.shift()();
  }

  function continueAfterFocus(targetWindow) {

    // Based on SimpleTest.waitForFocus
    var fm = Cc["@mozilla.org/focus-manager;1"].
             getService(Ci.nsIFocusManager);

    var childTargetWindow = {};
    fm.getFocusedElementForWindow(targetWindow, true, childTargetWindow);
    childTargetWindow = childTargetWindow.value;

    var focusedChildWindow = {};
    if (fm.activeWindow) {
      fm.getFocusedElementForWindow(fm.activeWindow, true, focusedChildWindow);
      focusedChildWindow = focusedChildWindow.value;
    }

    var focused = (focusedChildWindow == childTargetWindow);
    if (focused) {
      nextStep();
    } else {
      childTargetWindow.addEventListener("focus", function focusListener() {
        childTargetWindow.removeEventListener("focus", focusListener, true);
        nextStep();
      }, true);
    }

  }

  function finishTest() {
    window3.close(function() {
      window2.close(function() {
        test.done();
      });
    });
  }
};

exports.testTrackWindows = function(test) {
  test.waitUntilDone();

  let windows = [];
  let actions = [];

  let expects = [
    "activate 0", "global activate 0", "deactivate 0", "global deactivate 0",
    "activate 1", "global activate 1", "deactivate 1", "global deactivate 1",
    "activate 2", "global activate 2"
  ];

  function shutdown(window) {
    if (this.length === 1) {
      test.assertEqual(actions.join(), expects.join(),
        "correct activate and deactivate sequence")

      test.done();
    }
  }

  function openWindow() {
    windows.push(browserWindows.open({
      url: "data:text/html;charset=utf-8,<i>Hi</i>",

      onActivate: function(window) {
        let index = windows.indexOf(window);

        actions.push("activate " + index);

        if (windows.length < 3)
          openWindow()
        else
          for each (let win in windows)
            win.close(shutdown)
      },

      onDeactivate: function(window) {
        let index = windows.indexOf(window);

        actions.push("deactivate " + index)
      }
    }));
  }

  browserWindows.on("activate", function (window) {
    let index = windows.indexOf(window);

    actions.push("global activate " + index)
  })

  browserWindows.on("deactivate", function (window) {
    let index = windows.indexOf(window);
    // ignore windows not involved with test
    if (index < 0) return;

    actions.push("global deactivate " + index)
  })

  openWindow();
};

// If the module doesn't support the app we're being run in, require() will
// throw.  In that case, remove all tests above from exports, and add one dummy
// test that passes.
try {
  browserWindows = require("windows").browserWindows;
}
catch (err) {
  // This bug should be mentioned in the error message.
  let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=571449";
  if (err.message.indexOf(bug) < 0)
    throw err;

  module.exports = {
    testAppNotSupported: function (test) {
      test.pass("the windows module does not support this application.");
    }
  }
}
