package edu.umd.cs.findbugs.detect;

import static org.apache.bcel.Constants.DCMPG;
import static org.apache.bcel.Constants.DCMPL;
import static org.apache.bcel.Constants.FCMPG;
import static org.apache.bcel.Constants.FCMPL;
import static org.apache.bcel.Constants.IAND;
import static org.apache.bcel.Constants.IF_ACMPEQ;
import static org.apache.bcel.Constants.IF_ACMPNE;
import static org.apache.bcel.Constants.IF_ICMPEQ;
import static org.apache.bcel.Constants.IF_ICMPGE;
import static org.apache.bcel.Constants.IF_ICMPGT;
import static org.apache.bcel.Constants.IF_ICMPLE;
import static org.apache.bcel.Constants.IF_ICMPLT;
import static org.apache.bcel.Constants.IF_ICMPNE;
import static org.apache.bcel.Constants.INVOKEINTERFACE;
import static org.apache.bcel.Constants.INVOKEVIRTUAL;
import static org.apache.bcel.Constants.IOR;
import static org.apache.bcel.Constants.ISUB;
import static org.apache.bcel.Constants.IXOR;
import static org.apache.bcel.Constants.LAND;
import static org.apache.bcel.Constants.LCMP;
import static org.apache.bcel.Constants.LOR;
import static org.apache.bcel.Constants.LSUB;
import static org.apache.bcel.Constants.LXOR;
import static org.apache.bcel.Constants.POP;

import java.util.BitSet;
import java.util.Iterator;

import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;

import edu.umd.cs.findbugs.BugAnnotation;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.Detector;
import edu.umd.cs.findbugs.FieldAnnotation;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.CFGBuilderException;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.MethodUnprofitableException;
import edu.umd.cs.findbugs.ba.SignatureParser;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.vna.ValueNumber;
import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;
import edu.umd.cs.findbugs.ba.vna.ValueNumberSourceInfo;

public class FindSelfComparison2 implements Detector {

    private final BugReporter bugReporter;

    public FindSelfComparison2(BugReporter bugReporter) {
        this.bugReporter = bugReporter;
    }

    public void visitClassContext(ClassContext classContext) {
        Method[] methodList = classContext.getJavaClass().getMethods();

        for (Method method : methodList) {
            if (method.getCode() == null)
                continue;

            try {
                analyzeMethod(classContext, method);
            } catch (MethodUnprofitableException mue) {
                if (SystemProperties.getBoolean("unprofitable.debug")) // otherwise
                                                                       // don't
                                                                       // report
                    bugReporter.logError("skipping unprofitable method in " + getClass().getName());
            } catch (CFGBuilderException e) {
                bugReporter.logError("Detector " + this.getClass().getName() + " caught exception", e);
            } catch (DataflowAnalysisException e) {
                bugReporter.logError("Detector " + this.getClass().getName() + " caught exception", e);
            }
        }
    }

     static boolean booleanComparisonMethod(String methodName) {
        return methodName.equals("equals") ||methodName.equals("endsWith") || methodName.equals("startsWith")
                || methodName.equals("contains") || methodName.equals("equalsIgnoreCase");
    }

     static boolean comparatorMethod(String methodName) {
        return  methodName.equals("compareTo") || methodName.equals("compareToIgnoreCase");
    }
    private void analyzeMethod(ClassContext classContext, Method method) throws CFGBuilderException, DataflowAnalysisException {
        CFG cfg = classContext.getCFG(method);
        ValueNumberDataflow valueNumberDataflow = classContext.getValueNumberDataflow(method);
        ConstantPoolGen cpg = classContext.getConstantPoolGen();
        MethodGen methodGen = classContext.getMethodGen(method);
        String sourceFile = classContext.getJavaClass().getSourceFileName();

        for (Iterator<Location> i = cfg.locationIterator(); i.hasNext();) {
            Location location = i.next();

            Instruction ins = location.getHandle().getInstruction();
            switch (ins.getOpcode()) {
            case INVOKEVIRTUAL:
            case INVOKEINTERFACE:
                InvokeInstruction iins = (InvokeInstruction) ins;
                String invoking = iins.getName(cpg);
                if ( comparatorMethod(invoking) || booleanComparisonMethod(invoking) ) {
                    if (methodGen.getName().toLowerCase().indexOf("test") >= 0)
                        break;
                    if (methodGen.getClassName().toLowerCase().indexOf("test") >= 0)
                        break;
                    if (classContext.getJavaClass().getSuperclassName().toLowerCase().indexOf("test") >= 0)
                        break;
                    if (location.getHandle().getNext().getInstruction().getOpcode() == POP)
                        break;
                    String sig = iins.getSignature(cpg);

                    SignatureParser parser = new SignatureParser(sig);
                    if (parser.getNumParameters() == 1
                            && ( booleanComparisonMethod(invoking)  && sig.endsWith(";)Z") || comparatorMethod(invoking) && sig.endsWith(";)I")))
                        checkForSelfOperation(classContext, location, valueNumberDataflow, "COMPARISON", method, methodGen,
                                sourceFile);

                }
                break;

            case LOR:
            case LAND:
            case LXOR:
            case LSUB:
            case IOR:
            case IAND:
            case IXOR:
            case ISUB:
                checkForSelfOperation(classContext, location, valueNumberDataflow, "COMPUTATION", method, methodGen, sourceFile);
                break;
            case FCMPG:
            case DCMPG:
            case DCMPL:
            case FCMPL:
                break;
            case LCMP:
            case IF_ACMPEQ:
            case IF_ACMPNE:
            case IF_ICMPNE:
            case IF_ICMPEQ:
            case IF_ICMPGT:
            case IF_ICMPLE:
            case IF_ICMPLT:
            case IF_ICMPGE:
                checkForSelfOperation(classContext, location, valueNumberDataflow, "COMPARISON", method, methodGen, sourceFile);

            }

        }
    }

    /**
     * @param classContext
     *            TODO
     * @param location
     * @param method
     *            TODO
     * @param methodGen
     *            TODO
     * @param sourceFile
     *            TODO
     * @param string
     * @throws DataflowAnalysisException
     */
    private void checkForSelfOperation(ClassContext classContext, Location location, ValueNumberDataflow valueNumberDataflow,
            String op, Method method, MethodGen methodGen, String sourceFile) throws DataflowAnalysisException {
        ValueNumberFrame frame = valueNumberDataflow.getFactAtLocation(location);
        if (!frame.isValid())
            return;
        Instruction ins = location.getHandle().getInstruction();
        int opcode = ins.getOpcode();
        int offset = 1;
        if (opcode == LCMP || opcode == LXOR || opcode == LAND || opcode == LOR || opcode == LSUB)
            offset = 2;
        ValueNumber v0 = frame.getStackValue(0);
        ValueNumber v1 = frame.getStackValue(offset);
        if (!v1.equals(v0))
            return;
        if (v0.hasFlag(ValueNumber.CONSTANT_CLASS_OBJECT) || v0.hasFlag(ValueNumber.CONSTANT_VALUE))
            return;

        int priority = HIGH_PRIORITY;
        if (opcode == ISUB || opcode == LSUB || opcode == INVOKEINTERFACE || opcode == INVOKEVIRTUAL)
            priority = NORMAL_PRIORITY;
        XField field = ValueNumberSourceInfo.findXFieldFromValueNumber(method, location, v0, frame);
        BugAnnotation annotation;
        String prefix;
        if (field != null) {
            if (field.isVolatile())
                return;
            if (true)
                return; // don't report these; too many false positives
            annotation = FieldAnnotation.fromXField(field);
            prefix = "SA_FIELD_SELF_";
        } else {
            annotation = ValueNumberSourceInfo.findLocalAnnotationFromValueNumber(method, location, v0, frame);
            prefix = "SA_LOCAL_SELF_";
            if (opcode == ISUB)
                return; // only report this if simple detector reports it
        }
        if (annotation == null)
            return;
        SourceLineAnnotation sourceLine = SourceLineAnnotation.fromVisitedInstruction(classContext, methodGen, sourceFile,
                location.getHandle());
        int line = sourceLine.getStartLine();
        BitSet occursMultipleTimes = classContext.linesMentionedMultipleTimes(method);
        if (line > 0 && occursMultipleTimes.get(line))
            return;
        BugInstance bug = new BugInstance(this, prefix + op, priority).addClassAndMethod(methodGen, sourceFile);
        if (ins instanceof InvokeInstruction)
            bug.addCalledMethod(classContext.getConstantPoolGen(), (InvokeInstruction) ins);

        bug.add(annotation)
                .addSourceLine(classContext, methodGen, sourceFile, location.getHandle());
        bugReporter.reportBug(bug);
    }

    public void report() {
    }

}
