package org.apache.catalina.loader;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.servlet.ServletContext;

import org.apache.catalina.Globals;
import org.apache.catalina.LifecycleException;

/**
 * @author Martin Kahr
 *
 */
public final class DevLoader extends WebappLoader {
	/** Information */
	public static final String INFO = "org.apache.catalina.loader.DevLoader/1.0";

	/**
	 * コンストラクタ
	 */
	public DevLoader() {
		super();
	}

	/**
	 * コンストラクタ
	 * @param parent 親クラスローダ
	 */
	public DevLoader(final ClassLoader parent) {
		super(parent);
	}

	/**
	 * @see org.apache.catalina.util.LifecycleBase#startInternal()
	 */
	@Override
	protected void startInternal() throws LifecycleException {
		log("Starting DevLoader");

		super.startInternal();

		ClassLoader cl = super.getClassLoader();
		if (!(cl instanceof WebappClassLoader)) {
			logError("Unable to install WebappClassLoader !");
			return;
		}

		StringBuilder classpath = new StringBuilder();
		for (final String entry : readWebClassPathEntries()) {
			File f = new File(entry);
			if (f.exists()) {
				if (f.isDirectory() && !entry.endsWith("/")) {
					f = new File(entry + "/");
				}
				URI url = f.toURI();
				try {
					((WebappClassLoader) cl).addURL(url.toURL());
				} catch (final MalformedURLException e) {
					logError(e.getMessage());
					continue;
				}
				classpath.append(f.toString()).append(File.pathSeparator);
				log("added " + url.toString());
			} else {
				logError(entry + " does not exist !");
			}
		}

		String cp = (String)getServletContext().getAttribute(Globals.CLASS_PATH_ATTR);
		if (cp != null) {
			for (final String token : cp.split(File.pathSeparator)) {
				// only on windows
				if (token.charAt(0) == '/' && token.charAt(2) == ':') {
					classpath.append(token.substring(1)).append(File.pathSeparator);
				} else {
					classpath.append(token).append(File.pathSeparator);
				}
			}
		}
		getServletContext().setAttribute(Globals.CLASS_PATH_ATTR, classpath.toString());

		log("JSPCompiler Classpath = " + classpath);
	}

	/**
	 * ログ出力
	 * @param msg 出力メッセージ
	 */
	protected void log(final String msg) {
		System.out.println("[DevLoader] " + msg);
	}

	/**
	 * エラー出力
	 * @param msg エラーメッセージ
	 */
	protected void logError(final String msg) {
		System.err.println("[DevLoader] Error: " + msg);
	}

	/**
	 * Webクラスパス読込
	 * @return Webクラスパス行
	 */
	protected List<String> readWebClassPathEntries() {
		File prjDir = getProjectRootDir();
		if (prjDir == null) {
			return Collections.emptyList();
		}
		log("projectdir=" + prjDir.getAbsolutePath());

		return loadWebClassPathFile(prjDir);
	}

	/**
	 * プロジェクトルート取得
	 * @return プロジェクトルート
	 */
	protected File getProjectRootDir() {
		File rootDir = getWebappDir();
		FileFilter filter = new LoaderFilter();
		while (rootDir != null) {
			File[] files = rootDir.listFiles(filter);
			if (files != null && 0 < files.length) {
				return files[0].getParentFile();
			}
			rootDir = rootDir.getParentFile();
		}
		return null;
	}

	/**
	 * Webクラスパスファイル読込
	 * @param prjDir プロジェクトディレクトリ
	 * @return 読込行リスト
	 */
	protected List<String> loadWebClassPathFile(final File prjDir) {
		File cpFile = new File(prjDir, LoaderFilter.WEB_CLASS_PATH_FILE);
		if (!cpFile.exists()) {
			return Collections.emptyList();
		}

		List<String> rc = new ArrayList<>();
		try (FileInputStream fis = new FileInputStream(cpFile)) {
			try (InputStreamReader reader = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
				try (BufferedReader lr = new BufferedReader(reader)) {
					String line = null;
					while ((line = lr.readLine()) != null) {
						line = line.replace('\\', '/');
						rc.add(line);
					}
				}
			}
		} catch (final IOException ex) {
			logError(ex.getMessage());
		}
		return rc;
	}

	/**
	 * サーブレットコンテキスト取得
	 * @return サーブレットコンテキスト
	 */
	protected ServletContext getServletContext() {
		return super.getContext().getServletContext();
	}

	/**
	 * Webアプリディレクトリ取得
	 * @return Webアプリディレクトリ
	 */
	protected File getWebappDir() {
		return new File(getServletContext().getRealPath("/"));
	}

	/**
	 * フィルタ
	 * @author Tadashi Nakayama
	 */
	static class LoaderFilter implements FileFilter {
		/** クラスパスファイル */
		static final String WEB_CLASS_PATH_FILE = ".#webclasspath";
		/** プラグインファイル */
		static final String TOMCAT_PLUGIN_FILE = ".tomcatplugin";

		/**
		 * @see java.io.FileFilter#accept(java.io.File)
		 */
		@Override
		public boolean accept(final File file) {
			return file.getName().equalsIgnoreCase(WEB_CLASS_PATH_FILE)
					|| file.getName().equalsIgnoreCase(TOMCAT_PLUGIN_FILE);
		}
	}
}
