/*******************************************************************************
 * Copyright (c) 2012-2016 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.plugin.languageserver.server.registry;

import io.typefox.lsapi.ServerCapabilities;
import io.typefox.lsapi.services.LanguageServer;

import com.google.inject.Inject;
import com.google.inject.Singleton;

import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.project.server.FolderEntry;
import org.eclipse.che.api.project.server.ProjectManager;
import org.eclipse.che.api.project.server.VirtualFileEntry;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.plugin.languageserver.server.exception.LanguageServerException;
import org.eclipse.che.plugin.languageserver.server.launcher.LanguageServerLauncher;
import org.eclipse.che.plugin.languageserver.shared.ProjectExtensionKey;
import org.eclipse.che.plugin.languageserver.shared.model.LanguageDescription;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import static com.google.common.io.Files.getFileExtension;
import static org.eclipse.che.plugin.languageserver.shared.ProjectExtensionKey.createProjectKey;

@Singleton
public class LanguageServerRegistryImpl implements LanguageServerRegistry, ServerInitializerObserver {
    public final static String PROJECT_FOLDER_PATH = "/projects";

    /**
     * Available {@link LanguageServerLauncher} by extension.
     */
    private final ConcurrentHashMap<String, List<LanguageServerLauncher>> extensionToLauncher;

    /**
     * Started {@link LanguageServer} by project.
     */
    private final ConcurrentHashMap<ProjectExtensionKey, LanguageServer> projectToServer;

    private final ProjectManager         projectManager;
    private final ServerInitializer      initializer;

    @Inject
    public LanguageServerRegistryImpl(Set<LanguageServerLauncher> languageServerLaunchers,
                                      ProjectManager projectManager,
                                      ServerInitializer initializer) {
        this.projectManager = projectManager;
        this.initializer = initializer;
        this.extensionToLauncher = new ConcurrentHashMap<>();
        this.projectToServer = new ConcurrentHashMap<>();
        this.initializer.addObserver(this);

        for (LanguageServerLauncher launcher : languageServerLaunchers) {
            for (String extension : launcher.getLanguageDescription().getFileExtensions()) {
                extensionToLauncher.putIfAbsent(extension, new ArrayList<>());
                extensionToLauncher.get(extension).add(launcher);
            }
        }
    }

    @Override
    public LanguageServer findServer(String fileUri) throws LanguageServerException {
        String path = URI.create(fileUri).getPath();

        String extension = getFileExtension(path);
        String projectPath = extractProjectPath(path);

        return findServer(extension, projectPath);
    }

    @Nullable
    protected LanguageServer findServer(String extension, String projectPath) throws LanguageServerException {
        ProjectExtensionKey projectKey = createProjectKey(projectPath, extension);

        for (LanguageServerLauncher launcher : extensionToLauncher.get(extension)) {
            if (!projectToServer.containsKey(projectKey)) {
                synchronized (launcher) {
                    if (!projectToServer.containsKey(projectKey)) {
                        LanguageServer server = initializer.initialize(launcher, projectPath);
                        projectToServer.put(projectKey, server);
                    }
                }
            }
            return projectToServer.get(projectKey);
        }

        return null;
    }


    @Override
    public List<LanguageDescription> getSupportedLanguages() {
        return extensionToLauncher.values()
                                  .stream()
                                  .flatMap(Collection::stream)
                                  .map(LanguageServerLauncher::getLanguageDescription)
                                  .collect(Collectors.toList());
    }

    @Override
    public Map<ProjectExtensionKey, LanguageServerDescription> getInitializedLanguages() {
        Map<LanguageServer, LanguageServerDescription> initializedServers = initializer.getInitializedServers();
        return projectToServer.entrySet()
                              .stream()
                              .collect(Collectors.toMap(Map.Entry::getKey, e -> initializedServers.get(e.getValue())));
    }

    protected String extractProjectPath(String filePath) throws LanguageServerException {
        FolderEntry root;
        try {
            root = projectManager.getProjectsRoot();
        } catch (ServerException e) {
            throw new LanguageServerException("Project not found for " + filePath, e);
        }

        if (!filePath.startsWith(PROJECT_FOLDER_PATH)) {
            throw new LanguageServerException("Project not found for " + filePath);
        }

        VirtualFileEntry fileEntry;
        try {
            fileEntry = root.getChild(filePath.substring(PROJECT_FOLDER_PATH.length() + 1));
        } catch (ServerException e) {
            throw new LanguageServerException("Project not found for " + filePath, e);
        }

        if (fileEntry == null) {
            throw new LanguageServerException("Project not found for " + filePath);
        }

        return PROJECT_FOLDER_PATH + fileEntry.getProject();
    }

    @Override
    public void onServerInitialized(LanguageServer server,
                                    ServerCapabilities capabilities,
                                    LanguageDescription languageDescription,
                                    String projectPath) {
        for (String ext : languageDescription.getFileExtensions()) {
            projectToServer.put(createProjectKey(projectPath, ext), server);
        }
    }
}

@@ -706,16 +706,51 @@
 Inject;%0A
+import com.google.inject.Provider;%0A
 import c
@@ -2293,16 +2293,25 @@
 e final 
+Provider%3C
 ProjectM
@@ -2316,24 +2316,17 @@
 tManager
-        
+%3E
  project
@@ -2328,24 +2328,32 @@
 ojectManager
+Provider
 ;%0A    privat
@@ -2383,16 +2383,18 @@
 er      
+  
 initiali
@@ -2536,24 +2536,33 @@
             
+Provider%3C
 ProjectManag
@@ -2563,16 +2563,17 @@
 tManager
+%3E
  project
@@ -2579,16 +2579,24 @@
 tManager
+Provider
 ,%0A      
@@ -2687,16 +2687,24 @@
 tManager
+Provider
  = proje
@@ -2712,16 +2712,24 @@
 tManager
+Provider
 ;%0A      
@@ -5336,16 +5336,30 @@
 tManager
+Provider.get()
 .getProj

Use provider to prevent circular dependncy (#2502)