/*
 * Copyright (c) 2002 - 2014 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
 */

/*
 * MethodPositions.java
 *
 * Created on 23. Mai 2005, 14:49
 */

package com.ibm.wala.shrike.sourcepos;

import com.ibm.wala.shrike.sourcepos.InvalidRangeException.Cause;
import java.io.DataInputStream;
import java.io.IOException;

/**
 * This class represents the MethodPositions attribute.
 *
 * @author Siegfried Weber
 * @author Juergen Graf &lt;juergen.graf@gmail.com&gt;
 */
public final class MethodPositions extends PositionsAttribute {

  /** Stores the attribute name of this attribute */
  public static final String ATTRIBUTE_NAME = "joana.sourceinfo.MethodPositions";

  private static final String ERR_COLUMN_ZERO =
      "Error in MethodPositions attribute: Invalid column number in %1$s.";

  private static final String ERR_LINE_ZERO =
      "Error in MethodPositions attribute: Invalid line number in %1$s.";

  private static final String ERR_RANGE_UNDEFINED =
      "Error in MethodPositions attribute: %1$s and %2$s are undefined.";

  private static final String ERR_SET_RANGE_UNDEFINED =
      "Error in MethodPositions attribute: Invalid positions, so %1$s and %2$s are set undefined.";

  private static final String ERR_POSITION_UNDEFINED =
      "Error in MethodPositions attribute: %1$s is undefined.";

  private static final String ERR_END_BEFORE_START =
      "Error in MethodPositions attribute: %2$s (%4$s) is before %1$s (%3$s).";

  private static final String ERR_UNKNOWN_REASON =
      "Error in MethodPositions attribute: unknown reason %1$s.";

  private static final String WARN_INVALID_BLOCK_END =
      "Warning in MethodPositions attribute: Invalid method block end position.";

  private static final String WARN_PARAMETER_NOT_IN_DECLARATION =
      "Warning in MethodPositions attribute: Parameter not in the declaration range.";

  /** positions of the method declaration */
  private Range declaration;
  /** positions of the method parameters */
  private Range parameter;
  /** positions of the method block */
  private Range block_end;

  /**
   * Creates a new instance of MethodPositions
   *
   * @param data the byte array containing the attribute
   * @throws IOException if the attribute can't be read.
   */
  public MethodPositions(byte[] data) throws IOException {
    super(data);
    if (Debug.PRINT_CHARACTER_RANGE_TABLE) {
      Debug.info("MethodPositions found: ");
      Debug.info(toString());
    }
  }

  @Override
  protected void readData(DataInputStream in) throws IOException {
    declaration = readRange(in, "declaration_start", "declaration_end", false);
    parameter = readRange(in, "parameter_start", "parameter_end", true);
    block_end = readRange(in, "block_end_start", "block_end_end", false);
    if (!parameter.isUndefined()
        && (!declaration.getStartPosition().isBefore(parameter.getStartPosition())
            || !parameter.getEndPosition().isBefore(declaration.getEndPosition())))
      Debug.warn(WARN_PARAMETER_NOT_IN_DECLARATION);
    if (!declaration.getEndPosition().isBefore(block_end.getStartPosition()))
      Debug.warn(WARN_INVALID_BLOCK_END);
  }

  /**
   * Reads a range from the input stream.
   *
   * @param in the input stream
   * @param startVarName the variable name for the start position
   * @param endVarName the variable name for the end position
   * @param undefinedAllowed {@code true} if the range may be undefined.
   * @return the range
   * @throws IOException if the input stream cannot be read
   */
  private static Range readRange(
      DataInputStream in, String startVarName, String endVarName, boolean undefinedAllowed)
      throws IOException {
    boolean valid = true;
    Range range = null;
    Position start = null;
    Position end = null;
    try {
      start = readPosition(in, startVarName);
    } catch (InvalidPositionException e) {
      valid = false;
    }
    try {
      end = readPosition(in, endVarName);
    } catch (InvalidPositionException e) {
      valid = false;
    }
    if (valid) {
      try {
        range = new Range(start, end);
      } catch (InvalidRangeException e) {
        final Cause thisCause = e.getThisCause();
        switch (thisCause) {
          case END_BEFORE_START:
            Debug.warn(ERR_END_BEFORE_START, startVarName, endVarName, start, end);
            break;
          case START_UNDEFINED:
            Debug.warn(ERR_POSITION_UNDEFINED, startVarName);
            break;
          case END_UNDEFINED:
            Debug.warn(ERR_POSITION_UNDEFINED, endVarName);
            break;
          default:
            Debug.warn(ERR_UNKNOWN_REASON, thisCause);
        }
      }
    }
    if (range == null) {
      range = new Range();
      Debug.warn(ERR_SET_RANGE_UNDEFINED, startVarName, endVarName);
    }
    if (range.isUndefined() && !undefinedAllowed) {
      Debug.warn(ERR_RANGE_UNDEFINED, startVarName, endVarName);
    }

    return range;
  }

  /**
   * Reads a position from the input stream.
   *
   * @param in the input stream
   * @param varName the variable name for this position
   * @throws IOException if the input stream cannot be read
   * @throws InvalidPositionException if the read position is invalid
   */
  private static Position readPosition(DataInputStream in, String varName)
      throws IOException, InvalidPositionException {
    Position pos = null;
    try {
      pos = new Position(in.readInt());
    } catch (InvalidPositionException e) {
      switch (e.getThisCause()) {
        case LINE_NUMBER_ZERO:
          Debug.warn(ERR_LINE_ZERO, varName);
          throw e;
        case COLUMN_NUMBER_ZERO:
          Debug.warn(ERR_COLUMN_ZERO, varName);
          throw e;
        default:
          assert false;
      }
    }
    return pos;
  }

  /**
   * Returns the source position range of the method declaration.
   *
   * @return the source position range of the method declaration
   */
  public Range getHeaderInfo() {
    return declaration;
  }

  /**
   * Returns the source position range of the method parameter declaration.
   *
   * @return the source position range of the method parameter declaration
   */
  public Range getMethodInfo() {
    return parameter;
  }

  /**
   * Returns the source position range of the end of the method block.
   *
   * @return the source position range of the end of the method block
   */
  public Range getFooterInfo() {
    return block_end;
  }

  @Override
  public String toString() {
    return "header: " + declaration + " params: " + parameter + " footer:" + block_end;
  }
}
