/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2004-2006 University of Maryland
 *
 * 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 edu.umd.cs.findbugs.detect;

import org.apache.bcel.classfile.Code;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.OpcodeStack.Item;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;

/**
 * Find comparisons involving values computed with bitwise operations whose
 * outcomes are fixed at compile time.
 *
 * @author Tom Truscott <trt@unx.sas.com>
 * @author Tagir Valeev
 */
public class IncompatMask extends OpcodeStackDetector {
    private final BugReporter bugReporter;

    private int bitop = -1;

    private boolean equality;

    private Number arg1, arg2;

    private Item bitresultItem;

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

    static int populationCount(long i) {
        return Long.bitCount(i);
    }

    @Override
    public void visit(Code obj) {
        arg1 = arg2 = null;
        super.visit(obj);
    }

    private Number getArg() {
        Object constValue = stack.getStackItem(0).getConstant();
        if (!(constValue instanceof Number)) {
            constValue = stack.getStackItem(1).getConstant();
        }
        if (!(constValue instanceof Long) && !(constValue instanceof Integer)) {
            return null;
        }
        return (Number) constValue;
    }

    @Override
    public void sawOpcode(int seen) {
        switch (seen) {
        case IAND:
        case LAND:
            arg1 = getArg();
            bitop = IAND;
            return;
        case IOR:
        case LOR:
            arg1 = getArg();
            bitop = IOR;
            return;
        case LCMP:
            if (checkItem(2)) {
                arg2 = getArg();
            }
            return;
        case IF_ICMPEQ:
        case IF_ICMPNE:
            if (checkItem(2)) {
                arg2 = getArg();
                equality = true;
            }
            break;
        case IFEQ:
        case IFNE:
            if (arg1 instanceof Integer && checkItem(1)) {
                arg2 = 0;
            }
            equality = true;
            break;
        case IFLE:
        case IFLT:
        case IFGT:
        case IFGE:
            if (arg1 instanceof Integer && checkItem(1)) {
                arg2 = 0;
            }
            equality = false;
            break;
        default:
            return;
        }
        if (arg1 == null || arg2 == null) {
            return;
        }
        boolean isLong = arg1 instanceof Long;
        if (!equality && arg2.longValue() == 0) {
            long bits = getFlagBits(isLong, arg1.longValue());
            boolean highbit = !isLong && (bits & 0x80000000) != 0 || isLong && bits < 0 && bits << 1 == 0;
            boolean onlyLowBits = bits >>> 12 == 0;
            BugInstance bug;
            if (highbit) {
                bug = new BugInstance(this, "BIT_SIGNED_CHECK_HIGH_BIT", (seen == IFLE || seen == IFGT) ? HIGH_PRIORITY
                        : NORMAL_PRIORITY);
            } else {
                bug = new BugInstance(this, "BIT_SIGNED_CHECK", onlyLowBits ? LOW_PRIORITY : NORMAL_PRIORITY);
            }
            bug.addClassAndMethod(this).addString(toHex(arg1)+" ("+arg1+")").addSourceLine(this);
            bugReporter.reportBug(bug);
        }
        if (equality) {
            long dif;
            String t;

            long val1 = arg1.longValue();
            long val2 = arg2.longValue();

            if (bitop == IOR) {
                dif = val1 & ~val2;
                t = "BIT_IOR";
            } else if (val1 != 0 || val2 != 0) {
                dif = val2 & ~val1;
                t = "BIT_AND";
            } else {
                dif = 1;
                t = "BIT_AND_ZZ";
            }
            if (dif != 0) {
                BugInstance bug = new BugInstance(this, t, HIGH_PRIORITY).addClassAndMethod(this);
                if (!"BIT_AND_ZZ".equals(t)) {
                    bug.addString(toHex(arg1)).addString(toHex(arg2));
                }
                bug.addSourceLine(this);
                bugReporter.reportBug(bug);
            }
        }
        arg1 = arg2 = null;
        bitresultItem = null;
    }

    private static String toHex(Number n) {
        return "0x"+(n instanceof Long ? Long.toHexString(n.longValue()) : Integer.toHexString(n.intValue()));
    }

    private boolean checkItem(int n) {
        if(bitresultItem != null) {
            for (int i = 0; i < n; i++) {
                if (stack.getStackItem(i) == bitresultItem) {
                    return true;
                }
            }
        }
        arg1 = arg2 = null;
        bitresultItem = null;
        return false;
    }

    @Override
    public void afterOpcode(int seen) {
        super.afterOpcode(seen);
        switch (seen) {
        case IAND:
        case LAND:
        case IOR:
        case LOR:
            if(stack.getStackDepth() > 0) {
                bitresultItem = stack.getStackItem(0);
            }
            break;
        default:
            break;
        }
    }

    static long getFlagBits(boolean isLong, long arg0) {
        long bits = arg0;
        if (isLong) {
            if (populationCount(bits) > populationCount(~bits)) {
                bits = ~bits;
            }
        } else if (populationCount(0xffffffffL & bits) > populationCount(0xffffffffL & ~bits)) {
            bits = 0xffffffffL & ~bits;
        }
        return bits;
    }
}
