/*
 * Copyright 2012 The Error Prone Authors.
 *
 * 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.errorprone.bugpatterns;

import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;

import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.VariableTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.util.Name;
import javax.lang.model.element.ElementKind;

/** @author scottjohnson@google.com (Scott Johnson) */
@BugPattern(
    name = "ParameterPackage",
    summary = "Method parameter has wrong package",
    severity = ERROR)
public class WrongParameterPackage extends BugChecker implements MethodTreeMatcher {

  private MethodSymbol supermethod;

  @Override
  public Description matchMethod(MethodTree tree, VisitorState state) {
    MethodSymbol method = ASTHelpers.getSymbol(tree);
    if (method == null) {
      return Description.NO_MATCH;
    }
    ClassSymbol classSym = method.enclClass();
    if (classSym == null) {
      return Description.NO_MATCH;
    }
    TypeSymbol superClass = classSym.getSuperclass().tsym;
    if (superClass == null) {
      return Description.NO_MATCH;
    }

    for (Symbol s : superClass.members().getSymbols()) {
      if (s.name.contentEquals(method.name) && s.getKind() == ElementKind.METHOD) {
        MethodSymbol supermethod = (MethodSymbol) s;

        // if this method actually overrides the supermethod, then it's correct and not a match.
        if (method.overrides(supermethod, superClass, state.getTypes(), /* checkResult= */ true)) {
          return Description.NO_MATCH;
        }

        // if this doesn't have the right number of parameters, look at other ones.
        if (supermethod.params().size() != method.params().size()) {
          continue;
        }

        for (int x = 0; x < method.params().size(); x++) {
          Type methodParamType = method.params().get(x).type;
          Type supermethodParamType = supermethod.params().get(x).type;
          if (methodParamType.tsym.name.contentEquals(supermethodParamType.tsym.name)
              && !state.getTypes().isSameType(methodParamType, supermethodParamType)) {
            this.supermethod = supermethod;
            return describe(tree, state);
          }
        }
      }
    }
    return Description.NO_MATCH;
  }

  public Description describe(MethodTree tree, VisitorState state) {
    SuggestedFix.Builder builder = null;

    MethodSymbol method = ASTHelpers.getSymbol(tree);

    if (supermethod == null) {
      throw new IllegalStateException("Matching supermethod was not found");
    }

    for (int x = 0; x < method.params().size(); x++) {
      Type methodParamType = method.params().get(x).type;
      Type supermethodParamType = supermethod.params().get(x).type;
      if (methodParamType.tsym.name.contentEquals(supermethodParamType.tsym.name)
          && !state.getTypes().isSameType(methodParamType, supermethodParamType)) {
        VariableTree param = tree.getParameters().get(x);

        // TODO(user): Name is most likely more qualified than necessary.
        Name replacement = supermethodParamType.tsym.getQualifiedName();
        if (builder == null) {
          builder = SuggestedFix.builder();
        }
        builder.replace(param, replacement + " " + param.getName());
      }
    }

    return (builder != null) ? describeMatch(tree, builder.build()) : describeMatch(tree);
  }
}
