// Copyright 2015 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.syntax.compiler;

import com.google.devtools.build.lib.syntax.Operator;

import net.bytebuddy.implementation.Implementation.Context;
import net.bytebuddy.implementation.bytecode.StackManipulation;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * Implements byte code for gotos and conditional jumps.
 */
public class Jump implements StackManipulation {

  protected final int opCode;
  protected final Label target;
  protected final Size stackSizeChange;

  private Jump(int opCode, Label target, Size stackSizeChange) {
    this.opCode = opCode;
    this.target = target;
    this.stackSizeChange = stackSizeChange;
  }

  @Override
  public boolean isValid() {
    return true;
  }

  @Override
  public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
    methodVisitor.visitJumpInsn(opCode, target);
    return stackSizeChange;
  }

  @Override
  public String toString() {
    return "Jump(" + opCode + ", " + target + ")";
  }

  /**
   * Builds a conditional jump for two int operands on the stack.
   */
  public static JumpWithoutTarget ifIntOperands(PrimitiveComparison comparison) {
    return new JumpWithoutTarget(Opcodes.IF_ICMPEQ + comparison.ordinal(), new Size(-2, 0));
  }

  /**
   * Builds a conditional jump for one int operand from the stack compared to zero.
   */
  public static JumpWithoutTarget ifIntOperandToZero(PrimitiveComparison comparison) {
    return new JumpWithoutTarget(Opcodes.IFEQ + comparison.ordinal(), new Size(-1, 0));
  }

  /**
   * Builds a conditional jump for two reference type operands from the stack.
   */
  public static JumpWithoutTarget ifReferenceOperands(ReferenceComparison comparison) {
    return new JumpWithoutTarget(Opcodes.IF_ACMPEQ + comparison.ordinal(), new Size(-2, 0));
  }

  /**
   * Builds a conditional jump for one reference type operand from the stack compared to null.
   */
  public static JumpWithoutTarget ifReferenceOperandToNull(ReferenceComparison comparison) {
    return new JumpWithoutTarget(Opcodes.IFNULL + comparison.ordinal(), new Size(-1, 0));
  }

  /**
   * Builds an unconditional jump to the target label.
   */
  public static Jump to(Label target) {
    return new Jump(Opcodes.GOTO, target, new Size(0, 0));
  }

  /**
   * Builds an unconditional jump to the label added by the given {@link LabelAdder}.
   */
  public static Jump to(LabelAdder target) {
    return to(target.getLabel());
  }

  /**
   * Builder helper class for partially built jumps from conditionals.
   *
   * <p>Allows adding a jump target label.
   */
  public static final class JumpWithoutTarget {

    protected final int opCode;
    protected final Size stackSizeChange;

    private JumpWithoutTarget(int opCode, Size stackSizeChange) {
      this.opCode = opCode;
      this.stackSizeChange = stackSizeChange;
    }

    /**
     * Builds a jump to the given target and the previously initialized conditional.
     */
    public Jump to(LabelAdder target) {
      return new Jump(opCode, target.getLabel(), stackSizeChange);
    }
  }

  /**
   * All primitive comparisons for which there are byte code equivalents.
   */
  public enum PrimitiveComparison {
    EQUAL,
    NOT_EQUAL,
    LESS,
    GREATER_EQUAL,
    GREATER,
    LESS_EQUAL;

    public static PrimitiveComparison forOperator(Operator operator) {
      switch (operator) {
        case LESS:
          return LESS;
        case LESS_EQUALS:
          return PrimitiveComparison.LESS_EQUAL;
        case GREATER:
          return GREATER;
        case GREATER_EQUALS:
          return GREATER_EQUAL;
        default:
          throw new Error("unreachable code");
      }
    }
  }

  /**
   * All reference comparisons for which there are byte code equivalents.
   */
  public enum ReferenceComparison {
    EQUAL,
    NOT_EQUAL;
  }
}
