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

// This tests importing an ICS file. Rather than using the UI to trigger the
// import, loadEventsFromFile is called directly.

/* globals loadEventsFromFile */

var { CALENDARNAME, createCalendar, deleteCalendars } = ChromeUtils.import(
  "resource://testing-common/calendar/CalendarUtils.jsm"
);

const { MockFilePicker } = ChromeUtils.import("resource://specialpowers/MockFilePicker.jsm");
const ChromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry);

add_task(async () => {
  await CalendarTestUtils.setCalendarView(window, "month");
  await CalendarTestUtils.goToDate(window, 2019, 1, 1);

  let chromeUrl = Services.io.newURI(getRootDirectory(gTestPath) + "data/import.ics");
  let fileUrl = ChromeRegistry.convertChromeURL(chromeUrl);
  let file = fileUrl.QueryInterface(Ci.nsIFileURL).file;

  MockFilePicker.init(window);
  MockFilePicker.setFiles([file]);
  MockFilePicker.returnValue = MockFilePicker.returnCancel;

  let calendarId = createCalendar(window, CALENDARNAME);
  let calendar = cal.getCalendarManager().getCalendarById(calendarId);

  registerCleanupFunction(() => {
    deleteCalendars(window, CALENDARNAME);
    MockFilePicker.cleanup();
  });

  let cancelReturn = await loadEventsFromFile();
  ok(!cancelReturn, "loadEventsFromFile returns false on cancel");

  // Prepare to test the import dialog.
  MockFilePicker.returnValue = MockFilePicker.returnOK;

  let dialogWindowPromise = BrowserTestUtils.promiseAlertDialog(
    null,
    "chrome://calendar/content/calendar-ics-file-dialog.xhtml",
    {
      async callback(dialogWindow) {
        let doc = dialogWindow.document;
        let dialogElement = doc.querySelector("dialog");

        let optionsPane = doc.getElementById("calendar-ics-file-dialog-options-pane");
        let progressPane = doc.getElementById("calendar-ics-file-dialog-progress-pane");
        let resultPane = doc.getElementById("calendar-ics-file-dialog-result-pane");

        ok(!optionsPane.hidden);
        ok(progressPane.hidden);
        ok(resultPane.hidden);

        // Check the initial import dialog state.
        let displayedPath = doc.querySelector("#calendar-ics-file-dialog-file-path").value;
        let pathFragment = "browser/comm/calendar/test/browser/data/import.ics";
        if (Services.appinfo.OS == "WINNT") {
          pathFragment = pathFragment.replace(/\//g, "\\");
        }
        is(
          displayedPath.substring(displayedPath.length - pathFragment.length),
          pathFragment,
          "the displayed ics file path is correct"
        );

        let calendarMenu = doc.querySelector("#calendar-ics-file-dialog-calendar-menu");
        // 0 is the Home calendar.
        calendarMenu.selectedIndex = 1;
        let calendarMenuItems = calendarMenu.querySelectorAll("menuitem");
        is(calendarMenu.value, "Mozmill", "correct calendar name is selected");
        Assert.equal(calendarMenuItems.length, 1, "exactly one calendar is in the calendars menu");
        is(calendarMenuItems[0].selected, true, "calendar menu item is selected");

        let items;
        await TestUtils.waitForCondition(() => {
          items = doc.querySelectorAll(".calendar-ics-file-dialog-item-frame");
          return items.length == 4;
        }, "four calendar items are displayed");
        is(
          items[0].querySelector(".item-title").textContent,
          "Event One",
          "event 1 title should be correct"
        );
        is(
          items[1].querySelector(".item-title").textContent,
          "Event Two",
          "event 2 title should be correct"
        );
        is(
          items[2].querySelector(".item-title").textContent,
          "Event Three",
          "event 3 title should be correct"
        );
        is(
          items[3].querySelector(".item-title").textContent,
          "Event Four",
          "event 4 title should be correct"
        );
        is(
          items[0].querySelector(".item-date-row-start-date").textContent,
          cal.dtz.formatter.formatDateTime(cal.createDateTime("20190101T150000")),
          "event 1 start date should be correct"
        );
        is(
          items[0].querySelector(".item-date-row-end-date").textContent,
          cal.dtz.formatter.formatDateTime(cal.createDateTime("20190101T160000")),
          "event 1 end date should be correct"
        );
        is(
          items[1].querySelector(".item-date-row-start-date").textContent,
          cal.dtz.formatter.formatDateTime(cal.createDateTime("20190101T160000")),
          "event 2 start date should be correct"
        );
        is(
          items[1].querySelector(".item-date-row-end-date").textContent,
          cal.dtz.formatter.formatDateTime(cal.createDateTime("20190101T170000")),
          "event 2 end date should be correct"
        );
        is(
          items[2].querySelector(".item-date-row-start-date").textContent,
          cal.dtz.formatter.formatDateTime(cal.createDateTime("20190101T170000")),
          "event 3 start date should be correct"
        );
        is(
          items[2].querySelector(".item-date-row-end-date").textContent,
          cal.dtz.formatter.formatDateTime(cal.createDateTime("20190101T180000")),
          "event 3 end date should be correct"
        );
        is(
          items[3].querySelector(".item-date-row-start-date").textContent,
          cal.dtz.formatter.formatDateTime(cal.createDateTime("20190101T180000")),
          "event 4 start date should be correct"
        );
        is(
          items[3].querySelector(".item-date-row-end-date").textContent,
          cal.dtz.formatter.formatDateTime(cal.createDateTime("20190101T190000")),
          "event 4 end date should be correct"
        );

        function check_displayed_titles(expectedTitles) {
          let items = doc.querySelectorAll(
            ".calendar-ics-file-dialog-item-frame:not([hidden]) > calendar-item-summary"
          );
          Assert.deepEqual(
            [...items].map(summary => summary.item.title),
            expectedTitles
          );
        }

        let filterInput = doc.getElementById("calendar-ics-file-dialog-search-input");
        async function check_filter(filterText, expectedTitles) {
          let commandPromise = BrowserTestUtils.waitForEvent(filterInput, "command");

          EventUtils.synthesizeMouseAtCenter(filterInput, {}, dialogWindow);
          if (filterText) {
            EventUtils.synthesizeKey("a", { accelKey: true }, dialogWindow);
            EventUtils.sendString(filterText, dialogWindow);
          } else {
            EventUtils.synthesizeKey("VK_ESCAPE", {}, dialogWindow);
          }

          await commandPromise;

          check_displayed_titles(expectedTitles);
        }

        await check_filter("event", ["Event One", "Event Two", "Event Three", "Event Four"]);
        await check_filter("four", ["Event Four"]);
        await check_filter("ONE", ["Event One"]);
        await check_filter(`"event t"`, ["Event Two", "Event Three"]);
        await check_filter("", ["Event One", "Event Two", "Event Three", "Event Four"]);

        async function check_sort(order, expectedTitles) {
          let sortButton = doc.getElementById("calendar-ics-file-dialog-sort-button");
          let shownPromise = BrowserTestUtils.waitForEvent(sortButton, "popupshown");
          EventUtils.synthesizeMouseAtCenter(sortButton, {}, dialogWindow);
          await shownPromise;
          let hiddenPromise = BrowserTestUtils.waitForEvent(sortButton, "popuphidden");
          EventUtils.synthesizeMouseAtCenter(
            doc.getElementById(`calendar-ics-file-dialog-sort-${order}`),
            {},
            dialogWindow
          );
          await hiddenPromise;

          let items = doc.querySelectorAll("calendar-item-summary");
          is(items.length, 4, "four calendar items are displayed");
          Assert.deepEqual(
            [...items].map(summary => summary.item.title),
            expectedTitles
          );
        }

        await check_sort("title-ascending", [
          "Event Four",
          "Event One",
          "Event Three",
          "Event Two",
        ]);
        await check_sort("start-descending", [
          "Event Four",
          "Event Three",
          "Event Two",
          "Event One",
        ]);
        await check_sort("title-descending", [
          "Event Two",
          "Event Three",
          "Event One",
          "Event Four",
        ]);
        await check_sort("start-ascending", [
          "Event One",
          "Event Two",
          "Event Three",
          "Event Four",
        ]);

        items = doc.querySelectorAll(".calendar-ics-file-dialog-item-frame");

        // Import just the first item, and check that the correct number of items remains.
        let firstItemImportButton = items[0].querySelector(
          ".calendar-ics-file-dialog-item-import-button"
        );
        EventUtils.synthesizeMouseAtCenter(firstItemImportButton, { clickCount: 1 }, dialogWindow);

        await TestUtils.waitForCondition(() => {
          let remainingItems = doc.querySelectorAll(".calendar-ics-file-dialog-item-frame");
          return remainingItems.length == 3;
        }, "three items remain after importing the first item");
        check_displayed_titles(["Event Two", "Event Three", "Event Four"]);

        // Filter and import the shown items.
        await check_filter("four", ["Event Four"]);

        dialogElement.getButton("accept").click();
        ok(optionsPane.hidden);
        ok(!progressPane.hidden);
        ok(resultPane.hidden);

        await TestUtils.waitForCondition(() => !optionsPane.hidden);
        ok(progressPane.hidden);
        ok(resultPane.hidden);

        is(filterInput.value, "");
        check_displayed_titles(["Event Two", "Event Three"]);

        // Click the accept button to import the remaining items.
        dialogElement.getButton("accept").click();
        ok(optionsPane.hidden);
        ok(!progressPane.hidden);
        ok(resultPane.hidden);

        await TestUtils.waitForCondition(() => !resultPane.hidden);
        ok(optionsPane.hidden);
        ok(progressPane.hidden);

        let messageElement = doc.querySelector("#calendar-ics-file-dialog-result-message");
        is(messageElement.textContent, "Import complete.", "import success message appeared");

        dialogElement.getButton("accept").click();
      },
    }
  );

  await loadEventsFromFile();
  await dialogWindowPromise;

  // Check that the items were actually successfully imported.
  let promiseCalendar = cal.async.promisifyCalendar(calendar);
  let result = await promiseCalendar.getItems(
    Ci.calICalendar.ITEM_FILTER_ALL_ITEMS,
    0,
    cal.createDateTime("20190101T000000"),
    cal.createDateTime("20190102T000000")
  );
  is(result.length, 4, "all items that were imported were in fact imported");

  await CalendarTestUtils.monthView.waitForItemAt(window, 1, 3, 4);

  for (let item of result) {
    await promiseCalendar.deleteItem(item);
  }
});
