package com.chrisfolger.needsmoredojo.intellij.actions;

import com.chrisfolger.needsmoredojo.core.amd.filesystem.DojoModuleFileResolver;
import com.chrisfolger.needsmoredojo.core.amd.objectmodel.cycledetection.CyclicDependencyDetector;
import com.chrisfolger.needsmoredojo.core.amd.objectmodel.cycledetection.DependencyNode;
import com.chrisfolger.needsmoredojo.core.amd.objectmodel.cycledetection.DetectionResult;
import com.chrisfolger.needsmoredojo.core.settings.DojoSettings;
import com.chrisfolger.needsmoredojo.intellij.toolwindows.FindCyclicDependenciesToolWindow;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.openapi.wm.ToolWindowType;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import org.apache.log4j.Logger;

import javax.swing.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * This action runs a task that scans the dependency graph for your sources. It will list cycles in the graph
 * in a separate tool window at the bottom of the IDE.
 */
public class FindCyclicDependenciesAction extends JavaScriptAction
{
    private Logger logger = Logger.getLogger(FindCyclicDependenciesAction.class);

    private void updateToolWindow(int count, final Project project, final CyclicDependencyDetector detector)
    {
        final int finalCount = count;
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                ToolWindowManager.getInstance(project).unregisterToolWindow("Find Cyclic AMD Dependencies");
                ToolWindow window = ToolWindowManager.getInstance(project).registerToolWindow("Find Cyclic AMD Dependencies", true, ToolWindowAnchor.BOTTOM);
                window.setTitle("Find Cyclic AMD Dependencies");
                window.setDefaultState(ToolWindowAnchor.BOTTOM, ToolWindowType.DOCKED, null);
                window.show(null);
                window.activate(null);

                Map<String, List<String>> incriminatingModules = detector.getIncriminatingModules();
                new FindCyclicDependenciesToolWindow().createContent(project, window, incriminatingModules, finalCount);
            }
        });
    }

    @Override
    protected boolean fileAgnostic()
    {
        return true;
    }

    @Override
    public void update(AnActionEvent e) {
        if(e.getProject() == null)
        {
            e.getPresentation().setEnabled(false);
            return;
        }

        if(!ServiceManager.getService(e.getProject(), DojoSettings.class).isNeedsMoreDojoEnabled())
        {
            e.getPresentation().setEnabled(false);
            return;
        }
        else
        {
            e.getPresentation().setEnabled(true);
        }
    }

    @Override
    public void actionPerformed(final AnActionEvent e)
    {
        if(e.getProject() == null)
        {
            return;
        }

        final ProgressManager instance = ProgressManager.getInstance();

        instance.runProcessWithProgressSynchronously(new Runnable() {
            @Override
            public void run() {
                try
                {
                    instance.getProgressIndicator().setIndeterminate(true);

                    final CyclicDependencyDetector detector = new CyclicDependencyDetector();
                    Collection<VirtualFile> filesToSearch = new DojoModuleFileResolver().getAllDojoProjectSourceFiles(e.getProject());

                    int count = 0;
                    for (VirtualFile file : filesToSearch)
                    {
                        if (DojoModuleFileResolver.isInDojoSources(file.getPath()))
                        {
                            continue;
                        }

                        try
                        {
                            PsiFile psiFile = PsiManager.getInstance(e.getProject()).findFile(file);
                            DependencyNode cycle = detector.addDependenciesOfFile(psiFile, psiFile.getProject(), psiFile, null, null, false);

                            if (cycle != null)
                            {
                                DetectionResult cycleDetectionResult = detector.getCycleDetectionResult(cycle);
                                detector.updateIncriminatingModules(cycleDetectionResult.getDependencies(), cycleDetectionResult.getCyclePath());
                                count++;
                            }
                        }
                        catch(ProcessCanceledException exception)
                        {
                            new Notification("needsmoredojo", "Find Circular Dependencies", "Find Circular Dependencies action was canceled", NotificationType.INFORMATION).notify(e.getProject());
                            return;
                        }
                        catch (Exception ex)
                        {
                            logger.error(ex, ex);
                        }
                    }

                    if (count == 0)
                    {
                        new Notification("needsmoredojo", "Find Circular Dependencies", "No cycles were found in the dependency graph", NotificationType.INFORMATION).notify(e.getProject());
                    }
                    else
                    {
                        updateToolWindow(count, e.getProject(), detector);
                    }
                }
                catch(ProcessCanceledException exception)
                {
                    new Notification("needsmoredojo", "Find Circular Dependencies", "Find Circular Dependencies action was canceled", NotificationType.INFORMATION).notify(e.getProject());
                }
            }
        }, "Searching for circular dependencies", true, e.getProject());
    }
}