text: "use strict";
  
  var fs = require("fs");
  var assert = require("chai").assert;
  var PrismLoader = require("./prism-loader");
  var TokenStreamTransformer = require("./token-stream-transformer");
  
  /**
   * Handles parsing of a test case file.
   *
   *
   * A test case file consists of at least two parts, separated by a line of dashes.
   * This separation line must start at the beginning of the line and consist of at least three dashes.
   *
   * The test case file can either consist of two parts:
   *
   *     {source code}
   *     ----
   *     {expected token stream}
   *
   *
   * or of three parts:
   *
   *     {source code}
   *     ----
   *     {expected token stream}
   *     ----
   *     {text comment explaining the test case}
   *
   * If the file contains more than three parts, the remaining parts are just ignored.
   * If the file however does not contain at least two parts (so no expected token stream),
   * the test case will later be marked as failed.
   *
   *
   * @type {{runTestCase: Function, transformCompiledTokenStream: Function, parseTestCaseFile: Function}}
   */
  module.exports = {
  
  	/**
  	 * Runs the given test case file and asserts the result
  	 *
  	 * The passed language identifier can either be a language like "css" or a composed language
  	 * identifier like "css+markup". Composed identifiers can be used for testing language inclusion.
  	 *
  	 * When testing language inclusion, the first given language is the main language which will be passed
  	 * to Prism for highlighting ("css+markup" will result in a call to Prism to highlight with the "css" grammar).
  	 * But it will be ensured, that the additional passed languages will be loaded too.
  	 *
  	 * The languages will be loaded in the order they were provided.
  	 *
  	 * @param {string} languageIdentifier
  	 * @param {string} filePath
  	 */
  	runTestCase: function (languageIdentifier, filePath) {
  		var testCase = this.parseTestCaseFile(filePath);
  		var usedLanguages = this.parseLanguageNames(languageIdentifier);
  
  		if (null === testCase) {
  			throw new Error("Test case file has invalid format (or the provided token stream is invalid JSON), please read the docs.");
  		}
  
  		var Prism = PrismLoader.createInstance(usedLanguages.languages);
  		// the first language is the main language to highlight
  		var mainLanguageGrammar = Prism.languages[usedLanguages.mainLanguage];
  		var compiledTokenStream = Prism.tokenize(testCase.testSource, mainLanguageGrammar);
  		var simplifiedTokenStream = TokenStreamTransformer.simplify(compiledTokenStream);
  
  		assert.deepEqual(simplifiedTokenStream, testCase.expectedTokenStream, testCase.comment);
  	},
  
  
  	/**
  	 * Parses the language names and finds the main language.
  	 *
  	 * It is either the last language or the language followed by a exclamation mark “!”.
  	 * There should only be one language with an exclamation mark.
  	 *
  	 * @param {string} languageIdentifier
  	 *
  	 * @returns {{languages: string[], mainLanguage: string}}
  	 */
  	parseLanguageNames: function (languageIdentifier) {
  		var languages = languageIdentifier.split("+");
  		var mainLanguage = null;
  
  		languages = languages.map(
  			function (language) {
  				var pos = language.indexOf("!");
  
  				if (-1 < pos) {
  					if (mainLanguage) {
  						throw "There are multiple main languages defined.";
  					}
  
  					mainLanguage = language.replace("!", "");
  					return mainLanguage;
  				}
  
  				return language;
  			}
  		);
  
  		if (!mainLanguage) {
  			mainLanguage = languages[languages.length-1];
  		}
  
  		return {
  			languages: languages,
  			mainLanguage: mainLanguage
  		};
  	},
  
  
  	/**
  	 * Parses the test case from the given test case file
  	 *
  	 * @private
  	 * @param {string} filePath
  	 * @returns {{testSource: string, expectedTokenStream: Array.<Array.<string>>, comment:string?}|null}
  	 */
  	parseTestCaseFile: function (filePath) {
  		var testCaseSource = fs.readFileSync(filePath, "utf8");
  		var testCaseParts = testCaseSource.split(/^-{10,}\w*$/m);
  
  		try {
  			var testCase = {
  				testSource: testCaseParts[0].trim(),
  				expectedTokenStream: JSON.parse(testCaseParts[1]),
  				comment: null
  			};
  
  			// if there are three parts, the third one is the comment
  			// explaining the test case
  			if (testCaseParts[2]) {
  				testCase.comment = testCaseParts[2].trim();
  			}
  
  			return testCase;
  		}
  		catch (e) {
  			// the JSON can't be parsed (e.g. it could be empty)
  			return null;
  		}
  	},
  
  	/**
  	 * Runs the given pieces of codes and asserts their result.
  	 *
  	 * Code is provided as the key and expected result as the value.
  	 *
  	 * @param {string} languageIdentifier
  	 * @param {object} codes
  	 */
  	runTestsWithHooks: function (languageIdentifier, codes) {
  		var usedLanguages = this.parseLanguageNames(languageIdentifier);
  		var Prism = PrismLoader.createInstance(usedLanguages.languages);
  		// the first language is the main language to highlight
  
  		for (var code in codes) {
  			if (codes.hasOwnProperty(code)) {
  				var env = {
  					element: {},
  					language: usedLanguages.mainLanguage,
  					grammar: Prism.languages[usedLanguages.mainLanguage],
  					code: code
  				};
  				Prism.hooks.run('before-highlight', env);
  				env.highlightedCode = Prism.highlight(env.code, Prism.languages[usedLanguages.mainLanguage], usedLanguages.mainLanguage);
  				Prism.hooks.run('before-insert', env);
  				env.element.innerHTML = env.highlightedCode;
  				Prism.hooks.run('after-highlight', env);
  				Prism.hooks.run('complete', env);
  				assert.equal(env.highlightedCode, codes[code]);
  			}
  		}
  	}
  };
  