package org.rubypeople.rdt.core.parser.warnings;

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

import org.jruby.ast.ArgsNode;
import org.jruby.ast.BlockNode;
import org.jruby.ast.ClassNode;
import org.jruby.ast.DefnNode;
import org.jruby.ast.DefsNode;
import org.jruby.ast.HashNode;
import org.jruby.ast.IfNode;
import org.jruby.ast.ModuleNode;
import org.jruby.ast.Node;
import org.jruby.ast.RescueBodyNode;
import org.jruby.ast.RootNode;
import org.jruby.ast.SClassNode;
import org.jruby.ast.WhenNode;
import org.jruby.lexer.yacc.ISourcePosition;
import org.rubypeople.rdt.core.RubyCore;
import org.rubypeople.rdt.core.compiler.CategorizedProblem;
import org.rubypeople.rdt.core.compiler.IProblem;
import org.rubypeople.rdt.internal.core.parser.Error;
import org.rubypeople.rdt.internal.core.parser.InOrderVisitor;
import org.rubypeople.rdt.internal.core.parser.Warning;
import org.rubypeople.rdt.internal.core.util.ASTUtil;

public abstract class RubyLintVisitor extends InOrderVisitor
{

	private String contents;
	protected Map<String, String> fOptions;
	private List<CategorizedProblem> problems;

	public RubyLintVisitor(String contents)
	{
		this(RubyCore.getOptions(), contents);
	}

	public RubyLintVisitor(Map<String, String> options, String contents)
	{
		this.problems = new ArrayList<CategorizedProblem>();
		this.contents = contents;
		this.fOptions = options;
	}

	protected String getSource(Node node)
	{
		return ASTUtil.getSource(contents, node);
	}

	protected String getSource(int start, int end)
	{
		if (start > end)
		{
			int temp = end;
			end = start;
			start = temp;
		}
		if (contents.length() < end)
			end = contents.length();
		if (start < 0)
			start = 0;
		return new String(contents.substring(start, end));
	}

	public List<CategorizedProblem> getProblems()
	{
		return problems;
	}

	public boolean isIgnored()
	{
		String value = getSeverity();
		if (value != null && value.equals(RubyCore.IGNORE))
			return true;
		return false;
	}

	protected void createProblem(ISourcePosition position, String message)
	{
		String value = getSeverity();
		if (value != null && value.equals(RubyCore.IGNORE))
			return;
		CategorizedProblem problem;
		if (value != null && value.equals(RubyCore.ERROR))
			problem = new Error(position, message, getProblemID());
		else
			problem = new Warning(position, message, getProblemID());
		problems.add(problem);
	}

	protected String getSeverity()
	{
		return (String) fOptions.get(getOptionKey());
	}

	@Override
	protected Object visitNode(Node iVisited)
	{
		return null;
	}

	/**
	 * The key used to store the error/warning severity option.
	 * 
	 * @return a String key
	 */
	abstract protected String getOptionKey();

	/**
	 * Meant to be overridden by classes needing to perform some action when a class definition was exited.
	 * 
	 * @param iVisited
	 */
	public void exitClassNode(ClassNode iVisited)
	{
	}

	/**
	 * To be overridden by subclasses who need to run particular behavior/code when exiting a method definition.
	 * 
	 * @param iVisited
	 */
	public void exitDefnNode(DefnNode iVisited)
	{
	}

	/**
	 * To be overridden by subclasses who need to run particular behavior/code when exiting a singleton method
	 * definition.
	 * 
	 * @param iVisited
	 */
	public void exitIfNode(IfNode iVisited)
	{
	}

	/**
	 * To be overriden by subclasses who need to run particular behavior/code when exiting a singleton method
	 * definition.
	 * 
	 * @param iVisited
	 */
	public void exitBlockNode(BlockNode iVisited)
	{
	}

	/**
	 * To be overridden by subclasses who need to run particular behavior/code when exiting a singleton method
	 * definition.
	 * 
	 * @param iVisited
	 */
	public void exitDefsNode(DefsNode iVisited)
	{
	}

	/**
	 * To be overridden by subclasses who need to run particular behavior/code when exiting a singleton method
	 * definition.
	 * 
	 * @param iVisited
	 */
	public void exitModuleNode(ModuleNode iVisited)
	{
	}

	/**
	 * To be overridden by subclasses who need to run particular behavior/code when exiting a singleton method
	 * definition.
	 * 
	 * @param iVisited
	 */
	public void exitWhenNode(WhenNode iVisited)
	{
	}

	/**
	 * To be overridden by subclasses who need to run particular behavior/code when exiting a singleton method
	 * definition.
	 * 
	 * @param iVisited
	 */
	public void exitSClassNode(SClassNode iVisited)
	{
	}

	/**
	 * To be overridden by subclasses who need to run particular behavior/code when exiting a method signature.
	 * 
	 * @param iVisited
	 */
	public void exitArgsNode(ArgsNode iVisited)
	{
	}

	/**
	 * To be overridden by subclasses who need to run particular behavior/code when exiting rescue body.
	 * 
	 * @param iVisited
	 */
	public void exitRescueBodyNode(RescueBodyNode iVisited)
	{
	}

	/**
	 * To be overridden by subclasses who need to run particular behavior/code when exiting a hash node.
	 * 
	 * @param iVisited
	 */
	public void exitHashNode(HashNode iVisited)
	{
	}

	/**
	 * To be overridden by subclasses who need to run particular behavior/code when exiting the AST.
	 * 
	 * @param iVisited
	 */
	public void exitRootNode(RootNode iVisited)
	{
	}

	protected int getProblemID()
	{
		return IProblem.Uncategorized;
	}

	@Override
	public Object visitArgsNode(ArgsNode iVisited)
	{
		Object ins = super.visitArgsNode(iVisited);
		exitArgsNode(iVisited);
		return ins;
	}

	@Override
	public Object visitBlockNode(BlockNode iVisited)
	{
		Object ins = super.visitBlockNode(iVisited);
		exitBlockNode(iVisited);
		return ins;
	}

	@Override
	public Object visitClassNode(ClassNode iVisited)
	{
		Object ins = super.visitClassNode(iVisited);
		exitClassNode(iVisited);
		return ins;
	}

	@Override
	public Object visitDefnNode(DefnNode iVisited)
	{
		Object ins = super.visitDefnNode(iVisited);
		exitDefnNode(iVisited);
		return ins;
	}

	@Override
	public Object visitDefsNode(DefsNode iVisited)
	{
		Object ins = super.visitDefsNode(iVisited);
		exitDefsNode(iVisited);
		return ins;
	}

	@Override
	public Object visitHashNode(HashNode iVisited)
	{
		Object ins = super.visitHashNode(iVisited);
		exitHashNode(iVisited);
		return ins;
	}

	@Override
	public Object visitIfNode(IfNode iVisited)
	{
		Object ins = super.visitIfNode(iVisited);
		exitIfNode(iVisited);
		return ins;
	}

	@Override
	public Object visitModuleNode(ModuleNode iVisited)
	{
		Object ins = super.visitModuleNode(iVisited);
		exitModuleNode(iVisited);
		return ins;
	}

	@Override
	public Object visitRescueBodyNode(RescueBodyNode iVisited)
	{
		Object ins = super.visitRescueBodyNode(iVisited);
		exitRescueBodyNode(iVisited);
		return ins;
	}

	@Override
	public Object visitRootNode(RootNode iVisited)
	{
		problems.clear();
		Object ret = super.visitRootNode(iVisited);
		exitRootNode(iVisited);
		return ret;
	}

	@Override
	public Object visitSClassNode(SClassNode iVisited)
	{
		Object ins = super.visitSClassNode(iVisited);
		exitSClassNode(iVisited);
		return ins;
	}

	@Override
	public Object visitWhenNode(WhenNode iVisited)
	{
		Object ins = super.visitWhenNode(iVisited);
		exitWhenNode(iVisited);
		return ins;
	}

}
