/*
 * Copyright (c) 2002,2006 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 */
package com.ibm.wala.shrike.shrikeBT.info;

import com.ibm.wala.shrike.shrikeBT.ArrayLengthInstruction;
import com.ibm.wala.shrike.shrikeBT.ConstantInstruction;
import com.ibm.wala.shrike.shrikeBT.DupInstruction;
import com.ibm.wala.shrike.shrikeBT.ExceptionHandler;
import com.ibm.wala.shrike.shrikeBT.GotoInstruction;
import com.ibm.wala.shrike.shrikeBT.IArrayLoadInstruction;
import com.ibm.wala.shrike.shrikeBT.IArrayStoreInstruction;
import com.ibm.wala.shrike.shrikeBT.IBinaryOpInstruction;
import com.ibm.wala.shrike.shrikeBT.IComparisonInstruction;
import com.ibm.wala.shrike.shrikeBT.IConditionalBranchInstruction;
import com.ibm.wala.shrike.shrikeBT.IConversionInstruction;
import com.ibm.wala.shrike.shrikeBT.IGetInstruction;
import com.ibm.wala.shrike.shrikeBT.IInstanceofInstruction;
import com.ibm.wala.shrike.shrikeBT.IInstruction;
import com.ibm.wala.shrike.shrikeBT.IInvokeInstruction;
import com.ibm.wala.shrike.shrikeBT.ILoadInstruction;
import com.ibm.wala.shrike.shrikeBT.IPutInstruction;
import com.ibm.wala.shrike.shrikeBT.IShiftInstruction;
import com.ibm.wala.shrike.shrikeBT.IStoreInstruction;
import com.ibm.wala.shrike.shrikeBT.ITypeTestInstruction;
import com.ibm.wala.shrike.shrikeBT.IUnaryOpInstruction;
import com.ibm.wala.shrike.shrikeBT.MethodData;
import com.ibm.wala.shrike.shrikeBT.MonitorInstruction;
import com.ibm.wala.shrike.shrikeBT.NewInstruction;
import com.ibm.wala.shrike.shrikeBT.PopInstruction;
import com.ibm.wala.shrike.shrikeBT.ReturnInstruction;
import com.ibm.wala.shrike.shrikeBT.SwapInstruction;
import com.ibm.wala.shrike.shrikeBT.SwitchInstruction;
import com.ibm.wala.shrike.shrikeBT.ThrowInstruction;

/**
 * This method annotation counts the number of instructions of each type (according to each
 * Instruction subclass).
 *
 * <p>The get...Count methods are the only methods needed by clients. These methods check to see if
 * the MethodData object already has an InstructionTypeCounter annotation before recomputing the
 * counts and returning the desired count.
 */
public class InstructionTypeCounter implements MethodData.Results {
  private static final String key = InstructionTypeCounter.class.getName();

  private int countMonitors;

  private int countGets;

  private int countPuts;

  private int countArrayLoads;

  private int countArrayStores;

  private int countInvokes;

  private int countArrayLengths;

  private int countBinaryOps;

  private int countCheckCasts;

  private int countComparisons;

  private int countConditionalBranches;

  private int countConstants;

  private int countConversions;

  private int countDups;

  private int countGotos;

  private int countInstanceOfs;

  private int countLocalLoads;

  private int countLocalStores;

  private int countNews;

  private int countPops;

  private int countReturns;

  private int countShifts;

  private int countSwaps;

  private int countSwitches;

  private int countThrows;

  private int countUnaryOps;

  InstructionTypeCounter(MethodData info) {
    recalculateFrom(info.getInstructions());
  }

  private void recalculateFrom(IInstruction[] instructions) {
    countMonitors = 0;
    countGets = 0;
    countPuts = 0;
    countArrayLoads = 0;
    countArrayStores = 0;
    countInvokes = 0;
    countArrayLengths = 0;
    countBinaryOps = 0;
    countCheckCasts = 0;
    countComparisons = 0;
    countConditionalBranches = 0;
    countConstants = 0;
    countConversions = 0;
    countDups = 0;
    countGotos = 0;
    countInstanceOfs = 0;
    countLocalLoads = 0;
    countLocalStores = 0;
    countNews = 0;
    countPops = 0;
    countReturns = 0;
    countShifts = 0;
    countSwaps = 0;
    countSwitches = 0;
    countThrows = 0;
    countUnaryOps = 0;

    IInstruction.Visitor visitor =
        new IInstruction.Visitor() {
          @Override
          public void visitArrayLength(ArrayLengthInstruction instruction) {
            countArrayLengths++;
          }

          @Override
          public void visitBinaryOp(IBinaryOpInstruction instruction) {
            countBinaryOps++;
          }

          @Override
          public void visitCheckCast(ITypeTestInstruction instruction) {
            countCheckCasts++;
          }

          @Override
          public void visitComparison(IComparisonInstruction instruction) {
            countComparisons++;
          }

          @Override
          public void visitConditionalBranch(IConditionalBranchInstruction instruction) {
            countConditionalBranches++;
          }

          @Override
          public void visitConstant(ConstantInstruction instruction) {
            countConstants++;
          }

          @Override
          public void visitConversion(IConversionInstruction instruction) {
            countConversions++;
          }

          @Override
          public void visitDup(DupInstruction instruction) {
            countDups++;
          }

          @Override
          public void visitGoto(GotoInstruction instruction) {
            countGotos++;
          }

          @Override
          public void visitInstanceof(IInstanceofInstruction instruction) {
            countInstanceOfs++;
          }

          @Override
          public void visitLocalLoad(ILoadInstruction instruction) {
            countLocalLoads++;
          }

          @Override
          public void visitLocalStore(IStoreInstruction instruction) {
            countLocalStores++;
          }

          @Override
          public void visitNew(NewInstruction instruction) {
            countNews++;
          }

          @Override
          public void visitPop(PopInstruction instruction) {
            countPops++;
          }

          @Override
          public void visitReturn(ReturnInstruction instruction) {
            countReturns++;
          }

          @Override
          public void visitShift(IShiftInstruction instruction) {
            countShifts++;
          }

          @Override
          public void visitSwap(SwapInstruction instruction) {
            countSwaps++;
          }

          @Override
          public void visitSwitch(SwitchInstruction instruction) {
            countSwitches++;
          }

          @Override
          public void visitThrow(ThrowInstruction instruction) {
            countThrows++;
          }

          @Override
          public void visitUnaryOp(IUnaryOpInstruction instruction) {
            countUnaryOps++;
          }

          @Override
          public void visitArrayLoad(IArrayLoadInstruction instruction) {
            countArrayLoads++;
          }

          @Override
          public void visitArrayStore(IArrayStoreInstruction instruction) {
            countArrayStores++;
          }

          @Override
          public void visitGet(IGetInstruction instruction) {
            countGets++;
          }

          @Override
          public void visitPut(IPutInstruction instruction) {
            countPuts++;
          }

          @Override
          public void visitMonitor(MonitorInstruction instruction) {
            countMonitors++;
          }

          @Override
          public void visitInvoke(IInvokeInstruction instruction) {
            countInvokes++;
          }
        };

    for (IInstruction instruction : instructions) {
      instruction.visit(visitor);
    }
  }

  /**
   * Whenever the underlying method is updated, we'll throw away our counts so they can be
   * reconstructed from scratch next time.
   *
   * <p>This is not to be called by clients.
   */
  @Override
  public boolean notifyUpdate(
      MethodData info,
      IInstruction[] newInstructions,
      ExceptionHandler[][] newHandlers,
      int[] newInstructionMap) {
    // just throw this away and we'll recalculate from scratch if necessary
    return true;
  }

  public static int getArrayLoadCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countArrayLoads;
  }

  public static int getArrayStoreCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countArrayStores;
  }

  public static int getGetCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countGets;
  }

  public static int getPutCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countPuts;
  }

  public static int getMonitorCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countMonitors;
  }

  public static int getInvokeCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countInvokes;
  }

  public static int getComparisonCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countComparisons;
  }

  public static int getArrayLengthCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countArrayLengths;
  }

  public static int getConstantCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countConstants;
  }

  public static int getShiftCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countShifts;
  }

  public static int getSwitchesCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countSwitches;
  }

  public static int getSwapCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countSwaps;
  }

  public static int getBinaryOpCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countBinaryOps;
  }

  public static int getCheckCastCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countCheckCasts;
  }

  public static int getThrowCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countThrows;
  }

  public static int getConditionalBranchCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countConditionalBranches;
  }

  public static int getConversionCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countConversions;
  }

  public static int getDupCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countDups;
  }

  public static int getGotoCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countGotos;
  }

  public static int getReturnCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countReturns;
  }

  public static int getInstanceOfCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countInstanceOfs;
  }

  public static int getLocalLoadCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countLocalLoads;
  }

  public static int getLocalStoreCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countLocalStores;
  }

  public static int getNewCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countNews;
  }

  public static int getPopCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countPops;
  }

  public static int getUnaryOpCount(MethodData info) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    return getCounter(info).countUnaryOps;
  }

  private static InstructionTypeCounter getCounter(MethodData info) {
    InstructionTypeCounter c = (InstructionTypeCounter) info.getInfo(key);
    if (c == null) {
      c = new InstructionTypeCounter(info);
      info.putInfo(key, c);
    }

    return c;
  }
}
