package jp.sourceforge.asclipse.as3;

import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import jp.sourceforge.asclipse.as3.AS3Parser.fileContents_return;
import jp.sourceforge.asclipse.as3.ParserUtil.AS3ParserException;
import jp.sourceforge.asclipse.as3.builder.TreeBuilder;
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.AS3MxmlEmbeddedRoot;
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.AS3Variable;
import jp.sourceforge.asclipse.as3.internal.DefaultAS3GlobalContext;
import jp.sourceforge.asclipse.as3.internal.element.ModifiableAS3Root;
import jp.sourceforge.asclipse.as3.tool.SmallSourceGenerator;

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

/**
 * 外部から利用するためのサンプルにするテストケース。
 * @author shin1ogawa
 */
public class ParserUtilTest {

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


	/**
	 * AS3Sample.asを解析し、構築されたツリーをassert。
	 * @throws IOException
	 * @throws RecognitionException
	 * @throws AS3ParserException 
	 */
	@Test
	public void sample01() throws IOException, RecognitionException, AS3ParserException {
		final String asFileName = "/Sample01.as";
		LOGGER.info("parse start: " + asFileName);
		InputStream input = ParserUtilTest.class.getResourceAsStream(asFileName);
		assertThat(input, notNullValue());

		AS3Root rootElement = ParserUtil.parse(input, asFileName);
		DebugUtil.debugAS3Element(rootElement, 0);
		assertThat(rootElement.hasParserError(), is(false));

		assertThat(rootElement, instanceOf(AS3Package.class));
		assertThat(rootElement.getChildCount(), is(3));
		assertThat(rootElement.getStartLine(), is(1));

		AS3Element e = rootElement.getChildren().get(0);
		assertThat(e, instanceOf(AS3Import.class));
		e = rootElement.getChildren().get(1);
		assertThat(e, instanceOf(AS3Import.class));

		e = rootElement.getChildren().get(2);
		assertThat(e, instanceOf(AS3Class.class));
		assertThat(e.getStartLine(), is(6));

		assertThat(e.getChildCount(), is(9));
		List<AS3Element> members = e.getChildren();
		Class<?>[] expectedChildClasses = new Class<?>[] {
			AS3Property.class,
			AS3ConstProperty.class,
			AS3Property.class,
			AS3ConstProperty.class,
			AS3Property.class,
			AS3ConstProperty.class,
			AS3Function.class,
			AS3Function.class,
			AS3Function.class
		};
		for (int i = 0; i < expectedChildClasses.length; i++) {
			assertThat(members.get(i), instanceOf(expectedChildClasses[i]));
		}
		// package com.shin1ogawa {
		//   [0:AS3Import]import mx.*;
		//   [1:AS3Import]import mx.collections.*;
		//   [2:AS3Class]public class Sample01 extends nya.Hoge implements Foo, fuga.Bar{
		//     [0:AS3Property]private var field01:Object;
		//     [1:AS3ConstProperty]private const field02:XML = <root></root>;
		//     [2:AS3Property]var field03:Number;
		//     [3:AS3ConstProperty]const field04:Number;
		//     [4:AS3Property]var field05:* = X;
		//     [5:AS3ConstProperty]const field06:String = "a";
		//     [6:AS3Function]public function Sample01() {...}
		//     [7:AS3Function]public function func01(arg0:XML, arg1:Function):void {}
		//     [8:AS3Function]function func02():void {}

		//     [6:AS3Function]public function Sample01() {...}

		AS3Property field01 = (AS3Property) members.get(0);
		assertThat(field01.isPublic(), is(false));
		assertThat(field01.isPrivate(), is(true));
		assertThat(field01.getStartLine(), is(7)); // modifierが7行目"var"が8行目

		AS3Function func01 = (AS3Function) members.get(6);
		assertThat(func01.isPublic(), is(true));
		assertThat(func01.isConstructor(), is(true));
		assertThat(func01.getStartLine(), is(17)); // modifierが17行目"function"が18行目

		LOGGER.info("parse end: " + asFileName);
	}

	/**
	 * ASlimTimer.asを解析し、構築されたツリーをassert。
	 * @throws IOException
	 * @throws RecognitionException
	 * @throws AS3ParserException 
	 */
	@Test
	public void aslimtimer() throws IOException, RecognitionException, AS3ParserException {
		final String asFileName = "/ASlimTimer.as";
		LOGGER.info("parse start: " + asFileName);
		InputStream input = ParserUtilTest.class.getResourceAsStream(asFileName);
		assertThat(input, notNullValue());
		AS3Root rootElement = ParserUtil.parse(input, asFileName);
		DebugUtil.debugAS3Element(rootElement, 0);
		assertThat(rootElement.hasParserError(), is(false));

		assertThat(rootElement, instanceOf(AS3MxmlEmbeddedRoot.class));
		assertThat(rootElement.getChildCount(), greaterThan(0));

		LOGGER.info("parse end: " + asFileName);
	}

	/**
	 * Sample04.asを解析し、構築されたツリーをassert。
	 * <p>namespace directiveを含んだソースの解析。</p>
	 * @throws IOException
	 * @throws RecognitionException
	 * @throws AS3ParserException 
	 */
	@Test
	public void sample04() throws IOException, RecognitionException, AS3ParserException {
		final String asFileName = "/Sample04.as";
		LOGGER.info("parse start: " + asFileName);
		InputStream input = ParserUtilTest.class.getResourceAsStream(asFileName);
		assertThat(input, notNullValue());
		AS3Root rootElement = ParserUtil.parse(input, asFileName);
		SmallSourceGenerator.generate(rootElement, System.out);
		DebugUtil.debugAS3Element(rootElement, 0);
		assertThat(rootElement.hasParserError(), is(false));

		assertThat(rootElement, instanceOf(AS3Package.class));
		assertThat(rootElement.getChildCount(), is(1));
		AS3Element clazz = rootElement.getChildren().get(0);
		Class<?>[] expectedChildClasses = new Class<?>[] {
			AS3NamespaceDirective.class,
			AS3NamespaceDirective.class,
			AS3Property.class,
			AS3Property.class,
			AS3Function.class,
			AS3Function.class
		};
		assertThat(clazz.getChildCount(), is(expectedChildClasses.length));
		List<AS3Element> members = clazz.getChildren();
		for (int i = 0; i < expectedChildClasses.length; i++) {
			assertThat(members.get(i), instanceOf(expectedChildClasses[i]));
		}

		// package
		//  [0]public class Sample04 {
		//    [0]public namespace mynamespace1;
		//    [1]public namespace mynamespace2 = "http://www.shin1ogawa.com/mynamespace2";
		//    [2]var field01:int = 0;
		//    [3]mynamespace1 var field01:int = 0;
		//    [4]public function func01():void {}
		//    [5]mynamespace2 function func01():void {}
		assertThat(((AS3Property) members.get(2)).hasNamespaceModifier(), is(false));
		assertThat(((AS3Property) members.get(3)).hasNamespaceModifier(), is(true));
		assertThat(((AS3Property) members.get(3)).getNamespaceModifier().getText(),
				is("mynamespace1"));
		assertThat(((AS3Function) members.get(4)).hasNamespaceModifier(), is(false));
		assertThat(((AS3Function) members.get(5)).hasNamespaceModifier(), is(true));
		assertThat(((AS3Function) members.get(5)).getNamespaceModifier().getText(),
				is("mynamespace2"));

		LOGGER.info("parse end: " + asFileName);
	}

	/**
	 * Sample05.asを解析し、構築されたツリーをassert。
	 * <p>namespace directiveを含んだソースの解析。</p>
	 * @throws IOException
	 * @throws RecognitionException
	 * @throws AS3ParserException 
	 */
	@Test
	public void sample05() throws IOException, RecognitionException, AS3ParserException {
		final String asFileName = "/Sample05.as";
		LOGGER.info("parse start: " + asFileName);
		InputStream input = ParserUtilTest.class.getResourceAsStream(asFileName);
		assertThat(input, notNullValue());
		AS3Root rootElement = ParserUtil.parse(input, asFileName);
		SmallSourceGenerator.generate(rootElement, System.out);
		DebugUtil.debugAS3Element(rootElement, 0);
		assertThat(rootElement.hasParserError(), is(false));

		assertThat(rootElement.getChildCount(), is(1));
		AS3Class clazz = (AS3Class) rootElement.getChildren().get(0);
		assertThat(clazz.getChildCount(), is(2));
		AS3Property property = (AS3Property) clazz.getChildren().get(0);
		assertThat(property.isDynamic(), is(false));
		assertThat(property.isInternal(), is(false));
		assertThat(property.isNative(), is(false));
		assertThat(property.isOverride(), is(false));
		assertThat(property.isPrivate(), is(false));
		assertThat(property.isProtected(), is(false));
		assertThat(property.isStatic(), is(true));
		assertThat(property.isPublic(), is(true));
		AS3Function function = (AS3Function) clazz.getChildren().get(1);
		assertThat(function.isDynamic(), is(false));
		assertThat(function.isInternal(), is(false));
		assertThat(function.isNative(), is(false));
		assertThat(function.isOverride(), is(false));
		assertThat(function.isPrivate(), is(false));
		assertThat(function.isProtected(), is(false));
		assertThat(property.isStatic(), is(true));
		assertThat(function.isPublic(), is(true));
		LOGGER.info("parse end: " + asFileName);
	}

	/**
	 * Sample10.asを解析し、構築されたツリーをassert。
	 * <p>複数のclassを含んだソースの解析。</p>
	 * @throws IOException
	 * @throws RecognitionException
	 * @throws AS3ParserException 
	 */
	@Test
	public void sample10() throws IOException, RecognitionException, AS3ParserException {
		final String asFileName = "/Sample10.as";
		LOGGER.info("parse start: " + asFileName);
		InputStream input = ParserUtilTest.class.getResourceAsStream(asFileName);
		assertThat(input, notNullValue());
		AS3Root rootElement = ParserUtil.parse(input, asFileName);
		SmallSourceGenerator.generate(rootElement, System.out);
		assertThat(rootElement, instanceOf(AS3MxmlEmbeddedRoot.class));
		DebugUtil.debugAS3Element(rootElement, 0);
		assertThat(rootElement.hasParserError(), is(false));
	}

	/**
	 * Sample11.asを解析し、構築されたツリーをassert。
	 * <p>class直下にいきなりexpressionが登場するケースの解析。</p>
	 * @throws IOException
	 * @throws RecognitionException
	 * @throws AS3ParserException 
	 */
	@Test
	public void sample11() throws IOException, RecognitionException, AS3ParserException {
		final String asFileName = "/Sample11.as";
		LOGGER.info("parse start: " + asFileName);
		InputStream input = ParserUtilTest.class.getResourceAsStream(asFileName);
		assertThat(input, notNullValue());
//		AS3Root rootElement = ParserUtil.parse(input, asFileName);
		AS3Root rootElement = parseAndDebugAndBuild(input, asFileName);
		assertThat(rootElement.hasParserError(), is(false));
	}

	/**
	 * Sample12.asを解析し、構築されたツリーをassert。
	 * <p>ファイル直下にいきなりmetadataが登場するケースの解析。</p>
	 * @throws IOException
	 * @throws RecognitionException
	 * @throws AS3ParserException 
	 */
	@Test
	public void sample12() throws IOException, RecognitionException, AS3ParserException {
		final String asFileName = "/Sample12.as";
		LOGGER.info("parse start: " + asFileName);
		InputStream input = ParserUtilTest.class.getResourceAsStream(asFileName);
//		assertThat(input, notNullValue());
//		AS3Root rootElement = ParserUtil.parse(input, asFileName);
		AS3Root rootElement = parseAndDebugAndBuild(input, asFileName);
		assertThat(rootElement.hasParserError(), is(false));
		assertThat(rootElement, instanceOf(AS3MxmlEmbeddedRoot.class));
	}

	/**
	 * Sample13.asを解析し、構築されたツリーをassert。
	 * <p>ファイル直下にいきなりfunctionが登場するケースの解析。</p>
	 * @throws IOException
	 * @throws RecognitionException
	 * @throws AS3ParserException 
	 */
	@Test
	public void sample13() throws IOException, RecognitionException, AS3ParserException {
		final String asFileName = "/Sample13.as";
		LOGGER.info("parse start: " + asFileName);
		InputStream input = ParserUtilTest.class.getResourceAsStream(asFileName);
//		assertThat(input, notNullValue());
//		AS3Root rootElement = ParserUtil.parse(input, asFileName);
		AS3Root rootElement = parseAndDebugAndBuild(input, asFileName);
		assertThat(rootElement.hasParserError(), is(false));
		assertThat(rootElement, instanceOf(AS3MxmlEmbeddedRoot.class));
	}

	/**
	 * Sample14.asを解析し、構築されたツリーをassert。
	 * <p>ファイル直下にいきなりpropertyが登場するケースの解析。</p>
	 * @throws IOException
	 * @throws RecognitionException
	 * @throws AS3ParserException 
	 */
	@Test
	public void sample14() throws IOException, RecognitionException, AS3ParserException {
		final String asFileName = "/Sample14.as";
		LOGGER.info("parse start: " + asFileName);
		InputStream input = ParserUtilTest.class.getResourceAsStream(asFileName);
//		assertThat(input, notNullValue());
//		AS3Root rootElement = ParserUtil.parse(input, asFileName);
		AS3Root rootElement = parseAndDebugAndBuild(input, asFileName);
		assertThat(rootElement.hasParserError(), is(false));
		assertThat(rootElement, instanceOf(AS3MxmlEmbeddedRoot.class));
	}

	/**
	 * Sample15.asを解析し、構築されたツリーをassert。
	 * <p>可変長引数のFunctionがあるパターンの解析。</p>
	 * @throws IOException
	 * @throws RecognitionException
	 * @throws AS3ParserException 
	 */
	@Test
	public void sample15() throws IOException, RecognitionException, AS3ParserException {
		final String asFileName = "/Sample15.as";
		LOGGER.info("parse start: " + asFileName);
		InputStream input = ParserUtilTest.class.getResourceAsStream(asFileName);
		assertThat(input, notNullValue());
		AS3Root rootElement = ParserUtil.parse(input, asFileName);
		SmallSourceGenerator.generate(rootElement, System.out);
		assertThat(rootElement.hasParserError(), is(false));
		AS3Element func = rootElement.getChildren().get(0).getChildren().get(0);
		assertThat(func, instanceOf(AS3Function.class));
		AS3Variable variable = ((AS3Function) func).getParameters().get(0);
		assertThat(variable.getIdentifier(), is("arg1"));
		assertThat(variable.hasEllipsis(), is(false));
		// ここからがキモ
		func = rootElement.getChildren().get(0).getChildren().get(1);
		assertThat(func, instanceOf(AS3Function.class));
		variable = ((AS3Function) func).getParameters().get(0);
		assertThat(variable.getIdentifier(), is("args"));
		assertThat(variable.hasEllipsis(), is(true));
	}

	private AS3Root parseAndDebugAndBuild(InputStream input, String resourceName)
			throws IOException, RecognitionException {
		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();
		CommonTree tree = (CommonTree) fileContents.getTree();
		DebugUtil.debug(tree);
		ModifiableAS3Root root = new TreeBuilder().build(new DefaultAS3GlobalContext(), tree);
		LOGGER.debug("AS3Element Tree");
		DebugUtil.debugAS3Element(root, 0);
		return root;
	}
}
