/*
 * Copyright 2003-2013 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 jetbrains.mps.idea.core.library;

import com.intellij.ide.util.ChooseElementsDialog;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.impl.libraries.LibraryEx;
import com.intellij.openapi.roots.libraries.DummyLibraryProperties;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryType;
import com.intellij.openapi.roots.libraries.NewLibraryConfiguration;
import com.intellij.openapi.roots.libraries.PersistentLibraryKind;
import com.intellij.openapi.roots.libraries.ui.AttachRootButtonDescriptor;
import com.intellij.openapi.roots.libraries.ui.LibraryEditorComponent;
import com.intellij.openapi.roots.libraries.ui.LibraryPropertiesEditor;
import com.intellij.openapi.roots.libraries.ui.LibraryRootsComponentDescriptor;
import com.intellij.openapi.roots.libraries.ui.OrderRoot;
import com.intellij.openapi.roots.libraries.ui.OrderRootTypePresentation;
import com.intellij.openapi.roots.libraries.ui.RootDetector;
import com.intellij.openapi.roots.ui.configuration.FacetsProvider;
import com.intellij.openapi.roots.ui.configuration.libraryEditor.DefaultLibraryRootsComponentDescriptor;
import com.intellij.openapi.roots.ui.configuration.libraryEditor.LibraryEditor;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.ide.vfs.VirtualFileUtils;
import jetbrains.mps.idea.core.MPSBundle;
import jetbrains.mps.idea.core.facet.MPSFacetType;
import jetbrains.mps.idea.core.icons.MPSIcons;
import jetbrains.mps.idea.core.project.SolutionIdea;
import jetbrains.mps.project.AbstractModule;
import jetbrains.mps.project.SModuleOperations;
import jetbrains.mps.project.Solution;
import jetbrains.mps.smodel.ModuleRepositoryFacade;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.module.SRepository;

import javax.swing.Icon;
import javax.swing.JComponent;
import java.awt.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class ModuleLibraryType extends LibraryType<DummyLibraryProperties> {
  public ModuleLibraryType() {
    super(MpsModuleLibraryKindContainer.MPS_MODULE_LIBRARY_KIND);
  }

  @Nullable
  public static VirtualFile getJarFile(String path) {
    VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, path));
    if (vFile == null || vFile.isDirectory() || vFile.getFileType() != FileTypes.ARCHIVE) {
      return null;
    }
    return JarFileSystem.getInstance().findFileByPath(vFile.getPath() + JarFileSystem.JAR_SEPARATOR);
  }

  public static Set<VirtualFile> getModuleJars(AbstractModule usedModule) {
    Set<VirtualFile> stubFiles = new HashSet<VirtualFile>();
    for (String stubPath : SModuleOperations.getJavaFacet(usedModule).getClassPath()) {
      VirtualFile jarFile = getJarFile(stubPath);
      if (jarFile != null) {
        stubFiles.add(jarFile);
      }
    }
    return stubFiles;
  }

  @Override
  public String getCreateActionName() {
    return MPSBundle.message("library.mps.solutions");
  }

  @Override
  public NewLibraryConfiguration createNewLibrary(@NotNull JComponent parentComponent, @Nullable VirtualFile contextDirectory, @NotNull final Project project) {
    List<SModuleReference> availableSolutions = calculateVisibleModules(ProjectHelper.getProjectRepository(project), Collections.<VirtualFile>emptySet());

    ChooseElementsDialog<SModuleReference> chooser = new SModuleReferenceChooserDialog(project, availableSolutions);
    chooser.show();
    List<SModuleReference> chosenElements = chooser.getChosenElements();
    if (chosenElements.isEmpty()) {
      return null;
    }

    String name = ModuleLibrariesUtil.LIBRARY_PREFIX + chosenElements.get(0).getModuleName();
    if (chosenElements.size() > 1) {
      name += "...";
    }

    final Set<OrderRoot> roots = createRootsFor(chosenElements);

    return new NewLibraryConfiguration(name, this, new DummyLibraryProperties()) {
      @Override
      public void addRoots(@NotNull LibraryEditor editor) {
        editor.addRoots(roots);
      }
    };
  }

  private Set<OrderRoot> createRootsFor(List<SModuleReference> chosenElements) {
    final Set<OrderRoot> roots = new LinkedHashSet<OrderRoot>();
    for (SModuleReference moduleReference : chosenElements) {
      AbstractModule module = (AbstractModule) ModuleRepositoryFacade.getInstance().getModule(moduleReference);
      roots.add(new OrderRoot(VirtualFileUtils.getOrCreateVirtualFile(module.getDescriptorFile()), ModuleXmlRootDetector.MPS_MODULE_XML, false));
      for (VirtualFile virtualFile : getModuleJars(module)) {
        roots.add(new OrderRoot(virtualFile, OrderRootType.CLASSES, false));
      }
    }
    return roots;
  }

  @Override
  public LibraryPropertiesEditor createPropertiesEditor(@NotNull LibraryEditorComponent editorComponent) {
    return null;
  }

  @Override
  public Icon getIcon() {
    return MPSIcons.MPS_ICON;
  }

  @Override
  public DummyLibraryProperties detect(@NotNull List<VirtualFile> classesRoots) {
    return super.detect(classesRoots);
  }

  @Override
  public boolean isSuitableModule(@NotNull Module module, @NotNull FacetsProvider facetsProvider) {
    return !facetsProvider.getFacetsByType(module, MPSFacetType.ID).isEmpty();
  }

  @Override
  public LibraryRootsComponentDescriptor createLibraryRootsComponentDescriptor() {
    return new MyLibraryRootsComponentDescriptor();
  }

  public static boolean isModuleLibrary(Library l) {
    if (l instanceof LibraryEx) {
      PersistentLibraryKind<?> kind = ((LibraryEx) l).getKind();
      return kind != null && MpsModuleLibraryKindContainer.MPS_MODULE_LIBRARY_KIND.getKindId().equals(kind.getKindId());
    }
    return false;
  }

  public static ModuleLibraryType getInstance() {
    return LibraryType.EP_NAME.findExtension(ModuleLibraryType.class);
  }

  private static class SModuleReferenceChooserDialog extends ChooseElementsDialog<SModuleReference> {
    public SModuleReferenceChooserDialog(Project project, List<SModuleReference> availableSolutions) {
      super(project, availableSolutions, MPSBundle.message("used.modules.chooser.title"), null);
    }

    private SModuleReferenceChooserDialog(Component parent, List<SModuleReference> availableSolutions) {
      super(parent, availableSolutions, MPSBundle.message("used.modules.chooser.title"));
    }

    @Override
    protected String getItemText(SModuleReference item) {
      return item.getModuleName();
    }

    @Override
    protected Icon getItemIcon(SModuleReference item) {
      if (ModuleRepositoryFacade.getInstance().getModule(item) instanceof Solution) {
        return MPSIcons.SOLUTION_ICON;
      } else {
        return MPSIcons.LANGUAGE_ICON;
      }
    }


  }

  private static class MyLibraryRootsComponentDescriptor extends DefaultLibraryRootsComponentDescriptor {
    @Override
    public OrderRootTypePresentation getRootTypePresentation(@NotNull OrderRootType type) {
      if (type == ModuleXmlRootDetector.MPS_MODULE_XML) {
        return ModuleXmlRootDetector.getPresentation();
      }
      return null;
    }

    @Override
    public OrderRootType[] getRootTypes() {
      ArrayList<OrderRootType> types = new ArrayList<OrderRootType>();
      types.addAll(Arrays.asList(super.getRootTypes()));
      types.add(ModuleXmlRootDetector.MPS_MODULE_XML);
      return types.toArray(new OrderRootType[types.size()]);
    }

    @NotNull
    @Override
    public List<? extends RootDetector> getRootDetectors() {
      List<RootDetector> detectors = new ArrayList<RootDetector>();
      detectors.addAll(super.getRootDetectors());
      detectors.add(ModuleXmlRootDetector.getInstance());
      return detectors;
    }

    @NotNull
    @Override
    public FileChooserDescriptor createAttachFilesChooserDescriptor(@Nullable String libraryName) {
      // same as super apart from the constructor invocation parameters
      FileChooserDescriptor descriptor = new FileChooserDescriptor(false, false, true, false, true, true);
      descriptor.setTitle(StringUtil.isEmpty(libraryName) ? ProjectBundle.message("library.attach.files.action")
        : ProjectBundle.message("library.attach.files.to.library.action", libraryName));
      descriptor.setDescription(ProjectBundle.message("library.attach.files.description"));
      return descriptor;
    }

    @NotNull
    @Override
    public List<? extends AttachRootButtonDescriptor> createAttachButtons() {
      return Arrays.asList(new AttachRootButtonDescriptor(ModuleXmlRootDetector.MPS_MODULE_XML, MPSBundle.message("library.attach.mps.solution")) {
        @Override
        public VirtualFile[] selectFiles(@NotNull JComponent parent, @Nullable VirtualFile initialSelection, @Nullable final Module contextModule, @NotNull final LibraryEditor libraryEditor) {
          SRepository repository = ProjectHelper.getProjectRepository(contextModule.getProject());
          List<SModuleReference> visibleModules = calculateVisibleModules(repository, new HashSet<VirtualFile>(Arrays.asList(libraryEditor.getFiles(ModuleXmlRootDetector.MPS_MODULE_XML))));

          ChooseElementsDialog<SModuleReference> chooser = new SModuleReferenceChooserDialog(parent, visibleModules);
          chooser.show();
          final List<SModuleReference> chosenElements = chooser.getChosenElements();

          final Set<VirtualFile> addedDescriptors = new LinkedHashSet<VirtualFile>();
          final Set<VirtualFile> addedJars = new LinkedHashSet<VirtualFile>();
          repository.getModelAccess().runReadAction(new Runnable() {
            @Override
            public void run() {
              for (SModuleReference module : chosenElements) {
                AbstractModule chosenModule = (AbstractModule) ModuleRepositoryFacade.getInstance().getModule(module);
                addedDescriptors.add(VirtualFileUtils.getOrCreateVirtualFile(chosenModule.getDescriptorFile()));
                for (VirtualFile virtualFile : getModuleJars(chosenModule)) {
                  addedJars.add(virtualFile);
                }
              }
            }
          });
          // that's a hack
          // I want to add 2 different root types here: classes and module xml-s
          for (VirtualFile classesJar : addedJars) {
            libraryEditor.addRoot(classesJar, OrderRootType.CLASSES);
          }
          return addedDescriptors.toArray(new VirtualFile[addedDescriptors.size()]);
        }
      });
    }
  }

  private static List<SModuleReference> calculateVisibleModules(SRepository repository, final Set<VirtualFile> excluded) {
    final List<SModuleReference> availableSolutions = new ArrayList<SModuleReference>();
    final List<SModuleReference> availableLanguages = new ArrayList<SModuleReference>();
    repository.getModelAccess().runReadAction(new Runnable() {
      @Override
      public void run() {
        for (SModule module : new ModuleRepositoryFacade(repository).getAllModules(SModule.class)) {
          if (module instanceof SolutionIdea || ((AbstractModule) module).getDescriptorFile() == null) {
            continue;
          }
          if (excluded.contains(VirtualFileUtils.getOrCreateVirtualFile(((AbstractModule) module).getDescriptorFile()))) {
            // skip solutions that are already in a lib
            continue;
          }
          if (module instanceof Solution) {
            availableSolutions.add(module.getModuleReference());
          } else {
            availableLanguages.add(module.getModuleReference());
          }
        }
      }
    });
    Comparator<SModuleReference> moduleComparator = new Comparator<SModuleReference>() {
      @Override
      public int compare(SModuleReference o1, SModuleReference o2) {
        return o1.getModuleName().compareTo(o2.getModuleName());
      }
    };
    Collections.sort(availableSolutions, moduleComparator);
    Collections.sort(availableLanguages, moduleComparator);
    List<SModuleReference> result = new ArrayList<SModuleReference>();
    result.addAll(availableSolutions);
    result.addAll(availableLanguages);
    return result;
  }
}
