package jp.sourceforge.asclipse.as3;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jp.sourceforge.asclipse.as3.AS3Parser.fileContents_return;
import jp.sourceforge.asclipse.as3.builder.TreeBuilder;
import jp.sourceforge.asclipse.as3.element.AS3MxmlEmbeddedRoot;
import jp.sourceforge.asclipse.as3.element.AS3Package;
import jp.sourceforge.asclipse.as3.element.AS3Root;
import jp.sourceforge.asclipse.as3.internal.DefaultAS3GlobalContext;
import jp.sourceforge.asclipse.as3.internal.element.ModifiableAS3Root;

import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.CommonTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author shin1ogawa
 */
public class ParserUtil {

	private static final Logger LOGGER = LoggerFactory.getLogger(ParserUtil.class);

	private static final IAS3GlobalContext DEFAULT_GLOBAL_CONTEXT = new DefaultAS3GlobalContext();


	private ParserUtil() {
	}

	/**
	 * テスト時専用の、コンテキストを意識しないparse()用です。
	 * <p>プロダクトコードでは使用しないで下さい。</p>
	 * @param input
	 * @param resourceName
	 * @return アウトライン表示用に解析し、構築したツリーのルート要素。
	 * @throws AS3ParserException
	 */
	@Deprecated
	public static AS3Root parse(InputStream input, String resourceName) throws AS3ParserException {
		return parse(DEFAULT_GLOBAL_CONTEXT, input, resourceName);
	}

	/**
	 * {@link InputStream}からの入力をアウトライン表示用に解析し、ツリーを構築する。
	 * <p>{@link AS3Package}か、MXMLから外部化したファイルの場合は{@link AS3MxmlEmbeddedRoot}が
	 * ルート要素として返されるはず。</p>
	 * @param globalContext 
	 * @param input
	 * @param resourceName リソース名
	 * @return アウトライン表示用に解析し、構築したツリーのルート要素。
	 * @throws AS3ParserException 解析中に問題が発生した時。
	 */
	public static AS3Root parse(IAS3GlobalContext globalContext, InputStream input,
			String resourceName) throws AS3ParserException {
		try {
			ANTLRInputStream antlrIs;
			antlrIs = new ANTLRInputStream(input);
			AS3Lexer lexer = new AS3Lexer(antlrIs);
			CommonTokenStream tokenStream = new CommonTokenStream(lexer);
			AS3Parser parser = new AS3Parser(tokenStream);
			parser.setBacktrackingLevel(0);
			fileContents_return fileContents = parser.fileContents();
			if (fileContents == null) {
				throw new AS3ParserException("fail to parse. resourseName=" + resourceName);
			}
			CommonTree tree = (CommonTree) fileContents.getTree();
			if (tree == null) {
				throw new AS3ParserException("fail to parse. resourseName=" + resourceName);
			}
			ModifiableAS3Root root = null;
			try {
				root = new TreeBuilder().build(globalContext, tree);
			} catch (Exception ex) {
				//
			}
			if (root == null) {
				throw new AS3ParserException("fail to build. resourseName=" + resourceName);
			}
			if (resourceName != null) {
				root.setResourceName(resourceName);
			}
			List<Exception> parseErrors = parser.getParseErrors();
			if (parseErrors != null) {
				root.setParserErrors(parseErrors);
			}
			return root;
		} catch (IOException e) {
			LOGGER.warn("parse error.", e);
			throw new AS3ParserException(e);
		} catch (RecognitionException e) {
			LOGGER.warn("parse error.", e);
			throw new AS3ParserException(e);
		}
	}

	/**
	 * {@link InputStream}からの入力をアウトライン表示用に解析し、ツリーを構築する。
	 * <p>{@link AS3Package}か、MXMLから外部化したファイルの場合は{@link AS3MxmlEmbeddedRoot}が
	 * ルート要素として返されるはず。</p>
	 * @param input
	 * @return アウトライン表示用に解析し、構築したツリーのルート要素。
	 * @throws AS3ParserException 解析中に問題が発生した時。
	 */
	@Deprecated
	public static AS3Root parse(InputStream input) throws AS3ParserException {
		return parse(input, input.toString());
	}

	/**
	 * ソースフォルダのルート配下にあるファイルを全て解析する。
	 * <p>とりあえず{@literal .as}を対象にする。</p>
	 * @param folder ソースフォルダのルート
	 * @param ignoreNames 無視するファイル名のリスト
	 * @return ファイルを解析し、構築できたルート要素を値に持つ{@link Map}
	 */
	public static Map<File, AS3Root> parseAll(File folder, String[] ignoreNames) {
		if (!folder.isDirectory()) {
			throw new IllegalArgumentException();
		}
		Map<File, AS3Root> map = new HashMap<File, AS3Root>();
		parseChildren(folder, map, ignoreNames);
		return map;
	}

	private static void parseChildren(File parent, Map<File, AS3Root> map,
			final String[] ignoreNames) {
		File[] listFiles = parent.listFiles(new FileFilter() {

			public boolean accept(File pathname) {
				if (ignoreNames != null) {
					for (String ignoreName : ignoreNames) {
						if (pathname.getAbsolutePath().endsWith(ignoreName)) {
							return false;
						}
					}
				}
				return pathname.isDirectory() || pathname.getName().endsWith(".as");
			}
		});
		for (File file : listFiles) {
			if (file.isDirectory()) {
				parseChildren(file, map, ignoreNames);
			} else {
				FileInputStream input = null;
				try {
					input = new FileInputStream(file);
					LOGGER.debug("parse start: " + file.getAbsolutePath());
					map.put(file, parse(input, file.getName()));
				} catch (AS3ParserException ex) {
					//
				} catch (Exception ex) {
					//
				} finally {
					if (input != null) {
						try {
							input.close();
						} catch (IOException e) {
							//
						}
					}
				}
			}
		}
	}


	/**
	 * 解析処理中に問題が発生した時に、問題をラップするException。
	 * @author shin1ogawa
	 */
	@SuppressWarnings("serial")
	public static class AS3ParserException extends Exception {

		/**
		 * Constructor.
		 * @category constructor
		 */
		public AS3ParserException() {
			super();
		}

		/**
		 * Constructor.
		 * @param message
		 * @param cause
		 * @category constructor
		 */
		public AS3ParserException(String message, Throwable cause) {
			super(message, cause);
		}

		/**
		 * Constructor.
		 * @param message
		 * @category constructor
		 */
		public AS3ParserException(String message) {
			super(message);
		}

		/**
		 * Constructor.
		 * @param cause
		 * @category constructor
		 */
		public AS3ParserException(Throwable cause) {
			super(cause);
		}
	}
}
