/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */
package net.sourceforge.pmd.lang.java.rule.basic;

import net.sourceforge.pmd.PropertySource;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement;
import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement;
import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
import net.sourceforge.pmd.lang.rule.properties.EnumeratedMultiProperty;

public class AvoidBranchingStatementAsLastInLoopRule extends AbstractJavaRule {

    public static final String CHECK_FOR = "for";
    public static final String CHECK_DO = "do";
    public static final String CHECK_WHILE = "while";

    private static final String[] ALL_LOOP_TYPES_LABELS = new String[] { CHECK_FOR, CHECK_DO, CHECK_WHILE };
    private static final String[] ALL_LOOP_TYPES_VALUES = ALL_LOOP_TYPES_LABELS;
    private static final int[] ALL_LOOP_TYPES_DEFAULTS = new int[] { 0, 1, 2 };

    public static final EnumeratedMultiProperty<String> CHECK_BREAK_LOOP_TYPES = new EnumeratedMultiProperty(
	    "checkBreakLoopTypes", "Check for break statements in loop types", ALL_LOOP_TYPES_LABELS,
	    ALL_LOOP_TYPES_VALUES, ALL_LOOP_TYPES_DEFAULTS, 1);
    public static final EnumeratedMultiProperty<String> CHECK_CONTINUE_LOOP_TYPES = new EnumeratedMultiProperty(
	    "checkContinueLoopTypes", "Check for continue statements in loop types", ALL_LOOP_TYPES_LABELS,
	    ALL_LOOP_TYPES_VALUES, ALL_LOOP_TYPES_DEFAULTS, 2);
    public static final EnumeratedMultiProperty<String> CHECK_RETURN_LOOP_TYPES = new EnumeratedMultiProperty(
	    "checkReturnLoopTypes", "Check for return statements in loop types", ALL_LOOP_TYPES_LABELS,
	    ALL_LOOP_TYPES_VALUES, ALL_LOOP_TYPES_DEFAULTS, 3);

    public AvoidBranchingStatementAsLastInLoopRule() {
	definePropertyDescriptor(CHECK_BREAK_LOOP_TYPES);
	definePropertyDescriptor(CHECK_CONTINUE_LOOP_TYPES);
	definePropertyDescriptor(CHECK_RETURN_LOOP_TYPES);

	addRuleChainVisit(ASTBreakStatement.class);
	addRuleChainVisit(ASTContinueStatement.class);
	addRuleChainVisit(ASTReturnStatement.class);
    }

    @Override
    public Object visit(ASTBreakStatement node, Object data) {
        // skip breaks, that are within a switch statement
        if (node.getNthParent(3) instanceof ASTSwitchStatement) {
            return data;
        }
	return check(CHECK_BREAK_LOOP_TYPES, node, data);
    }

    @Override
    public Object visit(ASTContinueStatement node, Object data) {
	return check(CHECK_CONTINUE_LOOP_TYPES, node, data);
    }

    @Override
    public Object visit(ASTReturnStatement node, Object data) {
	return check(CHECK_RETURN_LOOP_TYPES, node, data);
    }

    protected Object check(EnumeratedMultiProperty<String> property, Node node, Object data) {
	Node parent = node.getNthParent(5);
	if (parent instanceof ASTForStatement) {
	    if (hasPropertyValue(property, CHECK_FOR)) {
		super.addViolation(data, node);
	    }
	} else if (parent instanceof ASTWhileStatement) {
	    if (hasPropertyValue(property, CHECK_WHILE)) {
		super.addViolation(data, node);
	    }
	} else if (parent instanceof ASTDoStatement) {
	    if (hasPropertyValue(property, CHECK_DO)) {
		super.addViolation(data, node);
	    }
	}
	return data;
    }

    protected boolean hasPropertyValue(EnumeratedMultiProperty<String> property, String value) {
	final Object[] values = getProperty(property);
	for (int i = 0; i < values.length; i++) {
	    if (value.equals(values[i])) {
		return true;
	    }
	}
	return false;
    }

	public boolean checksNothing() {

		return getProperty(CHECK_BREAK_LOOP_TYPES).length == 0 &&
			getProperty(CHECK_CONTINUE_LOOP_TYPES).length == 0 &&
			getProperty(CHECK_RETURN_LOOP_TYPES).length == 0 ;
	}

	/**
	 * @see PropertySource#dysfunctionReason()
	 */
	@Override
	public String dysfunctionReason() {
		return checksNothing() ?
				"All loop types are ignored" :
				null;
	}
}
