/*
 * Copyright 2015 Google Inc.
 *
 * 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.template.soy.jbcsrc;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.LocalVariablesSorter;
import org.objectweb.asm.commons.Method;
import org.objectweb.asm.commons.TableSwitchGenerator;

import javax.annotation.Nullable;

/**
 * A {@link MethodVisitor} that acts as a substitute for {@link GeneratorAdapter}.
 * 
 * <p>{@link GeneratorAdapter} has lots of convient methods (like 
 * {@link GeneratorAdapter#push(int)}), however it is a subtype of {@link LocalVariablesSorter} 
 * which automatically renumbers local variables.  This is actually fine (i think), but the problem
 * is that it makes our debugging information (like {@link ClassData#toString()} or 
 * {@link Expression#trace()}) very misleading since none of the local variable indices in the debug
 * information match the indexes in the generated code, so the debug data just looks wrong.
 * 
 * <p>So instead we use forwarding to reuse the safe subset of the {@link GeneratorAdapter} api and
 * this allows us to skip past all the local variable munging.
 */
final class CodeBuilder extends MethodVisitor {
  private final GeneratorAdapter adapter;

  CodeBuilder(int access, Method method, MethodVisitor mv) {
    this(mv, access, method.getName(), method.getDescriptor());
  }

  CodeBuilder(int access, Method method, @Nullable Type[] exceptions, ClassVisitor cv) {
    this(access, method, 
        cv.visitMethod(access, method.getName(), method.getDescriptor(),
            null /* generic signature */, getInternalNames(exceptions)));
  }

  CodeBuilder(MethodVisitor mv, int access, String name, String desc) {
    super(Opcodes.ASM5, mv);
    this.adapter = new GeneratorAdapter(mv, access, name, desc);
  }

  private static String[] getInternalNames(@Nullable Type[] types) {
    if (types == null) {
        return null;
    }
    String[] names = new String[types.length];
    for (int i = 0; i < names.length; ++i) {
        names[i] = types[i].getInternalName();
    }
    return names;
  }

  /** See {@link GeneratorAdapter#push(boolean)} */
  public void pushBoolean(boolean value) {
    adapter.push(value);
  }

  /** See {@link GeneratorAdapter#push(int)} */
  public void pushInt(int value) {
    adapter.push(value);
  }

  /** See {@link GeneratorAdapter#push(long)} */
  public void pushLong(long value) {
    adapter.push(value);
  }

  /** See {@link GeneratorAdapter#push(float)} */
  public void pushFloat(float value) {
    adapter.push(value);
  }

  /** See {@link GeneratorAdapter#push(double)} */
  public void pushDouble(double value) {
    adapter.push(value);
  }

  /** See {@link GeneratorAdapter#push(String)} */
  public void pushString(String value) {
    adapter.push(value);
  }

  /** See {@link GeneratorAdapter#push(Type)} */
  public void pushType(Type value) {
    adapter.push(value);
  }

  /** See {@link GeneratorAdapter#push(Type)} */
  public void pushNull() {
    adapter.visitInsn(Opcodes.ACONST_NULL);
  }

  /** See {@link GeneratorAdapter#loadThis()} */
  public void loadThis() {
    adapter.loadThis();
  }

  /** See {@link GeneratorAdapter#loadArgs()} */
  public void loadArgs() {
    adapter.loadArgs();
  }

  /** See {@link GeneratorAdapter#pop()} */
  public void pop() {
    adapter.pop();
  }

  /** See {@link GeneratorAdapter#pop2()} */
  public void pop2() {
    adapter.pop2();
  }

  /** See {@link GeneratorAdapter#dup()} */
  public void dup() {
    adapter.dup();
  }
  
  /** See {@link GeneratorAdapter#dupX1()} */
  public void dupX1() {
    adapter.dupX1();
  }

  /** See {@link GeneratorAdapter#dupX2()} */
  public void dupX2() {
    adapter.dupX2();
  }

  /** See {@link GeneratorAdapter#dup2()} */
  public void dup2() {
    adapter.dup2();
  }
  
  /** See {@link GeneratorAdapter#dup2X1()} */
  public void dup2X1() {
    adapter.dup2X1();
  }
  
  /** See {@link GeneratorAdapter#dup2X2()} */
  public void dup2X2() {
    adapter.dup2X2();
  }

  /** See {@link GeneratorAdapter#iinc(int, int)} */
  public void iinc(int local, int amount) {
    adapter.iinc(local, amount);
  }

  /** See {@link GeneratorAdapter#cast} */
  public void cast(Type from, Type to) {
    adapter.cast(from, to);
  }

  /** See {@link GeneratorAdapter#box(Type)} */
  public void box(Type type) {
    adapter.box(type);
  }

  /** See {@link GeneratorAdapter#valueOf(Type)} */
  public void valueOf(Type type) {
    adapter.valueOf(type);
  }

  /** See {@link GeneratorAdapter#unbox(Type)} */
  public void unbox(Type type) {
    adapter.unbox(type);
  }

  /** See {@link GeneratorAdapter#newLabel()} */
  public Label newLabel() {
    return adapter.newLabel();
  }

  /** See {@link GeneratorAdapter#mark(Label)} */
  public void mark(Label label) {
    adapter.mark(label);
  }

  /** See {@link GeneratorAdapter#mark()} */
  public Label mark() {
    return adapter.mark();
  }

  /** See {@link GeneratorAdapter#ifCmp} */
  public void ifCmp(Type type, int mode, Label label) {
    adapter.ifCmp(type, mode, label);
  }

  /** See {@link GeneratorAdapter#ifICmp(int, Label)} */
  public void ifICmp(int mode, Label label) {
    adapter.ifICmp(mode, label);
  }

  /** See {@link GeneratorAdapter#ifZCmp(int, Label)} */
  public void ifZCmp(int mode, Label label) {
    adapter.ifZCmp(mode, label);
  }

  /** See {@link GeneratorAdapter#ifNull(Label)} */
  public void ifNull(Label label) {
    adapter.ifNull(label);
  }

  /** See {@link GeneratorAdapter#ifNonNull(Label)} */
  public void ifNonNull(Label label) {
    adapter.ifNonNull(label);
  }

  /** See {@link GeneratorAdapter#goTo(Label)} */
  public void goTo(Label label) {
    adapter.goTo(label);
  }

  /** See {@link GeneratorAdapter#tableSwitch(int[], TableSwitchGenerator)} */
  public void tableSwitch(int[] keys, TableSwitchGenerator generator) {
    adapter.tableSwitch(keys, generator);
  }

  /** See {@link GeneratorAdapter#tableSwitch(int[], TableSwitchGenerator, boolean)} */
  public void tableSwitch(int[] keys, TableSwitchGenerator generator, boolean useTable) {
    adapter.tableSwitch(keys, generator, useTable);
  }

  /** See {@link GeneratorAdapter#returnValue()} */
  public void returnValue() {
    adapter.returnValue();
  }

  /** See {@link GeneratorAdapter#getStatic(Type, String, Type)} */
  public void getStatic(Type owner, String name, Type type) {
    adapter.getStatic(owner, name, type);
  }

  /** See {@link GeneratorAdapter#getField(Type, String, Type)} */
  public void getField(Type owner, String name, Type type) {
    adapter.getField(owner, name, type);
  }

  /** See {@link GeneratorAdapter#putField(Type, String, Type)} */
  public void putField(Type owner, String name, Type type) {
    adapter.putField(owner, name, type);
  }

  /** See {@link GeneratorAdapter#putStatic(Type, String, Type)} */
  public void putStatic(Type owner, String name, Type type) {
    adapter.putStatic(owner, name, type);
  }

  /** See {@link GeneratorAdapter#invokeVirtual(Type, Method)} */
  public void invokeVirtual(Type owner, Method method) {
    adapter.invokeVirtual(owner, method);
  }

  /** See {@link GeneratorAdapter#invokeConstructor(Type, Method)} */
  public void invokeConstructor(Type type, Method method) {
    adapter.invokeConstructor(type, method);
  }

  /** See {@link GeneratorAdapter#newInstance(Type)} */
  public void newInstance(Type type) {
    adapter.newInstance(type);
  }

  /** See {@link GeneratorAdapter#newArray(Type)} */
  public void newArray(Type type) {
    adapter.newArray(type);
  }

  /** See {@link GeneratorAdapter#arrayLength()} */
  public void arrayLength() {
    adapter.arrayLength();
  }

  /** See {@link GeneratorAdapter#throwException()} */
  public void throwException() {
    adapter.throwException();
  }

  /** See {@link GeneratorAdapter#throwException(Type, String)} */
  public void throwException(Type type, String msg) {
    adapter.throwException(type, msg);
  }

  /** See {@link GeneratorAdapter#checkCast(Type)} */
  public void checkCast(Type type) {
    adapter.checkCast(type);
  }

  /** See {@link GeneratorAdapter#endMethod()} */
  public void endMethod() {
    adapter.endMethod();
  }

  /** See {@link GeneratorAdapter#swap()} */
  public void swap() {
    adapter.swap();
  }
}
