/*
 * fb-contrib - Auxiliary detectors for Java programs
 * Copyright (C) 2005-2016 Dave Brosius
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.mebigfatguy.fbcontrib.detect;

import java.util.ArrayDeque;
import java.util.Deque;

import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.Type;

import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.ToString;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.ba.ClassContext;

public class BuryingLogic extends BytecodeScanningDetector {

    private static final double BUG_RATIO_LIMIT = 10.0;

    private BugReporter bugReporter;
    private OpcodeStack stack;
    private Deque<IfBlock> ifBlocks;
    private boolean activeUnconditional;
    private boolean isReported;

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

    @Override
    public void visitClassContext(ClassContext classContext) {
        try {
            stack = new OpcodeStack();
            ifBlocks = new ArrayDeque<>();
            super.visitClassContext(classContext);
        } finally {
            stack = null;
            ifBlocks = null;
        }
    }

    @Override
    public void visitCode(Code obj) {
        Method m = getMethod();
        if (m.getReturnType() == Type.VOID) {
            return;
        }

        stack.resetForMethodEntry(this);
        ifBlocks.clear();
        activeUnconditional = false;
        isReported = false;
        super.visitCode(obj);
    }

    @Override
    public void sawOpcode(int seen) {
        if (isReported) {
            return;
        }

        if (!ifBlocks.isEmpty()) {
            IfBlock block = ifBlocks.getFirst();
            if ((getPC() >= block.getEnd()) && (!block.isUnconditionalReturn())) {
                ifBlocks.removeFirst();
            }
        }

        if (isBranch(seen)) {
            if (activeUnconditional) {
                activeUnconditional = false;
                ifBlocks.removeFirst();
                return;
            }

            if (getBranchOffset() > 0) {
                ifBlocks.addLast(new IfBlock(getNextPC(), getBranchTarget()));
            }
        }

        if (isReturn(seen)) {
            if (activeUnconditional) {
                IfBlock block = ifBlocks.getFirst();
                int ifSize = block.getEnd() - block.getStart();
                int elseSize = getPC() - block.getEnd();

                double ratio = (double) ifSize / (double) elseSize;
                if (ratio > BUG_RATIO_LIMIT) {
                    bugReporter.reportBug(new BugInstance(this, BugType.BL_BURYING_LOGIC.name(), NORMAL_PRIORITY).addClass(this).addMethod(this)
                            .addSourceLineRange(this, block.getStart(), block.getEnd()));
                    isReported = true;
                }
            } else if (!ifBlocks.isEmpty() && (getNextPC() == ifBlocks.getFirst().getEnd())) {
                ifBlocks.getFirst().setUnconditionalReturn(true);
                activeUnconditional = true;
            }
        }
    }

    static class IfBlock {
        private int start;
        private int end;
        private boolean unconditinalReturn;

        public IfBlock(int s, int e) {
            start = s;
            end = e;
        }

        public int getStart() {
            return start;
        }

        public int getEnd() {
            return end;
        }

        public boolean isUnconditionalReturn() {
            return unconditinalReturn;
        }

        public void setUnconditionalReturn(boolean unconditinalReturn) {
            this.unconditinalReturn = unconditinalReturn;
        }

        @Override
        public String toString() {
            return ToString.build(this);
        }
    }
}

@@ -1451,23 +1451,114 @@
 c final 
-double 
+String BURY_LOGIC_RATIO_PROPERTY = %22fb-contrib.bl.ratio%22;%0A    private static final double DEFAULT_
 BUG_RATI
@@ -1568,17 +1568,17 @@
 IMIT = 1
-0
+2
 .0;%0A%0A   
@@ -1751,16 +1751,50 @@
 eported;
+%0A    private double bugRatioLimit;
 %0A%0A    pu
@@ -1834,24 +1834,24 @@
 Reporter) %7B%0A
-
         this
@@ -1873,24 +1873,368 @@
 bugReporter;
+%0A%0A        String ratio = System.getProperty(BURY_LOGIC_RATIO_PROPERTY);%0A        try %7B%0A            bugRatioLimit = Double.parseDouble(ratio);%0A            if (bugRatioLimit %3C= 0) %7B%0A                bugRatioLimit = DEFAULT_BUG_RATIO_LIMIT;%0A            %7D%0A        %7D catch (Exception e) %7B%0A            bugRatioLimit = DEFAULT_BUG_RATIO_LIMIT;%0A        %7D
 %0A    %7D%0A%0A    
@@ -3894,31 +3894,29 @@
 ratio %3E 
-BUG_RATIO_LIMIT
+bugRatioLimit
 ) %7B%0A    

bump up the ratio limit, and allow it to be overridden