/**
 * Copyright 2014-2015 SHAF-WORK
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.shaf.core._helper_;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.shaf.core.util.Log;

/**
 * Help utilities for setting up and clean up test environment.
 * 
 * @author Mykola Galushka
 */
public class HelpUtils {

	/**
	 * Define a logger.
	 */
	private static final Log LOG = Log.forClass(HelpUtils.class);

	/**
	 * The test directory name.
	 */
	public static final String TEST_DIRECTORY_NAME = "shaf-test";

	/**
	 * Returns the test directory.
	 * 
	 * @return the {@code File} object representing test directory.
	 */
	public static final File getTestBaseDirectory() {
		return new File(System.getProperty("java.io.tmpdir"),
				TEST_DIRECTORY_NAME);
	}

	/**
	 * Creates the test directory for the specified class.
	 * 
	 * @param cls
	 *            the class for which the test directory is created.
	 * @return the {@code File} object representing the test directory for the
	 *         specified class.
	 * @throws IOException
	 *             if failed to create directory.
	 */
	public static final File createTestDirectoryForClass(final Class<?> cls)
			throws IOException {
		deleteTestDirectoryForClass(cls);

		File dir = new File(getTestBaseDirectory(), cls.getName());

		dir.mkdirs();

		CheckDirExists checker = new CheckDirExists(dir);
		ExecutorService es = Executors.newSingleThreadExecutor();
		es.submit(checker);
		es.shutdown();

		try {
			es.awaitTermination(10, TimeUnit.SECONDS);
			return dir;
		} catch (InterruptedException exc) {
			throw new IOException("Failed to create directory: " + dir);
		}
	}

	/**
	 * Returns the test directory for the specified class.
	 * 
	 * @param cls
	 *            the class for which the test directory is requested.
	 * @return the {@code File} object representing the test directory for the
	 *         specified class, or {@code null} if directory not exists.
	 * @throws IOException
	 *             if failed to find directory.
	 */
	public static final File getTestDirectoryForClass(final Class<?> cls)
			throws IOException {
		File dir = new File(getTestBaseDirectory(), cls.getName());

		CheckDirExists checker = new CheckDirExists(dir);
		ExecutorService es = Executors.newSingleThreadExecutor();
		es.submit(checker);
		es.shutdown();

		try {
			es.awaitTermination(10, TimeUnit.SECONDS);
			return dir;
		} catch (InterruptedException exc) {
			throw new IOException("Failed to find directory: " + dir);
		}
	}

	/**
	 * Removes the test directory for the specified class.
	 * 
	 * @param cls
	 *            the class for which the test directory is removed.
	 * @throws IOException
	 *             if failed to delete directory.
	 */
	public static final void deleteTestDirectoryForClass(final Class<?> cls)
			throws IOException {
		File dir = new File(getTestBaseDirectory(), cls.getName());

		if (dir.exists()) {
			delete(dir);
		}

		CheckDirNotExists checker = new CheckDirNotExists(dir);
		ExecutorService es = Executors.newSingleThreadExecutor();
		es.submit(checker);
		es.shutdown();

		try {
			es.awaitTermination(10, TimeUnit.SECONDS);
		} catch (InterruptedException exc) {
			throw new IOException("Failed to delete directory: " + dir);
		}
	}

	/**
	 * Deletes the specified directory.
	 * 
	 * @param dir
	 *            the ditectory to delete.
	 */
	private static final void delete(File dir) {
		File[] files = dir.listFiles();
		for (File file : files) {
			if (file.isDirectory()) {
				delete(file);
				file.delete();
			} else {
				file.delete();
			}
		}
		dir.delete();
	}

	/**
	 * Process which checks that the specified directory exists.
	 * 
	 * @author Mykola Galushka
	 */
	private static class CheckDirExists implements Runnable {

		/**
		 * The directory to check.
		 */
		private final File dir;

		/**
		 * Constructs a new directory checker.
		 * 
		 * @param dir
		 *            the directory to check.
		 */
		public CheckDirExists(final File dir) {
			this.dir = dir;
		}

		/**
		 * Runs checking.
		 */
		@Override
		public void run() {
			while (!this.dir.exists()) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException exc) {
					LOG.warn("The sleeping thread was interrupted.", exc);
					return;
				}
			}
		}
	}

	/**
	 * Process which checks that the specified directory not exists.
	 * 
	 * @author Mykola Galushka
	 */
	private static class CheckDirNotExists implements Runnable {

		/**
		 * The directory to check.
		 */
		private final File dir;

		/**
		 * Constructs a new directory checker.
		 * 
		 * @param dir
		 *            the directory to check.
		 */
		public CheckDirNotExists(final File dir) {
			this.dir = dir;
		}

		/**
		 * Runs checking.
		 */
		@Override
		public void run() {
			while (this.dir.exists()) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException exc) {
					LOG.warn("The sleeping thread was interrupted.", exc);
					return;
				}
			}
		}
	}
}
