/*
 * Copyright 2000-2015 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.util;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.JBColor;
import com.intellij.ui.ListCellRendererWrapper;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.JdkBundle;
import com.intellij.util.JdkBundleList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Locale;

/**
 * @author denis
 */
public class SwitchBootJdkAction extends AnAction implements DumbAware {
  @NotNull private static final Logger LOG = Logger.getInstance("#com.intellij.ide.actions.SwitchBootJdkAction");
  @NotNull private static final String productJdkConfigFileName =
    getExecutable() + (SystemInfo.isWindows ? ((SystemInfo.is64Bit) ? "64.exe.jdk" : ".exe.jdk") : ".jdk");

  @Nullable private static final String pathsSelector = PathManager.getPathsSelector();
  @NotNull private static final File productJdkConfigDir = new File(pathsSelector != null ?
                                                                    PathManager.getDefaultConfigPathFor(pathsSelector) :
                                                                    PathManager.getConfigPath());

  @NotNull private static final File productJdkConfigFile = new File(productJdkConfigDir, productJdkConfigFileName);

  @NotNull private static final File bundledJdkFile = getBundledJDKFile();



  @NotNull
  private static File getBundledJDKFile() {
    return new File(SystemInfo.isMac ? "jdk" : "jre" + ((SystemInfo.is64Bit)? "64" : ""));
  }

  @Override
  public void update(AnActionEvent e) {
    e.getPresentation().setText("Switch Boot JDK");
  }

  @Override
  public void actionPerformed(AnActionEvent event) {

    if (!productJdkConfigDir.exists()) {
      try {
        if (!productJdkConfigDir.mkdirs()) {
          LOG.error("Could not create " + productJdkConfigDir + " productJdkConfigDir");
          return;
        }
        if (!productJdkConfigFile.exists()) {
          if (!productJdkConfigFile.createNewFile()) {
            LOG.error("Could not create " + productJdkConfigFileName + " productJdkConfigFile");
            return;
          }
        }
      }
      catch (IOException e) {
        LOG.error(e);
        return;
      }
    }

    SwitchBootJdkDialog dialog = new SwitchBootJdkDialog();
    if (dialog.showAndGet()) {
      File selectedJdkBundleFile = dialog.getSelectedFile();
      if (selectedJdkBundleFile == null) {
        LOG.error("SwitchBootJdkDialog returns null selection");
        return;
      }

      FileWriter fooWriter = null;
      try {
        //noinspection IOResourceOpenedButNotSafelyClosed
        fooWriter = new FileWriter(productJdkConfigFile, false);
        fooWriter.write(selectedJdkBundleFile.getPath());
      }
      catch (IOException e) {
        LOG.error(e);
      }
      finally {
        try {
          if (fooWriter != null) {
            fooWriter.close();
          }
        }
        catch (IOException e) {
          LOG.error(e);
        }
      }
      ApplicationManager.getApplication().restart();
    }
  }

  private static class SwitchBootJdkDialog extends DialogWrapper {

    static class JdkBundleItem {
      @Nullable private JdkBundle myBundle;

      public JdkBundleItem(@Nullable JdkBundle bundle) {
        myBundle = bundle;
      }

      @Nullable public JdkBundle getBundle() {
        return myBundle;
      }
    }

    @NotNull private final ComboBox myComboBox;

    private SwitchBootJdkDialog() {
      super((Project)null, false);

      final JdkBundleList pathsList = findJdkPaths();

      myComboBox = new ComboBox();

      final DefaultComboBoxModel<JdkBundleItem> model = new DefaultComboBoxModel<>();

      for (JdkBundle jdkBundlePath : pathsList.toArrayList()) {
        //noinspection unchecked
        model.addElement(new JdkBundleItem(jdkBundlePath));
      }

      model.addElement(new JdkBundleItem(null));

      //noinspection unchecked
      myComboBox.setModel(model);

      //noinspection unchecked
      myComboBox.setRenderer(new ListCellRendererWrapper() {
        @Override
        public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) {
          JdkBundle jdkBundleDescriptor = ((JdkBundleItem)value).getBundle();
          if (jdkBundleDescriptor != null) {
            if (jdkBundleDescriptor.isBoot()) {
              setForeground(JBColor.DARK_GRAY);
            }
            setText(jdkBundleDescriptor.getVisualRepresentation());
          }
          else {
            setText("...");
          }
        }
      });

      myComboBox.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
          if (myComboBox.getSelectedItem() == null) {
            LOG.error("Unexpected nullable selection");
            return;
          }

          JdkBundleItem item = (JdkBundleItem)myComboBox.getSelectedItem();

          if (item.getBundle() == null) {
            FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, false, false, false, false) {
              JdkBundle selectedBundle;

              @Override
              public boolean isFileSelectable(final VirtualFile file) {
                selectedBundle = null;
                if (!super.isFileSelectable(file)) return false;
                // allow selection of JDK of any arch, so that to warn about possible arch mismatch during validation
                JdkBundle bundle = JdkBundle.createBundle(new File(file.getPath()), false, false, false);
                if (bundle == null) return false;
                Version version = bundle.getVersion();
                selectedBundle = bundle;
                return version != null && !version.lessThan(JDK8_VERSION.major, JDK8_VERSION.minor, JDK8_VERSION.bugfix);
              }

              @Override
              public void validateSelectedFiles(VirtualFile[] files) throws Exception {
                super.validateSelectedFiles(files);
                assert files.length == 1;
                if (selectedBundle == null) {
                  throw new Exception("Invalid JDK bundle!");
                }
                if (selectedBundle.getBitness() != JdkBundle.runtimeBitness) {
                  //noinspection SpellCheckingInspection
                  throw new Exception("JDK arch mismatch! Your IDE's arch is " + JdkBundle.runtimeBitness);
                }
              }
            };

            FileChooser.chooseFiles(descriptor, null, null, files -> {
              if (files.size() > 0) {
                final File jdkFile = new File(files.get(0).getPath());
                JdkBundle selectedJdk = pathsList.getBundle(jdkFile.getPath());
                JdkBundleItem jdkBundleItem;
                if (selectedJdk == null) {
                  selectedJdk = JdkBundle.createBundle(jdkFile, false, false);
                  if (selectedJdk != null) {
                    pathsList.addBundle(selectedJdk, true);
                    if (model.getSize() > 0) {
                      jdkBundleItem = new JdkBundleItem(selectedJdk);
                      model.insertElementAt(jdkBundleItem, model.getSize() - 1);
                    }
                    else {
                      jdkBundleItem = new JdkBundleItem(selectedJdk);
                      model.addElement(jdkBundleItem);
                    }
                  }
                  else {
                    LOG.error("Cannot create bundle for path: " + jdkFile.getPath());
                    return;
                  }
                } else {
                  jdkBundleItem = new JdkBundleItem(selectedJdk);
                }
                myComboBox.setSelectedItem(jdkBundleItem);
              }
            });
          }

          item = (JdkBundleItem)myComboBox.getSelectedItem();

          if (item == null || item.getBundle() == null) {
            item = model.getElementAt(0);
            myComboBox.setSelectedItem(item);
          }

          setOKActionEnabled(item.getBundle() != null && !item.getBundle().isBoot());
        }
      });
      myComboBox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);

      setTitle("Switch IDE Boot JDK");
      setOKActionEnabled(false); // First item is a boot jdk
      init();
    }

    @Nullable
    @Override
    protected JComponent createNorthPanel() {
      return new JBLabel("Select Boot JDK");
    }

    @Nullable
    @Override
    protected JComponent createCenterPanel() {
      return myComboBox;
    }

    @Nullable
    @Override
    public JComponent getPreferredFocusedComponent() {
      return myComboBox;
    }

    @Nullable
    public File getSelectedFile() {
      final JdkBundleItem item = (JdkBundleItem)myComboBox.getSelectedItem();
      if (item == null) return null;

      final JdkBundle bundle = item.getBundle();

      return bundle != null ? bundle.getLocation() : null;
    }
  }

  private static final String STANDARD_JDK_LOCATION_ON_MAC_OS_X = "/Library/Java/JavaVirtualMachines/";
  private static final String [] STANDARD_JVM_LOCATIONS_ON_LINUX = new String[] {
    "/usr/lib/jvm/", // Ubuntu
    "/usr/java/"     // Fedora
  };
  private static final String STANDARD_JVM_X64_LOCATIONS_ON_WINDOWS = "Program Files/Java";

  private static final String STANDARD_JVM_X86_LOCATIONS_ON_WINDOWS = "Program Files (x86)/Java";

  private static final Version JDK8_VERSION = new Version(1, 8, 0);

  @NotNull
  public static JdkBundleList findJdkPaths() {
    JdkBundle bootJdk = JdkBundle.createBoot();

    JdkBundleList jdkBundleList = new JdkBundleList();
    if (bootJdk != null) {
      jdkBundleList.addBundle(bootJdk, true);
    }

    if (new File(PathManager.getHomePath() + File.separator + bundledJdkFile).exists()) {
      JdkBundle bundledJdk = JdkBundle.createBundle(bundledJdkFile, false, true);
      if (bundledJdk != null) {
        jdkBundleList.addBundle(bundledJdk, true);
      }
    }

    if (SystemInfo.isMac) {
      jdkBundleList.addBundlesFromLocation(STANDARD_JDK_LOCATION_ON_MAC_OS_X, JDK8_VERSION, null);
    }
    else if (SystemInfo.isLinux) {
      for (String location : STANDARD_JVM_LOCATIONS_ON_LINUX) {
        jdkBundleList.addBundlesFromLocation(location, JDK8_VERSION, null);
      }
    }
    else if (SystemInfo.isWindows) {
      for (File root : File.listRoots()) {
        if (SystemInfo.is32Bit) {
          jdkBundleList.addBundlesFromLocation(new File(root, STANDARD_JVM_X86_LOCATIONS_ON_WINDOWS).getAbsolutePath(), JDK8_VERSION, null);
        }
        else {
          jdkBundleList.addBundlesFromLocation(new File(root, STANDARD_JVM_X64_LOCATIONS_ON_WINDOWS).getAbsolutePath(), JDK8_VERSION, null);
        }
      }
    }

    return jdkBundleList;
  }

  @NotNull
  private static String getExecutable() {
    final String executable = System.getProperty("idea.executable");
    return executable != null ? executable : ApplicationNamesInfo.getInstance().getProductName().toLowerCase(Locale.US);
  }
}
