/*
 * Copyright 2000-2012 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.
 */

package com.intellij.openapi.projectRoots.impl;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkAdditionalData;
import com.intellij.openapi.projectRoots.SdkTable;
import com.intellij.openapi.projectRoots.SdkType;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

/**
 * @author yole
 */
public class SdkConfigurationUtil {
  public static final String PREDEFINED_PREFIX = " (predefined)";

  private SdkConfigurationUtil() {
  }

  private static FileChooserDescriptor createCompositeDescriptor(final SdkType... sdkTypes) {
    FileChooserDescriptor descriptor0 = sdkTypes[0].getHomeChooserDescriptor();
    FileChooserDescriptor descriptor =
      new FileChooserDescriptor(descriptor0.isChooseFiles(), descriptor0.isChooseFolders(), descriptor0.isChooseJars(),
                                descriptor0.isChooseJarsAsFiles(), descriptor0.isChooseJarContents(), descriptor0.isChooseMultiple()) {

        @Override
        public void validateSelectedFiles(final VirtualFile[] files) throws Exception {
          if (files.length > 0) {
            for (SdkType type : sdkTypes) {
              if (type.isValidSdkHome(files[0].getPath())) {
                return;
              }
            }
          }
          String message = files.length > 0 && files[0].isDirectory()
                           ? ProjectBundle.message("sdk.configure.home.invalid.error", sdkTypes[0].getPresentableName())
                           : ProjectBundle.message("sdk.configure.home.file.invalid.error", sdkTypes[0].getPresentableName());
          throw new Exception(message);
        }
      };
    descriptor.setTitle(descriptor0.getTitle());
    return descriptor;
  }

  public static void addSdk(@NotNull final Sdk sdk) {
    ApplicationManager.getApplication().runWriteAction(new Runnable() {
      @Override
      public void run() {
        SdkTable.getInstance().addSdk(sdk);
      }
    });
  }

  public static void removeSdk(final Sdk sdk) {
    ApplicationManager.getApplication().runWriteAction(new Runnable() {
      @Override
      public void run() {
        SdkTable.getInstance().removeSdk(sdk);
      }
    });
  }

  @Nullable
  public static Sdk setupSdk(final Sdk[] allSdks,
                             final VirtualFile homeDir,
                             final SdkType sdkType,
                             final boolean silent,
                             boolean predefined,
                             @Nullable final SdkAdditionalData additionalData,
                             @Nullable final String customSdkSuggestedName) {
    final SdkImpl sdk;
    try {
      String sdkPath = sdkType.sdkPath(homeDir);

      String sdkName = null;
      if (predefined) {
        sdkName = sdkType.getName() + PREDEFINED_PREFIX;
      }
      else {
        sdkName = customSdkSuggestedName == null
                  ? createUniqueSdkName(sdkType, sdkPath, allSdks)
                  : createUniqueSdkName(customSdkSuggestedName, allSdks);
      }

      sdk = new SdkImpl(sdkName, sdkType);
      sdk.setPredefined(predefined);

      if (additionalData != null) {
        // additional initialization.
        // E.g. some ruby sdks must be initialized before
        // setupSdkPaths() method invocation
        sdk.setSdkAdditionalData(additionalData);
      }

      sdk.setHomePath(sdkPath);
      sdkType.setupSdkPaths(sdk);
    }
    catch (Exception e) {
      if (!silent) {
        Messages.showErrorDialog("Error configuring SDK: " +
                                 e.getMessage() +
                                 ".\nPlease make sure that " +
                                 FileUtil.toSystemDependentName(homeDir.getPath()) +
                                 " is a valid home path for this SDK type.", "Error Configuring SDK");
      }
      return null;
    }
    return sdk;
  }

  /**
   * Tries to create an SDK identified by path; if successful, add the SDK to the global SDK table.
   *
   * @param path    identifies the SDK
   * @param sdkType
   * @param predefined
   * @return newly created SDK, or null.
   */
  @Nullable
  public static Sdk createAndAddSDK(final String path, SdkType sdkType, boolean predefined) {
    VirtualFile sdkHome = ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() {
      @Override
      public VirtualFile compute() {
        return LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
      }
    });
    if (sdkHome != null) {
      final Sdk newSdk = setupSdk(SdkTable.getInstance().getAllSdks(), sdkHome, sdkType, true, predefined, null, null);
      if (newSdk != null) {
        addSdk(newSdk);
      }
      return newSdk;
    }
    return null;
  }

  public static String createUniqueSdkName(SdkType type, String home, final Sdk[] sdks) {
    return createUniqueSdkName(type.suggestSdkName(null, home), sdks);
  }

  public static String createUniqueSdkName(final String suggestedName, final Sdk[] sdks) {
    final Set<String> names = new HashSet<String>();
    for (Sdk jdk : sdks) {
      names.add(jdk.getName());
    }
    String newSdkName = suggestedName;
    int i = 0;
    while (names.contains(newSdkName)) {
      newSdkName = suggestedName + " (" + (++i) + ")";
    }
    return newSdkName;
  }

  public static void selectSdkHome(final SdkType sdkType, @NotNull final Consumer<String> consumer) {
    final FileChooserDescriptor descriptor = sdkType.getHomeChooserDescriptor();
    if (ApplicationManager.getApplication().isUnitTestMode()) {
      Sdk sdk = SdkTable.getInstance().findMostRecentSdkOfType(sdkType);
      if (sdk == null) throw new RuntimeException("No SDK of type " + sdkType + " found");
      consumer.consume(sdk.getHomePath());
      return;
    }
    FileChooser.chooseFiles(descriptor, null, getSuggestedSdkPath(sdkType), new Consumer<List<VirtualFile>>() {
      @Override
      public void consume(final List<VirtualFile> chosen) {
        final String path = chosen.get(0).getPath();
        if (sdkType.isValidSdkHome(path)) {
          consumer.consume(path);
          return;
        }

        final String adjustedPath = sdkType.adjustSelectedSdkHome(path);
        if (sdkType.isValidSdkHome(adjustedPath)) {
          consumer.consume(adjustedPath);
        }
      }
    });
  }

  @Nullable
  public static VirtualFile getSuggestedSdkPath(SdkType sdkType) {
    Collection<String> paths = sdkType.suggestHomePaths();
    if(paths.isEmpty()) {
      return null;
    }

    for (String path : paths) {
      VirtualFile maybeSdkHomePath = LocalFileSystem.getInstance().findFileByPath(path);
      if(maybeSdkHomePath != null) {
        return maybeSdkHomePath;
      }
    }
    return null;
  }
}
