/*
 * 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.template.soy.jbcsrc.BytecodeUtils.ADVISING_APPENDABLE_TYPE;
import static com.google.template.soy.jbcsrc.BytecodeUtils.ADVISING_BUILDER_TYPE;

import com.google.template.soy.jbcsrc.api.AdvisingAppendable;

import org.objectweb.asm.Label;

/**
 * An expression for an {@link AdvisingAppendable}.
 */
final class AppendableExpression extends Expression {
  private static final MethodRef APPEND =
      MethodRef.create(AdvisingAppendable.class, "append", CharSequence.class).asNonNullable();

  private static final MethodRef APPEND_CHAR =
      MethodRef.create(AdvisingAppendable.class, "append", char.class).asNonNullable();

  private static final MethodRef SOFT_LIMITED =
      MethodRef.forMethod(AdvisingAppendable.class, "softLimitReached").asCheap();

  static AppendableExpression forLocal(LocalVariable delegate) {
    return new AppendableExpression(delegate, false /* hasSideEffects*/,
        true /* supportsSoftLimiting */);
  }

  static AppendableExpression forStringBuilder(Expression delegate) {
    checkArgument(delegate.resultType().equals(ADVISING_BUILDER_TYPE));
    return new AppendableExpression(delegate, false /* hasSideEffects*/,
        false /* supportsSoftLimiting */);
  }

  static AppendableExpression logger() {
    return new AppendableExpression(MethodRef.RUNTIME_LOGGER.invoke(), false /* hasSideEffects*/,
        false /* supportsSoftLimiting */);
  }

  private final Expression delegate;
  // Whether or not the expression contains operations with side effects (e.g. appends)
  private final boolean hasSideEffects;
  // Whether or not the appendable could ever return true from softLimitReached
  private final boolean supportsSoftLimiting;

  private AppendableExpression(
      Expression delegate, boolean hasSideEffects, boolean supportsSoftLimiting) {
    super(ADVISING_APPENDABLE_TYPE, delegate.features());
    delegate.checkAssignableTo(ADVISING_APPENDABLE_TYPE);
    checkArgument(delegate.isNonNullable(), 
        "advising appendable expressions should always be non null");
    this.delegate = delegate;
    this.hasSideEffects = hasSideEffects;
    this.supportsSoftLimiting = supportsSoftLimiting;
  }

  @Override void doGen(CodeBuilder adapter) {
    delegate.gen(adapter);
  }

  /**
   * Returns a similar {@link AppendableExpression} but with the given (string valued) expression
   * appended to it.
   */
  AppendableExpression appendString(Expression exp) {
    return withNewDelegate(delegate.invoke(APPEND, exp), true);
  }

  /**
   * Returns a similar {@link AppendableExpression} but with the given (char valued) expression
   * appended to it.
   */
  AppendableExpression appendChar(Expression exp) {
    return withNewDelegate(delegate.invoke(APPEND_CHAR, exp), true);
  }

  /** Returns an expression with the result of {@link AppendableExpression#softLimitReached}. */
  Expression softLimitReached() {
    checkArgument(supportsSoftLimiting);
    return delegate.invoke(SOFT_LIMITED);
  }

  @Override AppendableExpression labelStart(Label label) {
    return withNewDelegate(delegate.labelStart(label), this.hasSideEffects);
  }

  @Override Statement toStatement() {
    // .toStatement() by default just generates the expression and adds a 'POP' instruction
    // to clear the stack. However, this is only neccesary when the expression in question has a
    // side effect worth preserving.  If we know that it does not we can just return the empty
    // statement
    if (hasSideEffects) {
      return super.toStatement();
    }
    return Statement.NULL_STATEMENT;
  }

  private AppendableExpression withNewDelegate(Expression newDelegate, boolean hasSideEffects) {
    return new AppendableExpression(newDelegate, hasSideEffects, supportsSoftLimiting);
  }

  /**
   * Returns {@code true} if this expression requires detach logic to be generated based on runtime 
   * calls to {@link AdvisingAppendable#softLimitReached()}.
   */
  boolean supportsSoftLimiting() {
    return supportsSoftLimiting;
  }
}
