/*
 * 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.ExceptionHandler;
import com.ibm.wala.shrike.shrikeBT.IInstruction;
import com.ibm.wala.shrike.shrikeBT.ILoadInstruction;
import com.ibm.wala.shrike.shrikeBT.IStoreInstruction;
import com.ibm.wala.shrike.shrikeBT.MethodData;
import com.ibm.wala.shrike.shrikeBT.Util;

/**
 * This method annotation parcels out fresh local variables for use as temporaries by
 * instrumentation code. It assumes that local variables are not allocated by any other mechanism.
 */
public class LocalAllocator implements MethodData.Results {
  private static final String key = LocalAllocator.class.getName();

  private int nextLocal;

  LocalAllocator(MethodData info) {
    recalculateFrom(info);
  }

  private void recalculateFrom(MethodData info) {
    IInstruction[] instructions = info.getInstructions();
    final int[] max = {Util.getParamsWordSize(info.getSignature()) + (info.getIsStatic() ? 0 : 1)};

    IInstruction.Visitor visitor =
        new IInstruction.Visitor() {
          @Override
          public void visitLocalLoad(ILoadInstruction instruction) {
            int v = instruction.getVarIndex() + Util.getWordSize(instruction.getType());
            if (v > max[0]) {
              max[0] = v;
            }
          }

          @Override
          public void visitLocalStore(IStoreInstruction instruction) {
            int v = instruction.getVarIndex() + Util.getWordSize(instruction.getType());
            if (v > max[0]) {
              max[0] = v;
            }
          }
        };

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

    nextLocal = max[0];
  }

  private int allocateLocals(int count) {
    int r = nextLocal;
    nextLocal += count;
    return r;
  }

  /** This should not be called by clients. */
  @Override
  public boolean notifyUpdate(
      MethodData info,
      IInstruction[] newInstructions,
      ExceptionHandler[][] newHandlers,
      int[] newInstructionMap) {
    return false;
  }

  /** Allocates a new local variable of the specified type. */
  public static int allocate(MethodData info, int count) throws IllegalArgumentException {
    if (info == null) {
      throw new IllegalArgumentException();
    }
    LocalAllocator l = (LocalAllocator) info.getInfo(key);
    if (l == null) {
      l = new LocalAllocator(info);
      info.putInfo(key, l);
    }

    return l.allocateLocals(count);
  }

  public static int allocate(MethodData info, String type) throws IllegalArgumentException {
    return allocate(info, type == null ? 2 : Util.getWordSize(type));
  }

  /** Allocates a new local that will fit any type. */
  public static int allocate(MethodData info) throws IllegalArgumentException {
    return allocate(info, null);
  }
}
