package fr.adrienbrault.idea.symfony2plugin.action;

import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.ide.actions.GotoActionBase;
import com.intellij.ide.util.gotoByName.ChooseByNamePopup;
import com.intellij.navigation.ChooseByNameContributor;
import com.intellij.navigation.ChooseByNameContributorEx;
import com.intellij.navigation.NavigationItem;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.Processor;
import com.intellij.util.indexing.FindSymbolParameters;
import com.intellij.util.indexing.IdFilter;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons;
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
import fr.adrienbrault.idea.symfony2plugin.action.model.SymfonySymbolSearchModel;
import fr.adrienbrault.idea.symfony2plugin.dic.ContainerService;
import fr.adrienbrault.idea.symfony2plugin.doctrine.EntityHelper;
import fr.adrienbrault.idea.symfony2plugin.doctrine.EntityReference;
import fr.adrienbrault.idea.symfony2plugin.navigation.NavigationItemEx;
import fr.adrienbrault.idea.symfony2plugin.routing.Route;
import fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper;
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigExtension;
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigExtensionParser;
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import fr.adrienbrault.idea.symfony2plugin.util.SymfonyCommandUtil;
import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyCommand;
import icons.TwigIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

/**
 * @author Daniel Espendiller <[email protected]>
 */
public class SymfonySymbolSearchAction extends GotoActionBase {

    @Override
    protected void gotoActionPerformed(AnActionEvent paramAnActionEvent) {
        FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.file");
        Project localProject = paramAnActionEvent.getData(CommonDataKeys.PROJECT);
        if (localProject != null) {
            SymfonySymbolSearchModel searchModel = new SymfonySymbolSearchModel(localProject, new ChooseByNameContributor[] { new Symfony2NavigationContributor(localProject) });
            showNavigationPopup(paramAnActionEvent, searchModel, new MyGotoCallback(), null, true);
        }
    }

    @Override
    public void update(AnActionEvent event) {
        super.update(event);

        Project project = event.getData(CommonDataKeys.PROJECT);

        boolean enabled = Symfony2ProjectComponent.isEnabled(project);

        event.getPresentation().setVisible(enabled);
        event.getPresentation().setEnabled(enabled);

    }

    private static class Symfony2NavigationContributor implements ChooseByNameContributorEx, DumbAware {
        @NotNull
        final private Project project;

        private ContainerCollectionResolver.ServiceCollector serviceCollector;
        private Map<String, Set<VirtualFile>> templateMap;
        private Map<String, Route> routes;
        private Set<String> twigMacroSet;
        private Map<String, LookupElement> lookupElements;

        private Symfony2NavigationContributor(@NotNull Project project) {
            this.project = project;
        }

        private ContainerCollectionResolver.ServiceCollector getServiceCollector() {
            if(this.serviceCollector == null) {
                this.serviceCollector = ContainerCollectionResolver.ServiceCollector.create(this.project);
            }

            return this.serviceCollector;
        }

        private Map<String, Set<VirtualFile>> getTemplateMap() {
            if(this.templateMap == null) {
                this.templateMap = TwigUtil.getTemplateMap(this.project, true);
            }

            return this.templateMap;
        }

        private Map<String, Route> getRoutes() {
            if(this.routes == null) {
                this.routes = RouteHelper.getAllRoutes(project);
            }

            return this.routes;
        }

        private Set<String> getTwigMacroSet() {
            if(this.twigMacroSet == null) {
                this.twigMacroSet = TwigUtil.getTwigMacroSet(this.project);
            }

            return this.twigMacroSet;
        }

        private Map<String, LookupElement> getModelLookupElements() {

            if(this.lookupElements == null) {
                List<LookupElement> modelLookupElements = EntityReference.getModelLookupElements(this.project);

                this.lookupElements = new HashMap<>();
                for(LookupElement lookupElement: modelLookupElements) {
                    this.lookupElements.put(lookupElement.getLookupString(), lookupElement);
                }

            }

            return this.lookupElements;
        }

        @Override
        public void processNames(@NotNull Processor<? super String> processor, @NotNull GlobalSearchScope scope, @Nullable IdFilter filter) {
            for(String name: getServiceCollector().getServices().keySet()) {
                processor.process(name);
            }

            for(String templateName: getTemplateMap().keySet()) {
                processor.process(templateName);
            }

            for(String name: getRoutes().keySet()) {
                processor.process(name);
            }

            for(Map.Entry<String, Route> entry: getRoutes().entrySet()) {
                processor.process(entry.getKey());
                String path = entry.getValue().getPath();
                if(path != null) {
                    processor.process(path);
                }
            }

            for(String name: getTwigMacroSet()) {
                processor.process(name);
            }

            for(String name: getModelLookupElements().keySet()) {
                processor.process(name);
            }

            for(SymfonyCommand command: SymfonyCommandUtil.getCommands(project)) {
                processor.process(command.getName());
            }

            // Twig Extensions
            for (Map<String, TwigExtension> extensionMap : Arrays.asList(TwigExtensionParser.getFilters(project), TwigExtensionParser.getFunctions(project))) {
                for(String twigFilter: extensionMap.keySet()) {
                    processor.process(twigFilter);
                }
            }
        }

        @Override
        public void processElementsWithName(@NotNull String name, @NotNull Processor<? super NavigationItem> processor, @NotNull FindSymbolParameters parameters) {

            for(ContainerService containerService: getServiceCollector().collect()) {
                if(containerService.getName().equals(name)) {

                    String serviceClass = getServiceCollector().resolve(name);
                    if (serviceClass != null) {
                        PhpClass phpClass = PhpElementsUtil.getClassInterface(this.project, serviceClass);
                        if(phpClass != null) {
                            processor.process(new NavigationItemEx(phpClass, containerService.getName(), containerService.isWeak() ? Symfony2Icons.SERVICE_PRIVATE_OPACITY : Symfony2Icons.SERVICE, "Service"));
                        }
                    }

                }
            }

            // @TODO name filter
            if(getTemplateMap().containsKey(name)) {
                for (PsiFile psiFile : TwigUtil.getTemplatePsiElements(project, name)) {
                    processor.process(new NavigationItemEx(psiFile, name, psiFile.getFileType().getIcon(), "Template"));
                }
            }

            Set<String> controllers = new HashSet<>();
            if(getRoutes().containsKey(name)) {
                String controllerName = getRoutes().get(name).getController();
                if(controllerName != null) {
                    controllers.add(controllerName);
                }
            }

            // route path: /foo/bar
            for (Route route : getRoutes().values()) {
                if(!name.equals(route.getPath())) {
                    continue;
                }

                String controller = route.getController();
                if(controller != null) {
                    controllers.add(controller);
                }
            }

            if(controllers.size() > 0) {
                for (String controller : controllers) {
                    for(PsiElement psiElement: RouteHelper.getMethodsOnControllerShortcut(this.project, controller)) {
                        processor.process(new NavigationItemEx(psiElement, name, Symfony2Icons.ROUTE, "Route"));
                    }
                }
            }

            if(getTwigMacroSet().contains(name)) {
                for(PsiElement macroTarget: TwigUtil.getTwigMacroTargets(project, name)) {
                    processor.process(new NavigationItemEx(macroTarget, name, TwigIcons.TwigFileIcon, "Macro"));
                }
            }

            if(getModelLookupElements().containsKey(name)) {
                PsiElement[] psiElements = EntityHelper.getModelPsiTargets(this.project, name);

                getModelLookupElements().get(name).getLookupString();
                for(PsiElement target: psiElements) {
                    processor.process(new NavigationItemEx(target, name, target.getIcon(0), "Entity"));
                }
            }

            for (SymfonyCommand symfonyCommand : SymfonyCommandUtil.getCommands(project)) {
                if(symfonyCommand.getName().equals(name)) {
                    processor.process(new NavigationItemEx(symfonyCommand.getPsiElement(), name, Symfony2Icons.SYMFONY, "Command"));
                }
            }

            // Twig Extensions
            for (Map<String, TwigExtension> extensionMap : Arrays.asList(TwigExtensionParser.getFilters(project), TwigExtensionParser.getFunctions(project))) {
                for(Map.Entry<String, TwigExtension> twigFunc: extensionMap.entrySet()) {
                    if(twigFunc.getKey().equals(name)) {
                        TwigExtension twigExtension = twigFunc.getValue();
                        PsiElement extensionTarget = TwigExtensionParser.getExtensionTarget(project, twigExtension);
                        if(extensionTarget != null) {
                            processor.process(new NavigationItemEx(extensionTarget, name, TwigExtensionParser.getIcon(twigExtension.getTwigExtensionType()), twigExtension.getTwigExtensionType().toString()));
                        }
                    }
                }
            }
        }

        @NotNull
        @Override
        public String[] getNames(Project project, boolean includeNonProjectItems) {
            return new String[0];
        }

        @NotNull
        @Override
        public NavigationItem[] getItemsByName(String name, String pattern, Project project, boolean includeNonProjectItems) {
            return new NavigationItem[0];
        }
    }

    static class MyGotoCallback extends GotoActionBase.GotoActionCallback<FileType> {
        @Override
        public void elementChosen(ChooseByNamePopup popup, Object element) {
            if(element instanceof NavigationItem) {
                ((NavigationItem) element).navigate(true);
            }
        }
    }
}