/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

// Tests that the text filter box works to filter based on location.

"use strict";

// In this test, we are trying to test the filtering functionality of the filter
// input. We test filtering not only for the contents of logs themseleves but
// also for the filenames.
//
// We simulate an HTML file which executes two Javascript files, one with an
// ASCII filename outputs some ASCII logs and the other one with a Unicode
// filename outputs some Unicode logs.

const SEASON = {
  english: "season",
  chinese: "\u5b63",
};
const SEASONS = [
  {
    english: "spring",
    chinese: "\u6625",
    escapedChinese: "\\u6625",
  },
  {
    english: "summer",
    chinese: "\u590f",
    escapedChinese: "\\u590f",
  },
  {
    english: "autumn",
    chinese: "\u79cb",
    escapedChinese: "\\u79cb",
  },
  {
    english: "winter",
    chinese: "\u51ac",
    escapedChinese: "\\u51ac",
  },
];

// filenames
const HTML_FILENAME = `test.html`;
const JS_ASCII_FILENAME = `${SEASON.english}.js`;
const JS_UNICODE_FILENAME = `${SEASON.chinese}.js`;
const ENCODED_JS_UNICODE_FILENAME = encodeURIComponent(JS_UNICODE_FILENAME);

// file contents
const HTML_CONSOLE_OUTPUT = `Test filtering ${SEASON.english} names.`;
const HTML_CONTENT = `<!DOCTYPE html>
<meta charset="utf-8">
<title>Test filtering logs by filling keywords in the filter input.</title>
<script>
console.log("${HTML_CONSOLE_OUTPUT}");
</script>
<script src="/${JS_ASCII_FILENAME}"></script>
<script src="/${ENCODED_JS_UNICODE_FILENAME}"></script>`;

add_task(async function() {
  const testUrl = createServerAndGetTestUrl();
  const hud = await openNewTabAndConsole(testUrl);

  // Let's wait for the last logged message of each file to be displayed in the
  // output, in order to make sure all the logged messages have been displayed.
  const lastSeason = SEASONS[SEASONS.length - 1];
  await waitFor(() =>
    findMessage(hud, lastSeason.english) &&
    findMessage(hud, lastSeason.chinese)
  );

  // One external Javascript file outputs every season name in English, and the
  // other Javascript file outputs every season name in Chinese.
  // The HTML file outputs one line on its own.
  // So the total number of all the logs is the doubled number of seasons plus
  // one.
  let visibleLogs = getVisibleLogs(hud);
  is(visibleLogs.length, SEASONS.length * 2 + 1,
       "the total number of all the logs before starting filtering");
  checkLogContent(visibleLogs[0], HTML_CONSOLE_OUTPUT, HTML_FILENAME);
  for (let i = 0; i < SEASONS.length; i++) {
    checkLogContent(visibleLogs[i + 1], SEASONS[i].english, JS_ASCII_FILENAME);
  }
  for (let i = 0; i < SEASONS.length; i++) {
    checkLogContent(visibleLogs[i + 1 + SEASONS.length], SEASONS[i].chinese,
                    JS_UNICODE_FILENAME);
  }

  // All the logs outputted by the ASCII Javascript file are visible, the others
  // are hidden.
  setFilterInput(hud, JS_ASCII_FILENAME);
  visibleLogs = getVisibleLogs(hud);
  is(visibleLogs.length, SEASONS.length,
       `the number of all the logs containing ${JS_ASCII_FILENAME}`);
  for (let i = 0; i < SEASONS.length; i++) {
    checkLogContent(visibleLogs[i], SEASONS[i].english, JS_ASCII_FILENAME);
  }

  // Every season name in English is outputted once.
  for (const curSeason of SEASONS) {
    setFilterInput(hud, curSeason.english);
    visibleLogs = getVisibleLogs(hud);
    is(visibleLogs.length, 1,
         `the number of all the logs containing ${curSeason.english}`);
    checkLogContent(visibleLogs[0], curSeason.english, JS_ASCII_FILENAME);
  }

  // All the logs outputted by the Unicode Javascript file are visible, the
  // others are hidden.
  setFilterInput(hud, JS_UNICODE_FILENAME);
  visibleLogs = getVisibleLogs(hud);
  is(visibleLogs.length, SEASONS.length,
       `the number of all the logs containing ${JS_UNICODE_FILENAME}`);
  for (let i = 0; i < SEASONS.length; i++) {
    checkLogContent(visibleLogs[i], SEASONS[i].chinese, JS_UNICODE_FILENAME);
  }

  // Every season name in Chinese is outputted once.
  for (const curSeason of SEASONS) {
    setFilterInput(hud, curSeason.chinese);
    visibleLogs = getVisibleLogs(hud);
    is(visibleLogs.length, 1,
         `the number of all the logs containing ${curSeason.chinese}`);
    checkLogContent(visibleLogs[0], curSeason.chinese, JS_UNICODE_FILENAME);
  }

  // The filename of the ASCII Javascript file contains the English word season,
  // so all the logs outputted by the file are visible, besides, the HTML
  // outputs one line containing the English word season, so it is also visible.
  // The other logs are hidden. So the number of all the visible logs is the
  // season number plus one.
  setFilterInput(hud, SEASON.english);
  visibleLogs = getVisibleLogs(hud);
  is(visibleLogs.length, SEASONS.length + 1,
       `the number of all the logs containing ${SEASON.english}`);
  checkLogContent(visibleLogs[0], HTML_CONSOLE_OUTPUT, HTML_FILENAME);
  for (let i = 0; i < SEASONS.length; i++) {
    checkLogContent(visibleLogs[i + 1], SEASONS[i].english, JS_ASCII_FILENAME);
  }

  // The filename of the Unicode Javascript file contains the Chinese word
  // season, so all the logs outputted by the file are visible. The other logs
  // are hidden. So the number of all the visible logs is the season number.
  setFilterInput(hud, SEASON.chinese);
  visibleLogs = getVisibleLogs(hud);
  is(visibleLogs.length, SEASONS.length,
       `the number of all the logs containing ${SEASON.chinese}`);
  for (let i = 0; i < SEASONS.length; i++) {
    checkLogContent(visibleLogs[i], SEASONS[i].chinese, JS_UNICODE_FILENAME);
  }

  // After clearing the text in the filter input box, all the logs are visible
  // again.
  clearFilterInput(hud);
  visibleLogs = getVisibleLogs(hud);
  is(visibleLogs.length, SEASONS.length * 2 + 1,
       "the total number of all the logs after clearing filtering");
  checkLogContent(visibleLogs[0], HTML_CONSOLE_OUTPUT, HTML_FILENAME);
  for (let i = 0; i < SEASONS.length; i++) {
    checkLogContent(visibleLogs[i + 1], SEASONS[i].english, JS_ASCII_FILENAME);
  }
  for (let i = 0; i < SEASONS.length; i++) {
    checkLogContent(visibleLogs[i + 1 + SEASONS.length], SEASONS[i].chinese,
                    JS_UNICODE_FILENAME);
  }
});

// Create an HTTP server to simulate a response for the a URL request and return
// the URL.
function createServerAndGetTestUrl() {
  const httpServer = createTestHTTPServer();

  httpServer.registerContentType("html", "text/html");
  httpServer.registerContentType("js", "application/javascript");

  httpServer.registerPathHandler("/" + HTML_FILENAME,
    function(request, response) {
      response.setStatusLine(request.httpVersion, 200, "OK");
      response.write(HTML_CONTENT);
    }
  );
  httpServer.registerPathHandler("/" + JS_ASCII_FILENAME,
    function(request, response) {
      response.setStatusLine(request.httpVersion, 200, "OK");
      let content = "";
      for (const curSeason of SEASONS) {
        content += `console.log("${curSeason.english}");`;
      }
      response.write(content);
    }
  );
  httpServer.registerPathHandler("/" + ENCODED_JS_UNICODE_FILENAME,
    function(request, response) {
      response.setStatusLine(request.httpVersion, 200, "OK");
      let content = "";
      for (const curSeason of SEASONS) {
        content += `console.log("${curSeason.escapedChinese}");`;
      }
      response.write(content);
    }
  );
  const port = httpServer.identity.primaryPort;
  return `http://localhost:${port}/${HTML_FILENAME}`;
}

function setFilterInput(hud, value) {
  hud.ui.filterBox.focus();
  hud.ui.filterBox.select();
  EventUtils.sendString(value);
}

function clearFilterInput(hud) {
  hud.ui.filterBox.focus();
  hud.ui.filterBox.select();
  EventUtils.synthesizeKey("KEY_Delete");
}

function getVisibleLogs(hud) {
  const outputNode = hud.outputNode;
  return outputNode.querySelectorAll(".message");
}

/**
 * Check if the content of a log message is what we expect.
 *
 * @param Object node
 *        The node for the log message.
 * @param String expectedMessageBody
 *        The string we expect to match the message body in the log message.
 * @param String expectedFilename
 *        The string we expect to match the filename in the log message.
 */
function checkLogContent(node, expectedMessageBody, expectedFilename) {
  const messageBody = node.querySelector(".message-body").textContent;
  const location = node.querySelector(".message-location").textContent;
  // The location detail contains the line number and the column number, let's
  // strip them to get the filename.
  const filename = location.split(":")[0];

  is(messageBody, expectedMessageBody, "the expected message body");
  is(filename, expectedFilename, "the expected filename");
}
