package org.deft.language.java.parser;

import java.io.BufferedReader;
import java.io.CharArrayReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.nio.CharBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.antlr.runtime.ANTLRReaderStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.ParserRuleReturnScope;
import org.antlr.runtime.RecognitionException;
import org.deft.language.java.parser.antlradapter.DeftTreeAdaptor;
import org.deft.repository.ast.AstCombiner;
import org.deft.repository.ast.MergeConfiguration;
import org.deft.repository.ast.Token;
import org.deft.repository.ast.TokenNode;
import org.deft.repository.ast.TreeNode;
import org.deft.repository.ast.TreeNodeRoot;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;

//TODO I: modified
public class JavaParser {

	private static final String defaultSourceLevel = "1.5";

	private String sourceLevel = defaultSourceLevel;

	private static CharBuffer getCharBuffer(BufferedReader reader)
			throws IOException {
		CharBuffer cb = CharBuffer.allocate(1024 * 8);
		char[] buf = new char[1024];
		int length;
		int offset = 0;
		while ((length = reader.read(buf)) != -1) {
			cb.put(buf, 0, length);
			offset += length;
			if (offset == cb.capacity()) {
				CharBuffer tmp = CharBuffer.allocate(cb.capacity() * 2);
				cb.position(0);
				tmp.put(cb);
				cb = tmp;
			}
		}
		return CharBuffer.allocate(offset).put(cb.array(), 0, offset);
	}

	public TreeNodeRoot getAst(CharBuffer content, String type) {

		int kind;
		if (type.equals("K_EXPRESSION"))
			kind = ASTParser.K_EXPRESSION;
		else if (type.equals("K_STATEMENTS"))
			kind = ASTParser.K_STATEMENTS;
		else if (type.equals("K_CLASS_BODY_DECLARATIONS"))
			kind = ASTParser.K_CLASS_BODY_DECLARATIONS;
		else
			kind = ASTParser.K_COMPILATION_UNIT;

		// make position converter
		PositionConverter pConv = getPositionConverter(content);
		TreeNode javaTree = getJavaTree(content, pConv, kind);
		TreeNode commentTree = getCommentTree(content, pConv);

		// merge Java and comment tree
		MergeConfiguration config = new MergeConfiguration();
		AstCombiner combiner = new AstCombiner();
		combiner.combineTrees(javaTree, commentTree, config);

		// TreeNodeRoot root = visitor.getRoot();
		TreeNodeRoot root = new TreeNodeRoot(javaTree);
		return root;

	}

	public TreeNodeRoot getAst(CharBuffer content) {

		return getAst(content, "K_COMPILATION_UNIT");
	}

	private PositionConverter getPositionConverter(CharBuffer content) {
		PositionConverter pConv = null;
		BufferedReader reader = new BufferedReader(new CharArrayReader(content
				.array()));
		try {
			pConv = new PositionConverter(reader);
		} catch (IOException e) {
			throw new RuntimeException(
					"Error while trying to read input stream.", e);
		}
		return pConv;
	}

	private TreeNode getJavaTree(CharBuffer content, PositionConverter pConv,
			int kind) {
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		parser.setKind(kind);
		parser.setSource(content.array());

		// set options AFTER source, otherwise it is discarded
		Map<String, String> options = new HashMap<String, String>();
		// compiler source level is per default 1.3, change it
		options.put("org.eclipse.jdt.core.compiler.source", sourceLevel);
		parser.setCompilerOptions(options);

		ASTNode node = parser.createAST(null);

		AstConverterVisitor visitor = new AstConverterVisitor(content.array(),
				pConv);
		node.accept(visitor);
		// if there were parse errors
		// if (node.getProblems().length > 0) {
		// IProblem firstProblem = node.getProblems()[0];
		// String errorMsg = "Line [" + firstProblem.getSourceLineNumber() +
		// "]: " +
		// firstProblem.getMessage();
		// //throw exception
		// }
		TreeNode tree = visitor.getTree();
		return tree;
	}

	private TreeNode getCommentTree(CharBuffer content, PositionConverter pConv) {
		TreeNode commentTree = null;
		try {
			CStyleCommentsLexer commentLexer = new CStyleCommentsLexer(
					new ANTLRReaderStream(new CharArrayReader(content.array())));
			CommonTokenStream commentTokens = new CommonTokenStream(
					commentLexer);
			CStyleCommentsParser commentParser = new CStyleCommentsParser(
					commentTokens);
			commentParser.setTreeAdaptor(new DeftTreeAdaptor(
					new CStyleCommentsMapping(pConv)));
			ParserRuleReturnScope commentRet = commentParser.compilationunit();
			commentTree = (TreeNode) commentRet.getTree();
		} catch (IOException e) {

			e.printStackTrace();
		} catch (RecognitionException e) {

			e.printStackTrace();
		}
		return commentTree;
	}

	public TreeNodeRoot getAst(File file) {
		CharBuffer content = getContent(file);
		return getAst(content);
	}

	public static CharBuffer getContent(File file) {
		try {
			BufferedReader reader = new BufferedReader(new FileReader(file));
			return getCharBuffer(reader);
		} catch (IOException e) {
			e.printStackTrace();
		}

		return null;
	}

	public String getSourceLevel() {
		return sourceLevel;
	}

	/**
	 * Sets the source level to use. The default source level is "1.5".
	 * 
	 * @param sourceLevel
	 *            The source level to use.
	 */
	public void setSourceLevel(String sourceLevel) {
		this.sourceLevel = sourceLevel;
	}

	/**
	 * Temporary parser for strings with the following format. <br>
	 * PATH||XPATH<br>
	 * TYP||SOURCECODE/PATH||XPATH<br>
	 * <br>
	 * TYP = K_CLASS_BODY_DECLARATIONS, K_COMPILATION_UNIT, K_EXPRESSION,
	 * K_STATEMENTS<br>
	 * <br>
	 * XPATH = "/" can not be used too insert in a other tree.
	 * 
	 * @author Martin Heinzerling
	 */

	public static TreeNode get(String string) {

		String[] params = string.split("\\|\\|");

		if (params.length == 2) {
			return loadCompleteFileOrToken(string, params);
		} else if (params.length == 3) {
			return loadPartial(string, params);
		} else {
			return new TokenNode("Comment", new Token(1, 1, 1,
					"//Error while importing: " + string));
		}

	}

	private static TreeNode loadPartial(String string, String[] params) {
		String typ = params[0];
		if (isInvalidType(typ)) {
			return new TokenNode("Comment", new Token(1, 1, 1,
					"//Invalid parser typ: " + typ));
		}

		String data = params[1];
		CharBuffer content;
		File file = new File(data);
		if (file.exists()) {
			content = getContent(file);
		} else {
			content = CharBuffer.wrap(data.toCharArray());
		}

		JavaParser jp = new JavaParser();
		TreeNode root = jp.getAst(content, typ);

		List<TreeNode> tns = root.executeXPathQuery(params[2]);
		if (tns.size() > 0) {
			return tns.get(0);
		} else {
			return new TokenNode("Comment", new Token(1, 1, 1,
					"//Error while importing: " + string));
		}
	}

	private static TreeNode loadCompleteFileOrToken(String string,
			String[] params) {
		
		if (params[0].matches("[A-Za-z]{3,20}")) {
			TokenNode tn= new TokenNode(params[0], new Token(1, 1, 1,params[1]));
			return null;
		}

		JavaParser jp = new JavaParser();
		TreeNodeRoot root = jp.getAst(new File(params[0]));
		List<TreeNode> tns = root.executeXPathQuery(params[1]);
		if (tns.size() > 0) {
			return tns.get(0);
		} else {
			return new TokenNode("Comment", new Token(1, 1, 1,
					"//Error while importing: " + string));
		}
	}

	private static boolean isInvalidType(String type) {

		return !(type.equals("K_CLASS_BODY_DECLARATIONS")
				|| type.equals("K_COMPILATION_UNIT")
				|| type.equals("K_EXPRESSION") || type.equals("K_STATEMENTS"));
	}

}
