/* ====================================================================
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
==================================================================== */
package jp.co.minori.findbugs.detect.sc;

import java.util.HashMap;
import java.util.Map;

import jp.co.minori.findbugs.utils.BranchUtils;
import jp.co.minori.findbugs.utils.BranchUtils.BranchInfo;

import org.apache.bcel.classfile.LocalVariable;
import org.apache.bcel.classfile.LocalVariableTable;
import org.apache.bcel.classfile.Method;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
import edu.umd.cs.findbugs.visitclass.LVTHelper;

/**
 * NUM02-J ディテクタ
 * @author Zenichi Amano (crow.misia@gmail.com)
 */
public final class NUM02J extends OpcodeStackDetector {
    private final BugReporter bugReporter;

    private LocalVariableTable lvt;

    private final Map<LocalVariable, BranchInfo> lvMapper = new HashMap<LocalVariable, BranchInfo>();
    private final Map<XField, BranchInfo> fieldMapper = new HashMap<XField, BranchInfo>();

    public NUM02J(final BugReporter bugReporter) {
        this.bugReporter = bugReporter;
    }

    @Override
    public void visitMethod(final Method obj) {
        lvMapper.clear();
        lvt = obj.getLocalVariableTable();
        super.visitMethod(obj);
    }

    @Override
    public void sawOpcode(final int seen) {
        LocalVariable lv;
        BranchInfo branch;
        int register;

        switch (seen) {
        case IFEQ:
        case IFNE:
            if (stack.getStackDepth() > 0) {
                final OpcodeStack.Item item = stack.getStackItem(0);
                register = item.getRegisterNumber();
                if (register >= 0) {
                    lv = LVTHelper.getLocalVariableAtPC(lvt, register, getPC());
                    if (lv != null) {
                        // ローカル変数
                        branch = BranchUtils.getBranch(this, BranchUtils.INVALID_BRANCH);
                        branch.userValue = Boolean.valueOf(seen == IFEQ);
                        lvMapper.put(lv, branch);
                    }
                } else {
                    // フィールド変数
                    final XField field = item.getXField();
                    if (field != null) {
                        branch = BranchUtils.getBranch(this, BranchUtils.INVALID_BRANCH);
                        branch.userValue = Boolean.valueOf(seen == IFEQ);
                        fieldMapper.put(field, branch);
                    }
                }
            }
            break;

        case ISTORE:
        case ISTORE_0:
        case ISTORE_1:
        case ISTORE_2:
        case ISTORE_3:
        case LSTORE:
        case LSTORE_0:
        case LSTORE_1:
        case LSTORE_2:
        case LSTORE_3:
        case FSTORE:
        case FSTORE_0:
        case FSTORE_1:
        case FSTORE_2:
        case FSTORE_3:
        case DSTORE:
        case DSTORE_0:
        case DSTORE_1:
        case DSTORE_2:
        case DSTORE_3:
        case ASTORE:
        case ASTORE_0:
        case ASTORE_1:
        case ASTORE_2:
        case ASTORE_3:
            // 変数に何かしら格納された場合、再度チェックが必要
            register = getRegisterOperand();
            if (register >= 0) {
                lv = LVTHelper.getLocalVariableAtPC(lvt, register, getNextPC());
                if (lv != null) {
                    // ローカル変数
                    lvMapper.remove(lv);
                }
            } else {
                // フィールド変数
                final XField field = getXField();
                if (field != null) {
                    fieldMapper.remove(field);
                }
            }
            break;

        case IDIV:
        case LDIV:
        case FDIV:
        case DDIV:
        case IREM:
        case LREM:
        case FREM:
        case DREM:
            if (stack.getStackDepth() >= 2) {
                final int pc = getPC();
                final OpcodeStack.Item item = stack.getStackItem(0);
                if (item.getConstant() instanceof Number) {
                    // 定数
                    if (Double.compare(((Number)item.getConstant()).doubleValue(), 0.0) == 0) {
                        bugReporter.reportBug(
                                new BugInstance("SC_NUM02J_ZERO", HIGH_PRIORITY)
                                    .addClassAndMethod(this));
                    }
                    return;
                }

                register = item.getRegisterNumber();
                branch = null;
                if (register >= 0) {
                    // ローカル変数
                    lv = LVTHelper.getLocalVariableAtPC(lvt, register, pc);
                    if (lv != null) {
                        branch = lvMapper.get(lv);
                    }
                } else {
                    // フィールド変数
                    final XField field = item.getXField();
                    if (field != null) {
                        branch = fieldMapper.get(field);
                    } else {
                        break;
//                      final XMethod method = item.getReturnValueOf();
                    }
                }

                boolean detect = false;
                if (branch == null) {
                    detect = true;
                } else {
                    final boolean eq = ((Boolean) branch.userValue).booleanValue();
                    if (eq && branch.isOuter(pc)) {
                        detect = true;
                    } else if (!eq && branch.isBetween(pc)) {
                        detect = true;
                    }
                }

                if (detect) {
                    bugReporter.reportBug(
                            new BugInstance("SC_NUM02J_NOT_ZERO_CHECK", NORMAL_PRIORITY)
                            .addClassAndMethod(this)
                            .addSourceLine(this, pc));
                }
            }
            break;
        default:
            break;
        }
    }
}
