/*******************************************************************************
 * Copyright (c) 2008, 2019 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.ui.tests.chkpii;

import static org.junit.Assert.assertTrue;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.regex.Pattern;

import org.junit.Test;

import org.eclipse.jdt.testplugin.JavaTestPlugin;

import org.eclipse.core.runtime.FileLocator;

import org.eclipse.jdt.internal.ui.util.StringMatcher;

/**
 * Runs the 'tidy' command (e.g. from Cygwin) on HTML files in the workspace
 * and collects HTMLTidy's errors and warnings into /chkpiiResults/tidy.txt
 *
 * Note: Currently disabled.
 */
public class HTMLTidyTest {

	private static final int TIDY_TIMEOUT= 10000;
	private static final Pattern EXCLUSION_PATTERN_TABLE_SUMMARY= Pattern.compile("line \\d+ column \\d+ - Warning: <table> lacks \"summary\" attribute");

	private static final Pattern EXCLUSION_PATTERN_EMPTY_TAG= Pattern.compile("line \\d+ column \\d+ - Warning: trimming empty <\\w+>");
	private static final Pattern EXCLUSION_PATTERN_MALFORMED_URI= Pattern.compile("line \\d+ column \\d+ - Warning: <a> escaping malformed URI reference");
	private static final Pattern eXCLUSION_PATTERN_SCRIPT_TYPE= Pattern.compile("line \\d+ column \\d+ - Warning: <script> inserting \"type\" attribute");
	private static final Pattern eXCLUSION_PATTERN_IMG_LACKS_ALT= Pattern.compile("line \\d+ column \\d+ - Warning: <img> lacks \"alt\" attribute");

	private int fworkspacePathLength;
	private StringMatcher[] fIgnores;
	private Writer fTidyResults;

	/**
	 * Main method for manual testing of a file or folder selected in the workspace. To run, create
	 * a Java launch configuration with program argument<br>
	 * <code>"${resource_loc}"</code><br>
	 * (including double quotes!).
	 *
	 * @param args 1 argument: Absolute path to a file or folder
	 * @throws Exception if checking fails
	 */
	public static void main(String[] args) throws Exception {
		HTMLTidyTest test= new HTMLTidyTest();
		test.fIgnores= new StringMatcher[0];
		test.fTidyResults= new OutputStreamWriter(System.out);
		File file= new File(args[0]);
		if (file.isDirectory())
			test.checkFolder(file);
		else
			test.checkFile(file);
	}

	@Test
	public void testHTML() throws Exception {
		URL testBundleRoot= JavaTestPlugin.getDefault().getBundle().getEntry("/");
		URL testBundleFile= FileLocator.toFileURL(testBundleRoot);
		File hostWorkspace= new File(testBundleFile.getFile()).getParentFile().getParentFile().getParentFile();
		String workspacePath= hostWorkspace.getAbsolutePath();
		fworkspacePathLength= workspacePath.length();
		File chkpiiResults= new File(hostWorkspace, "chkpiiResults");
		File tidy= new File(chkpiiResults, "tidy.txt");
		fTidyResults= new OutputStreamWriter(new FileOutputStream(tidy), "UTF-8");
		String startTime= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
		fTidyResults.write(getClass().getName() + " started at " + startTime + "\n");
		fIgnores= getIgnores();

		try {
			boolean ok= true;
//			ok&= checkFolder(new File(hostWorkspace, "org.eclipse.jdt.doc.user"), tidyResults);
//			ok&= checkFolder(new File(hostWorkspace, "org.eclipse.jdt.doc.isv"), tidyResults);
//			ok&= checkFolder(new File(hostWorkspace, "org.eclipse.platform.doc.user"), tidyResults);
//			ok&= checkFolder(new File(hostWorkspace, "org.eclipse.platform.doc.isv"), tidyResults);
//			ok&= checkFolder(new File(hostWorkspace + "/org.eclipse.jdt.doc.isv/guide"));

			ok&= checkFolder(hostWorkspace);

			assertTrue("See " + tidy.getAbsolutePath(), ok);
		} finally {
			fTidyResults.flush();
			fTidyResults.close();
			fTidyResults= null;
		}
	}

	private StringMatcher[] getIgnores() throws IOException {
		ArrayList<StringMatcher> matchers= new ArrayList<>();
		InputStream is= getClass().getResourceAsStream("ignoreFiles.txt");
		try (BufferedReader reader= new BufferedReader(new InputStreamReader(is)))
		{
			while (reader.ready()) {
				String line= reader.readLine();
				if (line.length() > 0) {
					char first= line.charAt(0);
					if (line.endsWith("/*"))
						line= line.substring(0, line.length() - 2); // stops at directory during tree traversal
					if (first != '/' && first != '*') { // relative matches
						// emulate CHKPII specification:
						// matchers.add(new StringMatcher(fWorkspacePath + "/" + line, true, false));

						// emulate actual CHKPII implementation:
						matchers.add(new StringMatcher("*/" + line, true, false));
					}
					matchers.add(new StringMatcher(line, true, false));
				}
			}
		}
		return matchers.toArray(new StringMatcher[matchers.size()]);
	}

	private boolean isIgnored(File file) {
		String relativePath= file.getAbsolutePath().substring(fworkspacePathLength);
		relativePath= relativePath.replace('\\', '/');
		for (StringMatcher matcher : fIgnores) {
			if (matcher.match(relativePath))
				return true;
		}
		return false;
	}

	private boolean checkFolder(File folder) throws Exception {

		boolean success= true;
		for (File file : folder.listFiles()) {
			if (isIgnored(file)) {
//				System.out.println("Ignored: " + file.getAbsolutePath() + (file.isDirectory() ? "/*" : ""));
				continue;
			}
			if (isHTMLFile(file)) {
				success&= checkFile(file);
			} else if (file.isDirectory()) {
				success&= checkFolder(file);
			}
		}
		return success;
	}

	private boolean checkFile(File file) throws Exception {
		String filePath= file.getAbsolutePath();
		String command= "tidy -eq \"" + filePath + "\"";
		final Process process= Runtime.getRuntime().exec(command);
		long timeout= System.currentTimeMillis() + TIDY_TIMEOUT;

		BufferedReader tidyReader= new BufferedReader(new InputStreamReader(process.getErrorStream()));
		boolean available= tidyReader.ready();
		boolean first= true;
		while (available || System.currentTimeMillis() < timeout) {
			if (! available) {
				if (! isAlive(process))
					break;
				Thread.sleep(100);
			} else {
				while (available) {
					String line= tidyReader.readLine();
					if (isRelevant(line)) {
						if (first) {
							first= false;
							fTidyResults.write("\n--- " + filePath +'\n');
						}
						fTidyResults.write(line);
						fTidyResults.write('\n');
					}
					available= tidyReader.ready();
				}
			}
			available= tidyReader.ready();
			if (! available && ! isAlive(process))
				break;
		}
		fTidyResults.flush();
		try {
			return process.exitValue() == 0;
		} catch (IllegalThreadStateException e) {
			process.destroy();
			fTidyResults.write("Error: '" + command + "' killed after " + TIDY_TIMEOUT +" ms\n");
			return false;
		}
	}

	private static boolean isRelevant(String line) {
		return ! EXCLUSION_PATTERN_TABLE_SUMMARY.matcher(line).matches()
				&& ! EXCLUSION_PATTERN_EMPTY_TAG.matcher(line).matches()
				&& ! EXCLUSION_PATTERN_MALFORMED_URI.matcher(line).matches()
				&& ! eXCLUSION_PATTERN_SCRIPT_TYPE.matcher(line).matches()
				&& ! eXCLUSION_PATTERN_IMG_LACKS_ALT.matcher(line).matches()
				;
	}

	private static boolean isAlive(Process process) {
		try {
			process.exitValue();
		} catch (IllegalThreadStateException e) {
			return true;
		}
		return false;
	}

	private static boolean isHTMLFile(File file) {
		String name= file.getName();
		return (name.endsWith(".html") || name.endsWith(".htm")) && file.isFile();
	}
}
