/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed 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 com.android.dx.cf.code;

import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.type.StdTypeList;
import com.android.dx.rop.type.TypeList;
import com.android.dx.util.FixedSizeList;
import com.android.dx.util.IntList;

/**
 * List of catch entries, that is, the elements of an "exception table," which is part of a standard
 * {@code Code} attribute.
 */
public final class ByteCatchList extends FixedSizeList {
  /** {@code non-null;} convenient zero-entry instance */
  public static final ByteCatchList EMPTY = new ByteCatchList(0);

  /**
   * Constructs an instance.
   *
   * @param count the number of elements to be in the table
   */
  public ByteCatchList(int count) {
    super(count);
  }

  /**
   * Helper method for {@link #listFor}, which tells whether a match is <i>not</i> found for the
   * exception type of the given item in the given array. A match is considered to be either an
   * exact type match or the class {@code Object} which represents a catch-all.
   *
   * @param item {@code non-null;} item with the exception type to look for
   * @param arr {@code non-null;} array to search in
   * @param count {@code non-null;} maximum number of elements in the array to check
   * @return {@code true} iff the exception type is <i>not</i> found
   */
  private static boolean typeNotFound(Item item, Item[] arr, int count) {
    CstType type = item.getExceptionClass();

    for (int i = 0; i < count; i++) {
      CstType one = arr[i].getExceptionClass();
      if ((one == type) || (one == CstType.OBJECT)) {
        return false;
      }
    }

    return true;
  }

  /**
   * Gets the total length of this structure in bytes, when included in a {@code Code} attribute.
   * The returned value includes the two bytes for {@code exception_table_length}.
   *
   * @return {@code >= 2;} the total length, in bytes
   */
  public int byteLength() {
    return 2 + size() * 8;
  }

  /**
   * Gets the indicated item.
   *
   * @param n {@code >= 0;} which item
   * @return {@code null-ok;} the indicated item
   */
  public Item get(int n) {
    return (Item) get0(n);
  }

  /**
   * Sets the item at the given index.
   *
   * @param n {@code >= 0, < size();} which entry to set
   * @param item {@code non-null;} the item
   */
  public void set(int n, Item item) {
    if (item == null) {
      throw new NullPointerException("item == null");
    }

    set0(n, item);
  }

  /**
   * Sets the item at the given index.
   *
   * @param n {@code >= 0, < size();} which entry to set
   * @param startPc {@code >= 0;} the start pc (inclusive) of the handler's range
   * @param endPc {@code >= startPc;} the end pc (exclusive) of the handler's range
   * @param handlerPc {@code >= 0;} the pc of the exception handler
   * @param exceptionClass {@code null-ok;} the exception class or {@code null} to catch all
   *     exceptions with this handler
   */
  public void set(int n, int startPc, int endPc, int handlerPc, CstType exceptionClass) {
    set0(n, new Item(startPc, endPc, handlerPc, exceptionClass));
  }

  /**
   * Gets the list of items active at the given address. The result is automatically made immutable.
   *
   * @param pc which address
   * @return {@code non-null;} list of exception handlers active at {@code pc}
   */
  public ByteCatchList listFor(int pc) {
    int sz = size();
    Item[] resultArr = new Item[sz];
    int resultSz = 0;

    for (int i = 0; i < sz; i++) {
      Item one = get(i);
      if (one.covers(pc) && typeNotFound(one, resultArr, resultSz)) {
        resultArr[resultSz] = one;
        resultSz++;
      }
    }

    if (resultSz == 0) {
      return EMPTY;
    }

    ByteCatchList result = new ByteCatchList(resultSz);
    for (int i = 0; i < resultSz; i++) {
      result.set(i, resultArr[i]);
    }

    result.setImmutable();
    return result;
  }

  /**
   * Returns a target list corresponding to this instance. The result is a list of all the exception
   * handler addresses, with the given {@code noException} address appended if appropriate. The
   * result is automatically made immutable.
   *
   * @param noException {@code >= -1;} the no-exception address to append, or {@code -1} not to
   *     append anything
   * @return {@code non-null;} list of exception targets, with {@code noException} appended if
   *     necessary
   */
  public IntList toTargetList(int noException) {
    if (noException < -1) {
      throw new IllegalArgumentException("noException < -1");
    }

    boolean hasDefault = (noException >= 0);
    int sz = size();

    if (sz == 0) {
      if (hasDefault) {
        /*
         * The list is empty, but there is a no-exception
         * address; so, the result is just that address.
         */
        return IntList.makeImmutable(noException);
      }
      /*
       * The list is empty and there isn't even a no-exception
       * address.
       */
      return IntList.EMPTY;
    }

    IntList result = new IntList(sz + (hasDefault ? 1 : 0));

    for (int i = 0; i < sz; i++) {
      result.add(get(i).getHandlerPc());
    }

    if (hasDefault) {
      result.add(noException);
    }

    result.setImmutable();
    return result;
  }

  /**
   * Returns a rop-style catches list equivalent to this one.
   *
   * @return {@code non-null;} the converted instance
   */
  public TypeList toRopCatchList() {
    int sz = size();
    if (sz == 0) {
      return StdTypeList.EMPTY;
    }

    StdTypeList result = new StdTypeList(sz);

    for (int i = 0; i < sz; i++) {
      result.set(i, get(i).getExceptionClass().getClassType());
    }

    result.setImmutable();
    return result;
  }

  /** Item in an exception handler list. */
  public static class Item {
    /** {@code >= 0;} the start pc (inclusive) of the handler's range */
    private final int startPc;

    /** {@code >= startPc;} the end pc (exclusive) of the handler's range */
    private final int endPc;

    /** {@code >= 0;} the pc of the exception handler */
    private final int handlerPc;

    /**
     * {@code null-ok;} the exception class or {@code null} to catch all exceptions with this
     * handler
     */
    private final CstType exceptionClass;

    /**
     * Constructs an instance.
     *
     * @param startPc {@code >= 0;} the start pc (inclusive) of the handler's range
     * @param endPc {@code >= startPc;} the end pc (exclusive) of the handler's range
     * @param handlerPc {@code >= 0;} the pc of the exception handler
     * @param exceptionClass {@code null-ok;} the exception class or {@code null} to catch all
     *     exceptions with this handler
     */
    public Item(int startPc, int endPc, int handlerPc, CstType exceptionClass) {
      if (startPc < 0) {
        throw new IllegalArgumentException("startPc < 0");
      }

      if (endPc < startPc) {
        throw new IllegalArgumentException("endPc < startPc");
      }

      if (handlerPc < 0) {
        throw new IllegalArgumentException("handlerPc < 0");
      }

      this.startPc = startPc;
      this.endPc = endPc;
      this.handlerPc = handlerPc;
      this.exceptionClass = exceptionClass;
    }

    /**
     * Gets the start pc (inclusive) of the handler's range.
     *
     * @return {@code >= 0;} the start pc (inclusive) of the handler's range.
     */
    public int getStartPc() {
      return startPc;
    }

    /**
     * Gets the end pc (exclusive) of the handler's range.
     *
     * @return {@code >= startPc;} the end pc (exclusive) of the handler's range.
     */
    public int getEndPc() {
      return endPc;
    }

    /**
     * Gets the pc of the exception handler.
     *
     * @return {@code >= 0;} the pc of the exception handler
     */
    public int getHandlerPc() {
      return handlerPc;
    }

    /**
     * Gets the class of exception handled.
     *
     * @return {@code non-null;} the exception class; {@link CstType#OBJECT} if this entry handles
     *     all possible exceptions
     */
    public CstType getExceptionClass() {
      return (exceptionClass != null) ? exceptionClass : CstType.OBJECT;
    }

    /**
     * Returns whether the given address is in the range of this item.
     *
     * @param pc the address
     * @return {@code true} iff this item covers {@code pc}
     */
    public boolean covers(int pc) {
      return (pc >= startPc) && (pc < endPc);
    }
  }
}
