package com.cflint.plugins.core;

import java.util.Map;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import net.htmlparser.jericho.Element;

import cfml.parsing.cfscript.CFAssignmentExpression;
import cfml.parsing.cfscript.CFExpression;
import cfml.parsing.cfscript.CFIdentifier;
import cfml.parsing.cfscript.CFFullVarExpression;
import cfml.parsing.cfscript.CFVarDeclExpression;
import cfml.parsing.cfscript.script.CFFuncDeclStatement;
import cfml.parsing.cfscript.script.CFFunctionParameter;
import cfml.parsing.cfscript.script.CFScriptStatement;

import com.cflint.BugInfo;
import com.cflint.BugList;
import com.cflint.plugins.CFLintScannerAdapter;
import com.cflint.plugins.Context;

public class UnusedArgumentChecker extends CFLintScannerAdapter {
	final String severity = "INFO";

	protected Map<String, Boolean> methodArguments = new HashMap<String, Boolean>();
	protected Map<String, Integer> argumentLineNo = new HashMap<String, Integer>();

	@Override
	public void element(final Element element, final Context context, final BugList bugs) {
		if (element.getName().equals("cfargument")) {
			final String name = element.getAttributeValue("name");
			methodArguments.put(name, false);
			setArgumentLineNo(name, context.startLine());
		}
	}

	@Override
	public void expression(final CFScriptStatement expression, final Context context, final BugList bugs) {
		if (expression instanceof CFFuncDeclStatement) {
			final CFFuncDeclStatement function = (CFFuncDeclStatement) expression;
			for (final CFFunctionParameter argument : function.getFormals()) {
				final String name = argument.getName();
				methodArguments.put(name, false);
				setArgumentLineNo(name, function.getLine()); // close enough?
			}
		}
	}

	protected void setArgumentLineNo(final String argument, final Integer lineNo) {
		if (argumentLineNo.get(argument) == null) {
			argumentLineNo.put(argument, lineNo);
		}
	}

	@Override
	public void expression(final CFExpression expression, final Context context, final BugList bugs) {
		if (expression instanceof CFFullVarExpression) {
			CFExpression variable = ((CFFullVarExpression) expression).getExpressions().get(0);
			if (variable instanceof CFIdentifier) {
				String name = ((CFIdentifier) variable).getName();
				if (methodArguments.get(name) != null) {
					methodArguments.put(name, true);
				}
			}
		}
		else if (expression instanceof CFIdentifier) {
			String name = ((CFIdentifier) expression).getName();
			if (methodArguments.get(name) != null) {
				methodArguments.put(name, true);
			}
		}
	}

	@Override	
	public void startFunction(Context context, BugList bugs) {
		methodArguments.clear();
	}

	@Override	
	public void endFunction(Context context, BugList bugs) {
		// sort by line number
		final List<BugInfo> presortbugs = new ArrayList<BugInfo>();
		for (Map.Entry<String, Boolean> method : methodArguments.entrySet()) {
			Boolean used = method.getValue();
	    	if (!used) {
	    		final String name = method.getKey();
	    		final Integer lineNo = argumentLineNo.get(name);
	    		presortbugs.add(new BugInfo.BugInfoBuilder().setLine(lineNo).setMessageCode("UNUSED_METHOD_ARGUMENT")
					.setSeverity(severity).setFilename(context.getFilename())
					.setMessage("Argument " + name + " is not used in function " + context.getFunctionName() + ", consider removing it.")
					.build());
	    	}
	    }
		// Sort the bugs by line/col before adding to the list of bugs.
		Collections.sort(presortbugs);
		for(BugInfo bugInfo : presortbugs ){
			bugs.add(bugInfo);
		}
	}

}

@@ -1657,25 +1657,83 @@
 nt.getName()
-;
+.toLowerCase(); // CF variable names are not case sensitive
 %0A%09%09%09%09methodA
@@ -2006,32 +2006,375 @@
 ineNo);%0A%09%09%7D%0A%09%7D%0A%0A
+%09protected void useIdentifier(CFIdentifier identifier) %7B%0A%09%09String name = identifier.getName().toLowerCase();%0A%09%09if (name.equals(%22arguments%22)) %7B%0A%09%09%09name = identifier.Decompile(0).toLowerCase().replace(%22arguments.%22, %22%22); // TODO better way of doing this?%0A%09%09%7D%0A%09%09if (methodArguments.get(name) != null) %7B%0A%09%09%09methodArguments.put(name, true);%0A%09%09%7D%0A%09%7D%0A%0A
 %09@Override%0A%09publ
@@ -2657,147 +2657,49 @@
 %09%09%09%09
-String name = ((CFIdentifier) variable).getName();%0A%09%09%09%09if (methodArguments.get(name) != null) %7B%0A%09%09%09%09%09methodArguments.put(name, true);%0A%09%09%09%09%7D
+useIdentifier((CFIdentifier) expression);
 %0A%09%09%09
@@ -2756,30 +2756,29 @@
 r) %7B%0A%09%09%09
-String name = 
+useIdentifier
 ((CFIden
@@ -2800,105 +2800,11 @@
 ion)
-.getName();%0A%09%09%09if (methodArguments.get(name) != null) %7B%0A%09%09%09%09methodArguments.put(name, true);%0A%09%09%09%7D
+;%09%09
 %0A%09%09%7D

Add check for arguments scope and turn names to lower case