package cn.bran.japid.compiler;

import japa.parser.JavaParser;
import japa.parser.ParseException;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.body.MethodDeclaration;
import japa.parser.ast.body.ModifierSet;
import japa.parser.ast.body.Parameter;
import japa.parser.ast.expr.Expression;
import japa.parser.ast.expr.MethodCallExpr;
import japa.parser.ast.type.Type;
import japa.parser.ast.visitor.VoidVisitorAdapter;

import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

public class JavaSyntaxTool {
	private static final String UTF_8 = "UTF-8";

	public static CompilationUnit parse(String src) throws ParseException {
		ByteArrayInputStream in;
		try {
			in = new ByteArrayInputStream(src.getBytes(UTF_8));
			CompilationUnit cu;
			cu = JavaParser.parse(in, UTF_8);
			return cu;
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	public static boolean isValid(String src) {
		try {
			CompilationUnit cu = parse(src);
			return true;
		} catch (ParseException e) {
			String m = e.getMessage();
			System.out.println(m.substring(0, m.indexOf('\n')));
			return false;
		}
	}

	/**
	 * 
	 * @author Bing Ran<bing_ran@hotmail.com>
	 * @deprecated use the original Parameter for complete control
	 */
	public static class Param {
		public String type, name;

		public Param(String type, String name) {
			this.type = type;
			this.name = name;
		}
	}

	private static final String classTempForParams = "class T { void t(%s) {} }";

	/**
	 * parse a line of text that is supposed to be parameter list for a method
	 * declaration.
	 * 
	 * TODO: the parameter annotation, modifiers, etc ignored. should do it!
	 * 
	 * @param line
	 * @return
	 */
	public static List<Parameter> parseParams(String line) {
		final List<Parameter> ret = new ArrayList<Parameter>();
		if (line == null || line.trim().length() == 0)
			return ret;
		String cl = String.format(classTempForParams, line);
		try {
			CompilationUnit cu = parse(cl);
			VoidVisitorAdapter visitor = new VoidVisitorAdapter() {
				@Override
				public void visit(Parameter p, Object arg) {
					ret.add(p);
				}
			};
			cu.accept(visitor, null);
		} catch (ParseException e) {
			throw new RuntimeException("the line does not seem to be a valid param declaration list: " + line);
		}
		return ret;
	}

	private static final String classTempForArgs = "class T {  {  foo(%s); } }";

	public static List<String> parseArgs(String line) {
		final List<String> ret = new ArrayList<String>();
		if (line == null || line.trim().length() == 0)
			return ret;

		String cl = String.format(classTempForArgs, line);
		try {
			CompilationUnit cu = parse(cl);
			VoidVisitorAdapter visitor = new VoidVisitorAdapter() {
				@Override
				public void visit(MethodCallExpr n, Object arg) {
					List<Expression> args = n.getArgs();
					// api issue: args can be null in case of empty arg list
					if (args != null)
						for (Expression e : args) {
							ret.add(e.toString());
						}
				}
			};
			cu.accept(visitor, null);
		} catch (ParseException e) {
			throw new RuntimeException("the line does not seem to be a valid arg list: " + line);
		}
		return ret;
	}

	public static boolean hasMethod(String javaSource, String string) throws ParseException {
		CompilationUnit cu = parse(javaSource);
		return hasMethod(cu, string);
	}

	public static boolean hasMethodInvocatioin(CompilationUnit cu, final String string) {
		if (string == null || string.trim().length() == 0)
			return false;

		final StringBuilder re = new StringBuilder();

		VoidVisitorAdapter visitor = new VoidVisitorAdapter() {
			@Override
			public void visit(MethodCallExpr n, Object arg) {
				if (string.equals(n.getName())) {
					re.append(1);
					return;
				} else {
					super.visit(n, arg);
				}
			}
		};
		cu.accept(visitor, null);
		if (re.length() == 0)
			return false;
		else
			return true;
	}

	public static boolean hasMethod(CompilationUnit cu, final String string) {
		final StringBuilder sb = new StringBuilder();

		VoidVisitorAdapter visitor = new VoidVisitorAdapter() {
			@Override
			public void visit(MethodDeclaration n, Object arg) {
				if (n.getName().equals(string)) {
					sb.append(1);
					return;
				}
			}
		};
		cu.accept(visitor, null);
		if (sb.length() == 0)
			return false;
		else
			return true;
	}

	public static boolean hasMethod(CompilationUnit cu, final String name, final int modis, 
			final String returnType, String paramList) {
		final StringBuilder sb = new StringBuilder();
		
		if (paramList == null)
			paramList = "";
		String formalParamList = addParamNamesPlaceHolder(paramList);
		
		final List<Parameter> params = parseParams(formalParamList);
		
		VoidVisitorAdapter visitor = new VoidVisitorAdapter() {
			@Override
			public void visit(MethodDeclaration n, Object arg) {
				if (n.getName().equals(name)) {
					int modifiers2 = n.getModifiers();
					if (modifiers2 == modis) {
						Type type = n.getType();
						if (type.toString().equals(returnType)) {
							List<Parameter> ps = n.getParameters();
							if (ps == null)
								ps = new ArrayList<Parameter>();
							if (paramsMatch(params, ps)) {
								sb.append(1);
								return;
							}
						}
					}
				}
			}
		};
		cu.accept(visitor, null);
		if (sb.length() == 0)
			return false;
		else
			return true;
	}
			/**
	 * 
	 * @param cu
	 * @param name
	 * @param modifiers
	 * @param returnType
	 * @param paramList, parameter type only: String, final int, etc
	 * @return
	 */
	public static boolean hasMethod(CompilationUnit cu, final String name, final String modifiers, 
			final String returnType, String paramList) {
		final int modis = parseModifiers(modifiers);
		return hasMethod(cu, name, modis, returnType, paramList);
	}

	/**
	 * @param paramList
	 * @return
	 */
	static String addParamNamesPlaceHolder(String paramList) {
		List<String> names = getNames(paramList);

		String formalParamList = "";
		for (int i = 0; i < names.size(); i++) {
			formalParamList += names.get(i) + " " + (char)('a' + i) + ",";
		}
		
		if (formalParamList.endsWith(","))
			formalParamList = formalParamList.substring(0, formalParamList.length() - 1);
		return formalParamList;
	}

	/**
	 * @param paramList
	 * @return
	 */
	private static List<String> getNames(String paramList) {
		paramList = paramList.replace(' ', ',');
		String[] pams = paramList.split(",");
		List<String> names = new ArrayList<String>();
		for (int i = 0; i < pams.length; i++) {
			String p = pams[i].trim();
			if (p.length() > 0)
				names.add(p);
		}
		return names;
	}

	protected static boolean paramsMatch(List<Parameter> params, List<Parameter> ps) {
		if (params == ps)
			return true;
		
		if ((params == null && ps != null) || (params != null && ps == null) )
			return false;
		
		if (params.size() != ps.size()) {
			return false;
		}
		
		for (int i = 0; i < params.size(); i++) {
			Parameter p1 = params.get(i);
			Parameter p2 = ps.get(i);
			if (!matchParams(p1, p2)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 
	 * @param p1
	 * @param p2
	 * @return true if the parameters signature matches. Parameter name does not matter.
	 */
	private static boolean matchParams(Parameter p1, Parameter p2) {
		if (p1.equals(p2)) 
			return true;

		if (p1.getModifiers() != p2.getModifiers())
			return false;
		
		if (!p1.getType().equals(p2.getType())) 
			return false;
		
		// TODO: compare annotations
		
		return true;
	}

	private static int parseModifiers(String modifiers) {
		int ret = 0;
		
		List<String> names = getNames(modifiers);
		
		for (String m: names) {
			if (m.equals("public")) {
				ret |= ModifierSet.PUBLIC;
			}
			else if (m.equals("private")) {
				ret |= ModifierSet.PRIVATE;
			}
			else if (m.equals("protected")) {
				ret |= ModifierSet.PROTECTED;
			}
			else if (m.equals("static")) {
				ret |= ModifierSet.STATIC;
			}
			else if (m.equals("final")) {
				ret |= ModifierSet.FINAL;
			}
			else if (m.equals("final")) {
				ret |= ModifierSet.FINAL;
			}
			else if (m.equals("synchronized")) {
				ret |= ModifierSet.SYNCHRONIZED;
			}
		}
		
		return ret;
	}

	/**
	 * 
	 * @param params
	 * 	 Type1 p1, Type2 p2...
	 * @return
	 *   Final Type1 p1, final Type2 p2...
	 */
	public static String addFinalToAllParams(String paramline) {
		if (paramline == null)
			return null;
		paramline  = paramline.trim();
		if (paramline.length() == 0)
			return "";
		List<Parameter> params = parseParams(paramline);
		String s = "";
		for (Parameter p: params) {
			s += "final " + p.getType() + " " + p.getId().getName() + ", ";
		}
		
		return s.substring(0, s.lastIndexOf(", "));
	}
	
	private static final String classTempForExpr = "class T {  {  f = %s ; } }";

	/**
	 * starting from the start of a string and find out the longest possible valid java expression
	 * 
	 * @param src
	 * @return the longest or "" in case of none
	 */
	public static String matchLongestPossibleExpr(String src) {
		if (src == null || src.trim().length() == 0)
			return "";
		
		src = src.trim();
		
		String expr = "";
		int i = src.length();
		for (; i > 0; i--) {
			expr = src.substring(0, i);
			String ss = String.format(classTempForExpr, expr);
			try {
				parse(ss);
				break;
			} catch (ParseException e) {
				continue;
			}
		}
		
		return expr.trim();
	}
}
