/*
 * 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.
 */

/*
 * @author max
 */
package com.intellij.internal;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;

import java.util.List;

public class ExpressionStatisticsAction extends AnAction {
  @Override
  public void actionPerformed(AnActionEvent e) {
    final Project project = e.getProject();
    assert project != null;

    final Data topLevelData = new Data();
    final Data subData = new Data();

    final VirtualFile dir = CommonDataKeys.VIRTUAL_FILE.getData(e.getDataContext());
    assert dir != null;

    final List<VirtualFile> javaFiles = collectJavaFiles(dir, project);

    if (!ApplicationManagerEx.getApplicationEx().runProcessWithProgressSynchronouslyInReadAction(project, "Traversing PSI", true, "Cancel", null, new Runnable() {
      @Override
      public void run() {
        final PsiManager psiManager = PsiManager.getInstance(project);
        ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
        indicator.setIndeterminate(false);
        for (int i = 0; i < javaFiles.size(); i++) {
          VirtualFile file = javaFiles.get(i);
          indicator.setText2(file.getPath());
          indicator.setFraction((double)i / javaFiles.size());
          PsiFile psiFile = psiManager.findFile(file);
          if (psiFile instanceof PsiJavaFile) {
            psiFile.accept(new PsiRecursiveElementWalkingVisitor() {
              @Override
              public void visitElement(PsiElement element) {
                if (element instanceof PsiIdentifier) {
                  int offset = element.getTextRange().getStartOffset();
                  PsiExpression minExpression = PsiTreeUtil.getParentOfType(element, PsiExpression.class);
                  if (minExpression != null && minExpression.getTextRange().getStartOffset() == offset) {
                    PsiExpression maxExpression = minExpression;
                    while (true) {
                      PsiExpression nextExpression = PsiTreeUtil.getParentOfType(maxExpression, PsiExpression.class, true);
                      if (nextExpression == null || nextExpression.getTextRange().getStartOffset() != offset) break;
                      maxExpression = nextExpression;
                    }
                    collectExpressionData(minExpression, maxExpression, topLevelData, subData);
                  }
                }

                super.visitElement(element);
              }
            });
          }
        }
      }
    })) {
      return;
    }

    Messages.showMessageDialog("Top-level: " + topLevelData.toString() + "\n\n" + "Sub-expressions: " + subData.toString(), "Expression Statistics", null);
  }

  @NotNull
  private static List<VirtualFile> collectJavaFiles(VirtualFile dir, Project project) {
    final List<VirtualFile> javaFiles = ContainerUtil.newArrayList();
    ProjectFileIndex.SERVICE.getInstance(project).iterateContentUnderDirectory(dir, file -> {
      if (file.getName().endsWith(".java")) {
        javaFiles.add(file);
      }
      return true;
    });
    return javaFiles;
  }

  private static void collectExpressionData(PsiExpression minExpression, PsiExpression maxExpression, Data topLevelData, Data subExpressionData) {
    Data data;
    if (minExpression == maxExpression || maxExpression instanceof PsiMethodCallExpression && minExpression == ((PsiMethodCallExpression)maxExpression).getMethodExpression()) {
      data = topLevelData;
    } else {
      data = subExpressionData;
    }

    if (minExpression instanceof PsiClassObjectAccessExpression || minExpression instanceof PsiThisExpression || minExpression instanceof PsiSuperExpression) {
      data.classes++;
      return;
    }

    if (!(minExpression instanceof PsiJavaCodeReferenceElement)) {
      data.other++;
      return;
    }

    classifyTarget(data, ((PsiJavaCodeReferenceElement)minExpression).resolve());
  }

  private static void classifyTarget(Data data, PsiElement target) {
    if (target instanceof PsiLocalVariable) {
      data.localVars++;
    }
    else if (target instanceof PsiParameter) {
      data.parameters++;
    }
    else if (target instanceof PsiMethod) {
      data.methods++;
    }
    else if (target instanceof PsiClass) {
      data.classes++;
    }
    else if (target instanceof PsiPackage) {
      data.packages++;
    }
    else if (target instanceof PsiField) {
      data.fields++;
    }
    else {
      data.other++;
    }
  }

  private static class Data {
    int localVars;
    int parameters;
    int methods;
    int classes;
    int fields;
    int packages;
    int other;

    @Override
    public String toString() {
      return "localVars=" + localVars +
             "\nparameters=" + parameters +
             "\nmethods=" + methods +
             "\nfields=" + fields +
             "\nclasses=" + classes +
             "\npackages=" + packages +
             "\nother=" + other +
             "\ntotal=" + (localVars + parameters + methods + fields + classes + packages + other);
    }
  }


}

