/*
 * Copyright 2000-2016 JetBrains s.r.o.
 *
 * 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.intellij.codeInsight.generation;

import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiExtensibleClass;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

import javax.swing.*;
import java.util.*;

/**
 * @author Dmitry Batkovich <dmitry.batkovich@jetbrains.com>
 */
public class PsiMethodWithOverridingPercentMember extends PsiMethodMember {

  private final int myOverridingPercent;

  public PsiMethodWithOverridingPercentMember(final CandidateInfo info, final int overridingPercent) {
    super(info);
    myOverridingPercent = overridingPercent;
  }

  @Override
  public void renderTreeNode(final SimpleColoredComponent component, final JTree tree) {
    component.append(myOverridingPercent + "% ", SimpleTextAttributes.GRAY_ATTRIBUTES);
    super.renderTreeNode(component, tree);
  }

  @TestOnly
  public int getOverridingPercent() {
    return myOverridingPercent;
  }

  public static final Comparator<PsiMethodMember> COMPARATOR = (e1, e2) -> {
    if (!(e1 instanceof PsiMethodWithOverridingPercentMember)) {
      if (!(e2 instanceof PsiMethodWithOverridingPercentMember)) {
        return e1.equals(e2) ? 0 : -1;
      } else {
        return -1;
      }
    }


    if (!(e2 instanceof PsiMethodWithOverridingPercentMember)) {
      return 1;
    }
    int sub =
      ((PsiMethodWithOverridingPercentMember)e2).myOverridingPercent - ((PsiMethodWithOverridingPercentMember)e1).myOverridingPercent;
    if (sub != 0) return sub;
    return String.CASE_INSENSITIVE_ORDER.compare(e1.getText(), e2.getText());
  };

  @NotNull
  public static PsiMethodWithOverridingPercentMember[] calculateOverridingPercents(@NotNull final Collection<? extends CandidateInfo> candidateInfos) {
    final List<PsiMethodWithOverridingPercentMember> result = new ArrayList<>(candidateInfos.size());
    final Map<String, Collection<PsiClass>> classShortNames2Inheritors = new HashMap<>();
    for (final CandidateInfo candidateInfo : candidateInfos) {
      final PsiMethod method = (PsiMethod)candidateInfo.getElement();
      if (!method.hasModifierProperty(PsiModifier.FINAL) &&
          !method.isConstructor() &&
          !method.isDeprecated() &&
          !EXCLUDED_JAVA_LANG_OBJECT_METHOD_NAMES.contains(method.getName())) {
        final PsiClass containingClass = method.getContainingClass();
        if (containingClass == null) {
          continue;
        }

        final String classShortName = containingClass.getName();

        Collection<PsiClass> allInheritors = classShortNames2Inheritors.get(classShortName);
        if (allInheritors == null) {
          allInheritors = ClassInheritorsSearch.search(containingClass).findAll();
          classShortNames2Inheritors.put(classShortName, allInheritors);
        }

        final int allInheritorsCount = allInheritors.size() - 1;
        if (allInheritorsCount > 0) {
          final int percent = searchForOverridingCount(method, allInheritors) * 100 / allInheritorsCount;
          if (percent > 1) {
            result.add(new PsiMethodWithOverridingPercentMember(candidateInfo, percent));
          }
        }
      }
    }
    return result.toArray(new PsiMethodWithOverridingPercentMember[0]);
  }

  private static int searchForOverridingCount(final PsiMethod method, final Collection<? extends PsiClass> containingClassInheritors) {
    int counter = 0;
    for (final PsiClass inheritor : containingClassInheritors) {
      if (inheritor instanceof PsiExtensibleClass) {
        final List<PsiMethod> ownMethods = ((PsiExtensibleClass)inheritor).getOwnMethods();
        for (PsiMethod ownMethod : ownMethods) {
          if (maybeSuper(method, ownMethod)) {
            counter++;
            break;
          }
        }

      }
    }
    return counter;
  }

  private static boolean maybeSuper(@NotNull final PsiMethod superMethod, @NotNull final PsiMethod method) {
    if (!superMethod.getName().equals(method.getName())) {
      return false;
    }
    final PsiParameterList superMethodParameterList = superMethod.getParameterList();
    final PsiParameterList methodParameterList = method.getParameterList();
    if (superMethodParameterList.getParametersCount() != methodParameterList.getParametersCount()) {
      return false;
    }
    final PsiParameter[] superMethodParameters = superMethodParameterList.getParameters();
    final PsiParameter[] methodParameters = methodParameterList.getParameters();
    for (int i = 0; i < methodParameters.length; i++) {
      if (!StringUtil.equals(getTypeShortName(superMethodParameters[i].getType()), getTypeShortName(methodParameters[i].getType()))) {
        return false;
      }
    }
    return true;
  }

  @Nullable
  private static String getTypeShortName(@NotNull final PsiType type) {
    if (type instanceof PsiPrimitiveType) {
      return ((PsiPrimitiveType)type).getBoxedTypeName();
    }
    if (type instanceof PsiClassType) {
      return ((PsiClassType)type).getClassName();
    }
    if (type instanceof PsiArrayType) {
      return getTypeShortName(((PsiArrayType)type).getComponentType()) + "[]";
    }
    return null;
  }

  private static final Set<String> EXCLUDED_JAVA_LANG_OBJECT_METHOD_NAMES =
    ContainerUtil.newHashSet("hashCode", "finalize", "clone", "equals", "toString");

  @Override
  public String toString() {
    return "PsiMethodWithOverridingPercentMember{" +
           "myOverridingPercent=" + myOverridingPercent + ", myElement=" + getElement() +
           '}';
  }
}
