/*
 * Copyright 2003-2007 Dave Griffith
 *
 * 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.siyeh.ig.methodmetrics;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.siyeh.ig.psiutils.ClassUtils;
import com.siyeh.ig.psiutils.LibraryUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;

import java.util.HashSet;
import java.util.Set;

class CouplingVisitor extends JavaRecursiveElementWalkingVisitor {
  private int m_inClass;
  private final PsiMethod m_method;
  private final boolean m_includeJavaClasses;
  private final boolean m_includeLibraryClasses;
  private final Set<String> m_dependencies = new HashSet<>(10);

  CouplingVisitor(@NotNull PsiMethod method, boolean includeJavaClasses,
                  boolean includeLibraryClasses) {
    m_method = method;
    m_includeJavaClasses = includeJavaClasses;
    m_includeLibraryClasses = includeLibraryClasses;
  }

  @Override
  public void visitVariable(@NotNull PsiVariable variable) {
    super.visitVariable(variable);
    final PsiType type = variable.getType();
    addDependency(type);
  }

  @Override
  public void visitMethod(@NotNull PsiMethod method) {
    super.visitMethod(method);
    final PsiType returnType = method.getReturnType();
    addDependency(returnType);
    addDependenciesForThrowsList(method);
  }

  private void addDependenciesForThrowsList(PsiMethod method) {
    final PsiReferenceList throwsList = method.getThrowsList();
    final PsiClassType[] throwsTypes = throwsList.getReferencedTypes();
    for (PsiClassType throwsType : throwsTypes) {
      addDependency(throwsType);
    }
  }

  @Override
  public void visitNewExpression(@NotNull PsiNewExpression exp) {
    super.visitNewExpression(exp);
    final PsiType classType = exp.getType();
    addDependency(classType);
  }

  @Override
  public void visitClassObjectAccessExpression(@NotNull PsiClassObjectAccessExpression exp) {
    super.visitClassObjectAccessExpression(exp);
    final PsiTypeElement operand = exp.getOperand();
    addDependency(operand);
  }

  @Override
  public void visitClass(@NotNull PsiClass aClass) {
    final int wasInClass = m_inClass++;
    if (wasInClass == 0) {
      super.visitClass(aClass);
    }
    final PsiType[] superTypes = aClass.getSuperTypes();
    for (PsiType superType : superTypes) {
      addDependency(superType);
    }
  }

  @Override
  protected void elementFinished(@NotNull PsiElement element) {
    super.elementFinished(element);
    if (element instanceof PsiClass) {
      m_inClass--;
    }
  }

  @Override
  public void visitTryStatement(@NotNull PsiTryStatement statement) {
    super.visitTryStatement(statement);
    final PsiParameter[] catchBlockParameters = statement.getCatchBlockParameters();
    for (PsiParameter catchBlockParameter : catchBlockParameters) {
      final PsiType catchType = catchBlockParameter.getType();
      addDependency(catchType);
    }
  }

  @Override
  public void visitInstanceOfExpression(@NotNull PsiInstanceOfExpression exp) {
    super.visitInstanceOfExpression(exp);
    final PsiTypeElement checkType = exp.getCheckType();
    addDependency(checkType);
  }

  @Override
  public void visitTypeCastExpression(@NotNull PsiTypeCastExpression exp) {
    super.visitTypeCastExpression(exp);
    final PsiTypeElement castType = exp.getCastType();
    addDependency(castType);
  }

  private void addDependency(PsiTypeElement typeElement) {
    if (typeElement == null) {
      return;
    }
    final PsiType type = typeElement.getType();
    addDependency(type);
  }

  private void addDependency(PsiType type) {
    if (type == null) {
      return;
    }
    final PsiType baseType = type.getDeepComponentType();
    if (ClassUtils.isPrimitive(type)) {
      return;
    }
    final PsiClass containingClass = m_method.getContainingClass();
    if (containingClass == null) {
      return;
    }
    final String qualifiedName = containingClass.getQualifiedName();
    if (qualifiedName == null) {
      return;
    }
    if (baseType.equalsToText(qualifiedName)) {
      return;
    }
    final @NonNls String baseTypeName = baseType.getCanonicalText();
    if (!m_includeJavaClasses &&
        (baseTypeName.startsWith("java.") ||
         baseTypeName.startsWith("javax."))) {
      return;
    }
    if (StringUtil.startsWithConcatenation(baseTypeName, qualifiedName, ".")) {
      return;
    }
    if (!m_includeLibraryClasses) {
      final Project project = m_method.getProject();
      final GlobalSearchScope searchScope = GlobalSearchScope.allScope(project);
      final PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(baseTypeName, searchScope);
      if (aClass == null) {
        return;
      }
      if (LibraryUtil.classIsInLibrary(aClass)) {
        return;
      }
    }
    m_dependencies.add(baseTypeName);
  }

  int getNumDependencies() {
    return m_dependencies.size();
  }
}
