/*
 * Copyright 2003-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 jetbrains.mps.ide.ui.dialogs.properties.roots.editors;

import com.intellij.icons.AllIcons.Actions;
import com.intellij.icons.AllIcons.Nodes;
import com.intellij.ide.util.treeView.AbstractTreeBuilder;
import com.intellij.ide.util.treeView.AbstractTreeStructure;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.idea.ActionsBundle;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.actionSystem.ex.CustomComponentAction;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.fileChooser.FileSystemTree;
import com.intellij.openapi.fileChooser.actions.NewFolderAction;
import com.intellij.openapi.fileChooser.ex.FileSystemTreeImpl;
import com.intellij.openapi.fileChooser.impl.FileTreeBuilder;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.roots.ui.configuration.actions.IconWithTextAction;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.TreeSpeedSearch;
import com.intellij.ui.roots.ToolbarPanel;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.ui.tree.TreeUtil;
import jetbrains.mps.extapi.persistence.FileBasedModelRoot;
import jetbrains.mps.extapi.persistence.SourceRootKind;
import jetbrains.mps.extapi.persistence.SourceRootKinds;
import jetbrains.mps.ide.vfs.VirtualFileUtils;
import jetbrains.mps.vfs.IFile;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.ui.persistence.ModelRootEntryEditor;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import java.awt.BorderLayout;
import java.awt.LayoutManager;
import java.util.Collection;
import java.util.Comparator;

public class FileBasedModelRootEditor implements ModelRootEntryEditor {
  protected Tree myTree;
  private FileSystemTreeImpl myFileSystemTree;
  private final JPanel myTreePanel;
  private final DefaultMutableTreeNode EMPTY_TREE_ROOT = new DefaultMutableTreeNode("<empty>");
  protected DefaultActionGroup myEditingActionsGroup;

  private FileBasedModelRootEntry myFileBasedModelRootEntry;
  private FileChooserDescriptor myDescriptor;

  public FileBasedModelRootEditor() {
    myTree = new Tree();
    myTree.setRootVisible(true);
    myTree.setShowsRootHandles(true);

    myEditingActionsGroup = new DefaultActionGroup();

    TreeUtil.installActions(myTree);
    new TreeSpeedSearch(myTree);

    myTreePanel = new MyPanel(new BorderLayout());
    final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree);
    myTreePanel.add(new ToolbarPanel(scrollPane, myEditingActionsGroup), BorderLayout.CENTER);

    myTreePanel.setVisible(false);
    myDescriptor = FileChooserDescriptorFactory.createMultipleFilesNoJarsDescriptor();
    myDescriptor.setShowFileSystemRoots(false);
  }

  @NotNull
  static String getKindText(@NotNull SourceRootKind kind) {
    if (kind == SourceRootKinds.EXCLUDED) {
      return "Excluded";
    } else if (kind == SourceRootKinds.SOURCES) {
      return "Sources";
    }
    return "Unknown";
  }

  protected void createEditingActions() {
    myEditingActionsGroup.removeAll();

    FileBasedModelRoot fileBasedModelRoot = myFileBasedModelRootEntry.getModelRoot();
    Collection<SourceRootKind> kinds = fileBasedModelRoot.getSupportedFileKinds1();

    for (final SourceRootKind kind : kinds) {
      AnAction modelRootAnAction =
          new ToggleFBModelRootKindAction(myTree, this, getKindText(kind), null, myFileBasedModelRootEntry.getKindIcon(kind)) {
            @NotNull
            @Override
            protected SourceRootKind getKind() {
              return kind;
            }
          };
//      modelRootAnAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.ALT_MASK)), myTree);
      myEditingActionsGroup.add(modelRootAnAction);
    }
  }

  protected TreeCellRenderer getModelRootEntryCellRenderer() {
    return new FileBasedModelRootEntryTreeCellRender(this);
  }

  public void setFileBasedModelRootEntry(final FileBasedModelRootEntry fileBasedModelRootEntry) {
    if (myFileBasedModelRootEntry != null && myFileBasedModelRootEntry.equals(fileBasedModelRootEntry)) {
      return;
    }
    if (myFileSystemTree != null) {
      Disposer.dispose(myFileSystemTree);
      myFileSystemTree = null;
    }
    if (myFileBasedModelRootEntry != null) {
      myFileBasedModelRootEntry = null;
    }
    if (fileBasedModelRootEntry == null) {
      ((DefaultTreeModel) myTree.getModel()).setRoot(EMPTY_TREE_ROOT);
      myTreePanel.setVisible(false);
      if (myFileSystemTree != null) {
        Disposer.dispose(myFileSystemTree);
      }
      return;
    }
    myTreePanel.setVisible(true);
    myFileBasedModelRootEntry = fileBasedModelRootEntry;

    IFile path = myFileBasedModelRootEntry.getModelRoot().getContentDirectory();

    VirtualFile file = path == null ? null : VirtualFileUtils.getProjectVirtualFile(path);
    setRoot(file);

    final Runnable init = () -> {
      myFileSystemTree.updateTree();
      if (file != null) {
        myFileSystemTree.select(file, null);
      }
    };

    myFileSystemTree = new FileSystemTreeImpl(null, myDescriptor, myTree, getModelRootEntryCellRenderer(), init, null) {
      @Override
      protected AbstractTreeBuilder createTreeBuilder(JTree tree, DefaultTreeModel treeModel, AbstractTreeStructure treeStructure,
          Comparator<NodeDescriptor> comparator, FileChooserDescriptor descriptor, final Runnable onInitialized) {
        return new MyFileTreeBuilder(tree, treeModel, treeStructure, comparator, descriptor, onInitialized);
      }
    };
    myFileSystemTree.showHiddens(true);
    Disposer.register(myFileBasedModelRootEntry, myFileSystemTree);

    final NewFolderAction newFolderAction = new MyNewFolderAction();
    final DefaultActionGroup mousePopupGroup = new DefaultActionGroup();
    mousePopupGroup.add(myEditingActionsGroup);
    mousePopupGroup.addSeparator();
    mousePopupGroup.add(newFolderAction);
    mousePopupGroup.add(new ChooseModelRootContentFolder());
    myFileSystemTree.registerMouseListener(mousePopupGroup);

    createEditingActions();
  }

  private void setRoot(VirtualFile file) {
    myDescriptor.setRoots(file);
    if (file != null) {
      myDescriptor.setTitle(FileUtil.toSystemDependentName(file.getPath()));
    }
  }

  public void selectFile(@NotNull IFile file) {
    VirtualFile file2Select = VirtualFileUtils.getProjectVirtualFile(file);
    if (file2Select == null) {
      return;
    }

    myTree.requestFocus();
    if (myFileSystemTree != null) {
      myFileSystemTree.select(file2Select, null);
    }
  }

  public FileBasedModelRootEntry getFileBasedModelRootEntry() {
    return myFileBasedModelRootEntry;
  }

  public FileChooserDescriptor getDescriptor() {
    return myDescriptor;
  }

  public void setDescriptor(FileChooserDescriptor descriptor) {
    myDescriptor = descriptor;

    //TODO: find better way to update
    final FileBasedModelRootEntry entry = myFileBasedModelRootEntry;
    setFileBasedModelRootEntry(null);
    setFileBasedModelRootEntry(entry);
  }

  @Override
  public JComponent createComponent() {
    return myTreePanel;
  }

  private class MyPanel extends JPanel implements DataProvider {
    private MyPanel(final LayoutManager layout) {
      super(layout);
    }

    @Override
    @Nullable
    public Object getData(@NonNls final String dataId) {
      if (CommonDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) {
        return myFileSystemTree.getSelectedFiles();
      }

      if (FileSystemTree.DATA_KEY.is(dataId)) {
        return myFileSystemTree;
      }
      return null;
    }
  }

  private static class MyFileTreeBuilder extends FileTreeBuilder {
    public MyFileTreeBuilder(JTree tree,
        DefaultTreeModel treeModel,
        AbstractTreeStructure treeStructure,
        Comparator<NodeDescriptor> comparator,
        FileChooserDescriptor descriptor,
        @Nullable Runnable onInitialized) {
      super(tree, treeModel, treeStructure, comparator, descriptor, onInitialized);
    }

    @Override
    protected boolean isAlwaysShowPlus(NodeDescriptor nodeDescriptor) {
      return false; // need this in order to not show plus for empty directories
    }
  }

  private static class MyNewFolderAction extends NewFolderAction implements CustomComponentAction {
    private MyNewFolderAction() {
      super(ActionsBundle.message("action.FileChooser.NewFolder.text"),
          ActionsBundle.message("action.FileChooser.NewFolder.description"),
          Actions.NewFolder);
    }

    @Override
    public JComponent createCustomComponent(Presentation presentation) {
      return IconWithTextAction.createCustomComponentImpl(this, presentation);
    }
  }

  private class ChooseModelRootContentFolder extends AnAction implements DumbAware {
    private final FileChooserDescriptor myDescriptor;

    public ChooseModelRootContentFolder() {
      super("Change Root Folder", "", Nodes.HomeFolder);
      myDescriptor = new FileChooserDescriptor(false, true, true, false, true, false);
      myDescriptor.setTitle("Select new content entry for model root");
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      VirtualFile[] files = FileChooser.chooseFiles(myDescriptor, null, null,
          VirtualFileManager.getInstance().findFileByUrl(
              VirtualFileManager.constructUrl("file", myFileBasedModelRootEntry.getModelRoot().getContentRoot())
          )
      );

      if (files.length != 1) {
        return;
      }

      FileBasedModelRootEntry fileBasedModelRootEntry = FileBasedModelRootEditor.this.myFileBasedModelRootEntry;
      fileBasedModelRootEntry.getModelRoot().setContentRoot(files[0].getPath());
      FileBasedModelRootEditor.this.myFileBasedModelRootEntry = null;
      FileBasedModelRootEditor.this.setFileBasedModelRootEntry(fileBasedModelRootEntry);
      FileBasedModelRootEditor.this.myFileBasedModelRootEntry.updateUI();
    }
  }
}
