/*
 * Bytecode Analysis Framework
 * Copyright (C) 2004,2005 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.ba.obl;

import java.util.Arrays;

/**
 * A multiset of obligations that must be cleaned up by error-handling code.
 *
 * <p>
 * See Weimer and Necula, <a href="http://doi.acm.org/10.1145/1028976.1029011"
 * >Finding and preventing run-time error handling mistakes</a>, OOPSLA 2004.
 * </p>
 *
 * @author David Hovemeyer
 */
public class ObligationSet {
    private static final int INVALID_HASH_CODE = -1;

    private final short[] countList;

    // private final short[] whereCreated;
    private final ObligationFactory factory;

    private int cachedHashCode;

    public ObligationSet(/* int maxObligationTypes, */ObligationFactory factory) {
        this.countList = new short[factory.getMaxObligationTypes()];
        // this.whereCreated = new short[factory.getMaxObligationTypes()];
        this.factory = factory;
        invalidate();
    }


    public boolean isEmpty() {
        for(short s : countList) {
            if (s > 0) {
                return false;
            }
        }
        return true;
    }
    public void add(Obligation obligation) {
        invalidate();
        countList[obligation.getId()]++;
    }

    public void remove(Obligation obligation) {
        invalidate();
        int count = countList[obligation.getId()];
        if (count > 0)
        {
            countList[obligation.getId()]--; // = (short)(count - 1);
        }
    }

    public int getCount(int id) {
        return countList[id];
    }

    // public int getCount(Obligation obligation) {
    // return getCount(obligation.getId());
    // }

    // /**
    // * Called upon the first creation of given kind of obligation on path.
    // *
    // * @param obligation an Obligation
    // * @param basicBlockId BasicBlock id of first creation of the given
    // obligation type
    // */
    // public void setWhereCreated(Obligation obligation, int basicBlockId) {
    // assert getCount(obligation.getId()) == 1;
    // invalidate();
    //
    // if (basicBlockId > Short.MAX_VALUE) {
    // whereCreated[obligation.getId()] = -1;
    // return;
    // }
    //
    // whereCreated[obligation.getId()] = (short) basicBlockId;
    // }

    // /**
    // * Find out where the first instance of given obligation type was created.
    // *
    // * @param obligation an obligation
    // * @return id of basic block where created, or -1 if the basic block
    // couldn't be stored
    // */
    // public int getWhereCreated(Obligation obligation) {
    // return whereCreated[obligation.getId()];
    // }

    @Override
    public boolean equals(Object o) {
        if (o == null || o.getClass() != this.getClass()) {
            return false;
        }

        ObligationSet other = (ObligationSet) o;

        if (!Arrays.equals(this.countList, other.countList)
                /* || !Arrays.equals(this.whereCreated, other.whereCreated) */) {
            return false;
        }

        return true;
    }

    /*
     * NOTE: this string is incorporated into a StringAnnotation when reporting
     * OBL_ warnings, so the output needs to be user-friendly.
     */
    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append("{");
        int count = 0;
        for (int i = 0; i < countList.length; ++i) {
            if (countList[i] == 0) {
                continue;
            }
            if (count > 0) {
                buf.append(",");
            }
            buf.append(factory.getObligationById(i).toString());
            buf.append(" x ");
            buf.append(countList[i]);
            ++count;
        }
        buf.append("}");
        // buf.append("@" + System.identityHashCode(this));
        return buf.toString();
    }

    public void copyFrom(ObligationSet other) {
        System.arraycopy(other.countList, 0, this.countList, 0, other.countList.length);
        // System.arraycopy(other.whereCreated, 0, this.whereCreated, 0,
        // other.whereCreated.length);
        invalidate();
    }

    public ObligationSet duplicate() {
        ObligationSet dup = new ObligationSet(/* countList.length, */factory);
        dup.copyFrom(this);
        return dup;
    }

    @Override
    public int hashCode() {
        if (cachedHashCode == INVALID_HASH_CODE) {
            int value = 0;
            for (int i = 0; i < countList.length; ++i) {
                value += (13 * i * (countList[i]/* + whereCreated[i] */));
            }
            cachedHashCode = value;
        }
        return cachedHashCode;
    }

    private void invalidate() {
        this.cachedHashCode = INVALID_HASH_CODE;
    }
}

