/*
 * 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Optional;

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

/**
 * A local variable representation.
 * 
 * <p>This does nothing to enforce required constraints, e.g.:
 * <ul>
 *    <li>This does not ensure that {@link #start()} and {@link #end()} are valid and exist in the
 *        method.
 *    <li>This does not ensure that the {@link #index} is otherwise unused and that only one 
 *        variable is active at a time with the index.
 * </ul>
 * 
 * <p>Note: This class does not attempt to make use of the convenience methods on generator adapter
 * such as {@link CodeBuilder#newLocal(Type)} or {@link CodeBuilder#loadArg(int)} that
 * make it easier to work with local variables (and calculating local variable indexes).  Instead
 * we push this responsibility onto our caller.  This is because CodeBuilder doesn't make it
 * possible to generate local variable debugging tables in this case (e.g. there is no way to map
 * a method parameter index to a local variable index).
 */
final class LocalVariable extends Expression {
  // TODO(lukes): the fact that you need to specify the start and end labels during construction
  // ends up being awkward... Due to the fact that it is unclear who is responsible for actually
  // visiting the labels.  Maybe this object should be label agnostic and the labels should just be
  // parameters to tableEntry?

  static LocalVariable createThisVar(TypeInfo owner, Label start, Label end) {
    return new LocalVariable("this", owner.type(), 0, start, end, Feature.NON_NULLABLE);
  }

  static LocalVariable createLocal(String name, int index, Type type, Label start, Label end) {
    checkArgument(!name.equals("this"));
    return new LocalVariable(name, type, index, start, end);
  }

  private final String variableName;
  private final int index;
  private final Label start;
  private final Label end;
  
  private LocalVariable(
      String variableName, Type type, int index, Label start, Label end, Feature ...features) {
    super(type, Feature.CHEAP /* locals are always cheap */, features);
    this.variableName = checkNotNull(variableName);
    this.index = index;
    this.start = checkNotNull(start);
    this.end = checkNotNull(end);
  }
  
  /** The name of the variable, ends up in debugging tables. */
  String variableName() {
    return variableName;
  }

  int index() {
    return index;
  }
  
  /** A label defining the earliest point at which this variable is defined. */
  Label start() {
    return start;
  }
  
  /** A label defining the latest point at which this variable is defined. */
  Label end() {
    return end;
  }

  @Override LocalVariable asCheap() {
    return this;
  }
  
  @Override LocalVariable asNonNullable() {
    if (isNonNullable()) {
      return this;
    }
    return new LocalVariable(variableName, resultType(), index, start, end, Feature.NON_NULLABLE);
  }
  
  /**
   * Write a local variable table entry for this variable.  This informs debuggers about variable
   * names, types and lifetime.
   */
  void tableEntry(CodeBuilder mv) {
    mv.visitLocalVariable(
        variableName(),
        resultType().getDescriptor(),
        null /** no generic signature */,
        start(),
        end(),
        index());
  }

  @Override void doGen(CodeBuilder mv) {
    mv.visitVarInsn(resultType().getOpcode(Opcodes.ILOAD), index());
  }

  /**
   * Return a {@link Statement} that stores the value of the given expression into this variable.
   */
  Statement store(final Expression expr) {
    return store(expr, Optional.<Label>absent());
  }

  /**
   * Return a {@link Statement} that stores the value of the given expression into this variable.
   * 
   * @param expr The expression to store
   * @param firstVarInstruction A label to use to mark the store instruction
   */
  Statement store(final Expression expr, Label firstVarInstruction) {
    return store(expr, Optional.<Label>of(firstVarInstruction));
  }

  /** Writes the value at the top of the stack to the local variable. */
  private Statement store(final Expression expr, final Optional<Label> firstVarInstruction) {
    expr.checkAssignableTo(resultType());
    return new Statement() {
      @Override void doGen(CodeBuilder adapter) {
        expr.gen(adapter);
        if (firstVarInstruction.isPresent()) {
          adapter.mark(firstVarInstruction.get());
        }
        adapter.visitVarInsn(resultType().getOpcode(Opcodes.ISTORE), index());
      }
    };
  }
}
