package jp.sourceforge.asclipse.as3.tool;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import jp.sourceforge.asclipse.as3.ParserUtil;
import jp.sourceforge.asclipse.as3.element.AS3Class;
import jp.sourceforge.asclipse.as3.element.AS3ConstProperty;
import jp.sourceforge.asclipse.as3.element.AS3Element;
import jp.sourceforge.asclipse.as3.element.AS3Function;
import jp.sourceforge.asclipse.as3.element.AS3Import;
import jp.sourceforge.asclipse.as3.element.AS3Include;
import jp.sourceforge.asclipse.as3.element.AS3Interface;
import jp.sourceforge.asclipse.as3.element.AS3NamespaceDirective;
import jp.sourceforge.asclipse.as3.element.AS3Package;
import jp.sourceforge.asclipse.as3.element.AS3Property;
import jp.sourceforge.asclipse.as3.element.AS3Root;
import jp.sourceforge.asclipse.as3.element.AS3UseNamespaceDirective;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 解析したソースを小さい形式で出力する。
 * <p></p>
 * @author shin1ogawa
 */
public class SmallSourceGenerator {

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

	/** 出力先に出力対象が既にあった場合に上書きするか。 */
	public static boolean isOverride = true;

	/** インデントに使用する文字 */
	public static String indentString = "\t";


	private SmallSourceGenerator() {
	}


	private static final String FRAMEWORK_SOURCE_HOME = "as/frameworks/";

	/** ソースフォルダのリスト */
	public static String[] srcFolders = {
		"as/FP9",
		"as/AIR",
		"as/Flex3",
//		FRAMEWORK_SOURCE_HOME + "air/ApplicationUpdater/src/ApplicationUpdater",
//		FRAMEWORK_SOURCE_HOME + "air/ApplicationUpdater/src/ApplicationUpdaterDialogs",
//		FRAMEWORK_SOURCE_HOME + "air/ServiceMonitor/src/",
//		FRAMEWORK_SOURCE_HOME + "airframework/src/",
//		FRAMEWORK_SOURCE_HOME + "flash-integration/src/",
//		FRAMEWORK_SOURCE_HOME + "framework/src/",
//		FRAMEWORK_SOURCE_HOME + "haloclassic/src/",
//		FRAMEWORK_SOURCE_HOME + "rpc/src/",
//		FRAMEWORK_SOURCE_HOME + "utilities/src/",
			};


	/**
	 * @param args
	 */
	public static void main(String[] args) {
		int count = srcFolders.length;
		for (int i = 0; i < count; i++) {
			String srcFolder = srcFolders[i];
			try {
				isOverride = i < 3;
				parse(srcFolder);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * まとめて解析する。
	 * @param subFolder
	 * @throws IOException 
	 */
	public static void parse(String subFolder) throws IOException {
		File file = new File(subFolder);
		SmallSourceGenerator.convert(file, new File("src/main/resources/as"), getIgnoreList());
	}

	/**
	 * ignoreしたい対象ファイルのリストを返す。
	 * <p>デカイファイルとか解析できないとわかっているファイルとか。</p>
	 * @return ignoreしたい対象ファイルのファイル名のリスト
	 * @throws IOException
	 */
	private static String[] getIgnoreList() throws IOException {
		File file = new File("as/frameworks/ignoreas.txt");
		if (!file.exists()) {
			return new String[0];
		}
		List<String> list = new ArrayList<String>();
		BufferedReader reader = new BufferedReader(new FileReader(file));
		String line = null;
		while ((line = reader.readLine()) != null) {
			list.add(line);
		}
		reader.close();
		return list.toArray(new String[0]);
	}

	/**
	 * 特定のフォルダ配下のソースを解析し、functionの中身を削除して出力する。
	 * @param srcRootFolder 読み込み元フォルダ
	 * @param dstRootFolder 出力先フォルダ
	 * @param ignoreNames 無視するファイルのファイル名のリスト
	 */
	public static void convert(File srcRootFolder, File dstRootFolder, String[] ignoreNames) {
		if (!srcRootFolder.exists()) {
			throw new IllegalArgumentException("rootFolder is null.");
		}
		if (!dstRootFolder.exists()) {
			LOGGER.info("mkdirs: " + dstRootFolder.getAbsolutePath());
			dstRootFolder.mkdirs();
		}
		convertFolder(srcRootFolder.getAbsolutePath(), srcRootFolder, dstRootFolder
			.getAbsolutePath(), ignoreNames);
	}

	private static void convertFolder(String rootFolderName, File srcFolder,
			String dstRootFolderName, final String[] ignoreNames) {
		File[] listFiles = srcFolder.listFiles(new FileFilter() {

			public boolean accept(File pathname) {
				if (pathname.getAbsolutePath().endsWith(".svn")) {
					return false;
				}
				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()) {
				convertFolder(rootFolderName, file, dstRootFolderName, ignoreNames);
			} else {
				String dstFolderName =
						dstRootFolderName + "/"
								+ srcFolder.getAbsolutePath().replace(rootFolderName, "") + "/";
				File dstFolder = new File(dstFolderName);
				if (!dstFolder.exists()) {
					dstFolder.mkdirs();
					LOGGER.info("mkdirs: " + dstFolder.getAbsolutePath());
				}
				String dstFileName = dstFolderName + file.getName();
				File dstFile = new File(dstFileName);
				convertFile(file, dstFile, rootFolderName);
			}
		}
	}

	/**
	 * 特定のファイルを解析し、functionの中身を削除して出力する。
	 * @param srcFile 読み込むファイル
	 * @param dstFile 出力するファイル
	 * @param rootFolderName 読み込み元のファイルのルートフォルダ名
	 */
	public static void convertFile(File srcFile, File dstFile, String rootFolderName) {
		if (dstFile.exists()) {
			if (!isOverride) {
				LOGGER.info("already exists. skip: " + dstFile.getAbsolutePath());
				return;
			} else {
				LOGGER.info("already exists. overwrite: " + dstFile.getAbsolutePath());
			}
		}
		String resourceName = srcFile.getAbsolutePath().replace(rootFolderName, "");
		InputStream input = null;
		AS3Root element = null;
		try {
			input = new FileInputStream(srcFile);
			LOGGER.debug("start: " + srcFile.getAbsolutePath());
			element = ParserUtil.parse(input, resourceName);
		} catch (Exception e) {
//			e.printStackTrace();
			LOGGER.error("fail to parse: " + srcFile.getAbsolutePath());
			return;
		} finally {
			if (input != null) {
				try {
					input.close();
				} catch (IOException e) {
					// e.printStackTrace();
				}
			}
		}
		if (element.hasParserError()) {
			LOGGER.error("fail to parse: " + resourceName);
			return;
		}
		PrintStream out = null;
		try {
			if (!dstFile.exists()) {
				dstFile.createNewFile();
			}
			out = new PrintStream(dstFile);
			generate(element, out);
			LOGGER.info("converted successfly. -> " + dstFile.getAbsolutePath());
		} catch (FileNotFoundException e) {
			LOGGER.warn("fail to write: " + resourceName, e);
		} catch (IOException e) {
			LOGGER.warn("fail to write: " + resourceName, e);
		} finally {
			if (out != null) {
				out.close();
			}
		}
	}

	/**
	 * 解析済みのツリーからfunctionの中身を削除して出力する。
	 * @param element 解析済みのツリーのルート要素
	 * @param out 出力先
	 */
	public static void generate(AS3Element element, PrintStream out) {
		generate(element, out, 0);
	}

	private static void generate(AS3Element element, PrintStream out, int depth) {
		String indent = StringUtils.repeat(indentString, depth);
		if (element instanceof AS3Package || element instanceof AS3Class
				|| element instanceof AS3Interface) {
			out.print(indent);
			out.print(element.getTitle());
			out.println(" {");
		} else if (element instanceof AS3Function || element instanceof AS3Property
				|| element instanceof AS3ConstProperty || element instanceof AS3NamespaceDirective
				|| element instanceof AS3UseNamespaceDirective || element instanceof AS3Import
				|| element instanceof AS3Include) {
			// TODO import文は必要無いかもしれない。
			out.print(indent);
			out.print(element.getTitle());
			out.println(";");
		}
		List<AS3Element> children = element.getChildren();
		for (AS3Element child : children) {
			generate(child, out, depth + 1);
		}
		if (element instanceof AS3Package || element instanceof AS3Class
				|| element instanceof AS3Interface) {
			out.print(indent);
			out.println("}");
		}
	}

}
