/*
 * Copyright 2006 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.gwt.dev.cfg;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.javac.CompilationProblemReporter;
import com.google.gwt.thirdparty.guava.common.base.Objects;

/**
 * A deferred binding condition to determine whether the type being rebound is
 * assignment-compatible with a particular type.
 */
// TODO(stalcup): guard against attempts to replace classes that have special prototype handling
// like String and Array.
public class ConditionWhenTypeAssignableTo extends Condition {

  private static boolean warnedMissingValidationJar = false;

  private final String assignableToTypeName;

  public ConditionWhenTypeAssignableTo(String assignableToTypeName) {
    this.assignableToTypeName = assignableToTypeName;
  }

  @Override
  public boolean equals(Object object) {
    if (object instanceof ConditionWhenTypeAssignableTo) {
      ConditionWhenTypeAssignableTo that = (ConditionWhenTypeAssignableTo) object;
      return Objects.equal(this.assignableToTypeName, that.assignableToTypeName);
    }
    return false;
  }

  public String getAssignableToTypeName() {
    return assignableToTypeName;
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(assignableToTypeName);
  }

  @Override
  public String toString() {
    return "<when-assignable class='" + assignableToTypeName + "'/>";
  }

  @Override
  protected boolean doEval(TreeLogger logger, DeferredBindingQuery query)
      throws UnableToCompleteException {
    TypeOracle typeOracle = query.getTypeOracle();
    String testType = query.getTestType();
    JClassType fromType = typeOracle.findType(testType);
    if (fromType == null) {
      CompilationProblemReporter.logErrorTrace(logger, TreeLogger.ERROR,
          query.getCompilationState().getCompilerContext(), testType, true);
      throw new UnableToCompleteException();
    }

    JClassType toType = typeOracle.findType(assignableToTypeName);
    if (toType == null) {
      // If we don't know the type, it can't be assignable to it.
      // This isn't a strict failure case because stale rules can reference
      // types that have been deleted.
      //
      TreeLogger.Type level = TreeLogger.WARN;
      if (shouldSuppressWarning(logger, assignableToTypeName)) {
        // Suppress validation related errors
        level = TreeLogger.DEBUG;
      }
      logger.log(level, "Unknown type '" + assignableToTypeName
          + "' specified in deferred binding rule", null);
      return false;
    }

    if (fromType.isAssignableTo(toType)) {
      return true;
    } else {
      return false;
    }
  }

  @Override
  protected String getEvalAfterMessage(String testType, boolean result) {
    if (result) {
      return "Yes, the requested type was assignable";
    } else {
      return "No, the requested type was not assignable";
    }
  }

  @Override
  protected String getEvalBeforeMessage(String testType) {
    return toString();
  }

  /**
   * Suppress multiple validation related messages and replace with a hint.
   *
   * @param typeName fully qualified type name to check for filtering
   */
  // TODO(zundel): Can be removed when javax.validation is included in the JRE
  private boolean shouldSuppressWarning(TreeLogger logger, String typeName) {
    if (typeName.startsWith("javax.validation.")
        || typeName.startsWith("com.google.gwt.validation.")
        || typeName.startsWith("com.google.gwt.editor.client")) {
      if (!warnedMissingValidationJar) {
        warnedMissingValidationJar = true;
        logger.log(TreeLogger.WARN, "Detected warnings related to '" + typeName + "'. "
            + "  Are validation-api-<version>.jar and validation-api-<version>-sources.jar on the classpath?");
        logger.log(TreeLogger.INFO, "Specify -logLevel DEBUG to see all errors.");
        // Show the first error that matches
        return false;
      }
      // Suppress subsequent errors that match
      return true;
    }
    return false;
  }
}
