/*
 * Copyright 2000-2017 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.compiler.options;

import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ui.configuration.ChooseModulesDialog;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.SpeedSearchBase;
import com.intellij.ui.SpeedSearchComparator;
import com.intellij.ui.TableUtil;
import com.intellij.ui.ToolbarDecorator;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ui.EditableModel;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.util.List;
import java.util.*;

public class ProcessedModulesTable extends JPanel {
  private JBTable myTable = null;
  private MyTableModel myTableModel = null;

  public ProcessedModulesTable(final Project project) {
    super(new BorderLayout());

    myTableModel = new MyTableModel(project);
    myTable = new JBTable(myTableModel);
    myTable.getEmptyText().setText("No modules configured");

    //myTable.setShowGrid(false);
    myTable.setIntercellSpacing(JBUI.emptySize());
    myTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
    myTable.setColumnSelectionAllowed(false);

    final TableColumnModel columnModel = myTable.getColumnModel();

    final TableColumn dirNameColumn = columnModel.getColumn(myTableModel.DIRNAME_COLUMN_INDEX);
    final String title = "Generated Sources Directory Name";
    dirNameColumn.setHeaderValue(title);
    final JTableHeader tableHeader = myTable.getTableHeader();
    final FontMetrics metrics = tableHeader.getFontMetrics(tableHeader.getFont());
    final int preferredWidth = metrics.stringWidth(title) + 12;
    dirNameColumn.setPreferredWidth(preferredWidth);
    dirNameColumn.setMaxWidth(preferredWidth + 20);
    dirNameColumn.setCellRenderer(new MyElementColumnCellRenderer());

    final TableColumn moduleColumn = columnModel.getColumn(myTableModel.ELEMENT_COLUMN_INDEX);
    moduleColumn.setHeaderValue("Module");
    moduleColumn.setCellRenderer(new MyElementColumnCellRenderer());

    final JPanel panel = ToolbarDecorator.createDecorator(myTable)
      .disableUpDownActions()
      .setPreferredSize(JBUI.size(100, 155))
      .createPanel();
    add(panel, BorderLayout.CENTER);

    final SpeedSearchBase<JBTable> speedSearch = new SpeedSearchBase<JBTable>(myTable) {
      @Override
      public int getSelectedIndex() {
        return myTable.getSelectedRow();
      }

      @Override
      protected int convertIndexToModel(int viewIndex) {
        return myTable.convertRowIndexToModel(viewIndex);
      }

      @NotNull
      @Override
      public Object[] getAllElements() {
        final int count = myTableModel.getRowCount();
        Object[] elements = new Object[count];
        for (int idx = 0; idx < count; idx++) {
          elements[idx] = myTableModel.getModuleAt(idx);
        }
        return elements;
      }

      @Override
      public String getElementText(Object element) {
        return ((Module)element).getName() + " (" + FileUtil.toSystemDependentName(((Module)element).getModuleFilePath()) + ")";
      }

      @Override
      public void selectElement(Object element, String selectedText) {
        final int count = myTableModel.getRowCount();
        for (int row = 0; row < count; row++) {
          if (element.equals(myTableModel.getModuleAt(row))) {
            final int viewRow = myTable.convertRowIndexToView(row);
            myTable.getSelectionModel().setSelectionInterval(viewRow, viewRow);
            TableUtil.scrollSelectionToVisible(myTable);
            break;
          }
        }
      }
    };
    speedSearch.setComparator(new SpeedSearchComparator(false));
  }

  public void refresh() {
    myTableModel.fireTableDataChanged();
  }

  public void refresh(Module element) {
    final int row = myTableModel.getElementRow(element);
    if (row >= 0) {
      myTableModel.fireTableRowsUpdated(row, row);
    }
  }

  private int[] mySavedSelection = null;
  public void saveSelection() {
    mySavedSelection = myTable.getSelectedRows();
  }

  public void restoreSelection() {
    if (mySavedSelection != null) {
      TableUtil.selectRows(myTable, mySavedSelection);
      mySavedSelection = null;
    }
  }

  public void addModule(Module element, String dirName) {
    myTableModel.addElement(element, dirName);
    selectRow(myTableModel.getRowCount() - 1);
    IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> {
      IdeFocusManager.getGlobalInstance().requestFocus(myTable, true);
    });
  }

  public void removeModule(Module element) {
    final int elementRow = myTableModel.getElementRow(element);
    if (elementRow < 0) {
      return; // no such element
    }
    final boolean wasSelected = myTable.getSelectionModel().isSelectedIndex(elementRow);

    myTableModel.removeElement(element);

    if (wasSelected) {
      final int rowCount = myTableModel.getRowCount();
      if (rowCount > 0) {
        selectRow(elementRow % rowCount);
      }
      else {
        myTable.getSelectionModel().clearSelection();
      }
    }
    IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> {
      IdeFocusManager.getGlobalInstance().requestFocus(myTable, true);
    });
  }

  public void removeAllElements() {
    myTableModel.removeAllElements();
    myTable.getSelectionModel().clearSelection();
  }

  private void selectRow(final int row) {
    myTable.getSelectionModel().setSelectionInterval(row, row);
    myTable.scrollRectToVisible(myTable.getCellRect(row, 0, true));
  }

  @Nullable
  public Module getSelectedElement() {
    final int selectedRow = getSelectedElementRow();
    return selectedRow < 0? null : myTableModel.getModuleAt(selectedRow);
  }

  public int getSelectedElementRow() {
    return myTable.getSelectedRow();
  }

  public List<Module> getSelectedElements() {
    final List<Module> elements = new ArrayList<>();
    final int[] selectedRows = myTable.getSelectedRows();
    for (int selectedRow : selectedRows) {
      if (selectedRow < 0) {
        continue;
      }
      elements.add(myTableModel.getModuleAt(selectedRow));
    }
    return elements;
  }

  public void selectElements(Collection<? extends Module> elements) {
    if (elements.size() == 0) {
      myTable.clearSelection();
      return;
    }
    final int[] rows = getElementsRows(elements);
    TableUtil.selectRows(myTable, rows);
    TableUtil.scrollSelectionToVisible(myTable);
    IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> {
      IdeFocusManager.getGlobalInstance().requestFocus(myTable, true);
    });
  }

  private int[] getElementsRows(final Collection<? extends Module> elements) {
    final int[] rows = new int[elements.size()];
    int index = 0;
    for (final Module element : elements) {
      rows[index++] = myTableModel.getElementRow(element);
    }
    return rows;
  }

  public List<Pair<Module, String>> getAllModules() {
    final int count = myTableModel.getRowCount();
    List<Pair<Module, String>> elements = new ArrayList<>();
    for (int idx = 0; idx < count; idx++) {
      final Module module = myTableModel.getModuleAt(idx);
      elements.add(Pair.create(module, myTableModel.getGenDirName(module)));
    }
    return elements;
  }

  public void sort(Comparator<? super Module> comparator) {
    myTableModel.sort(comparator);
  }

  @Override
  public void setEnabled(boolean enabled) {
    super.setEnabled(enabled);
    myTable.setRowSelectionAllowed(enabled);
    myTableModel.fireTableDataChanged();
  }

  public void stopEditing() {
    TableCellEditor editor = myTable.getCellEditor();
    if (editor != null) {
      editor.stopCellEditing();
    }
  }

  public JComponent getComponent() {
    return myTable;
  }

  public void clear() {
    myTableModel.clear();
  }

  public int getElementCount() {
    return myTableModel.getRowCount();
  }

  public Module getElementAt(int row) {
    return myTableModel.getModuleAt(row);
  }

  private final class MyTableModel extends AbstractTableModel implements EditableModel {
    private final List<Module> myElements = new ArrayList<>();
    private final Map<Module, String> myDirNameMap = new HashMap<>();
    public final int ELEMENT_COLUMN_INDEX = 0;
    public final int DIRNAME_COLUMN_INDEX = 1;
    private final Project myProject;

    private MyTableModel(Project project) {
      myProject = project;
    }

    public void sort(Comparator<? super Module> comparator) {
      Collections.sort(myElements, comparator);
      fireTableDataChanged();
    }

    public List<Module> getAllModules() {
      return Collections.unmodifiableList(myElements);
    }

    public Module getModuleAt(int index) {
      return myElements.get(index);
    }

    public String getGenDirName(Module module) {
      return myDirNameMap.get(module);
    }

    void addElement(Module module, final String dirName) {
      myElements.add(module);
      if (dirName != null && dirName.length() > 0) {
        myDirNameMap.put(module, dirName);
      }
      int row = myElements.size() - 1;
      fireTableRowsInserted(row, row);
    }

    @Override
    public void addRow() {
      final Set<Module> projectModules = new HashSet<>(Arrays.asList(ModuleManager.getInstance(myProject).getModules()));
      projectModules.removeAll(getAllModules());
      final ChooseModulesDialog chooser =
        new ChooseModulesDialog(ProcessedModulesTable.this, new ArrayList<>(projectModules), "ChooseModule");
      if (chooser.showAndGet()) {
        final List<Module> chosen = chooser.getChosenElements();
        for (Module module : chosen) {
          addElement(module, null);
        }
      }
    }

    @Override
    public void removeRow(int idx) {
      final Module element = myElements.remove(idx);
      myDirNameMap.remove(element);
      fireTableRowsDeleted(idx, idx);
    }

    @Override
    public void exchangeRows(int oldIndex, int newIndex) {
    }

    @Override
    public boolean canExchangeRows(int oldIndex, int newIndex) {
      return false;
    }

    public void removeElement(Module element) {
      final boolean reallyRemoved = myElements.remove(element);
      if (reallyRemoved) {
        myDirNameMap.remove(element);
        fireTableDataChanged();
      }
    }

    public int getElementRow(Module element) {
      return myElements.indexOf(element);
    }

    public void removeAllElements() {
      myElements.clear();
      fireTableDataChanged();
    }

    @Override
    public int getRowCount() {
      return myElements.size();
    }

    @Override
    public int getColumnCount() {
      return 2;
    }

    @Override
    @Nullable
    public Object getValueAt(int rowIndex, int columnIndex) {
      Module element = myElements.get(rowIndex);
      if (columnIndex == ELEMENT_COLUMN_INDEX) {
        return element;
      }
      if (columnIndex == DIRNAME_COLUMN_INDEX) {
        return myDirNameMap.get(element);
      }
      return null;
    }

    @Override
    public void setValueAt(Object value, int rowIndex, int columnIndex) {
      if (columnIndex == DIRNAME_COLUMN_INDEX) {
        final Module module = myElements.get(rowIndex);
        if (value != null) {
          String dir = FileUtil.toSystemIndependentName((String)value);
          while (dir.startsWith("/")) {
            dir = dir.substring(1);
          }
          if (dir.length() > 0) {
            myDirNameMap.put(module, dir);
          }
          else {
            myDirNameMap.remove(module);
          }
        }
        else {
          myDirNameMap.remove(module);
        }
        fireTableRowsUpdated(rowIndex, rowIndex);
      }
    }

    @Override
    public Class getColumnClass(int columnIndex) {
      if (columnIndex == DIRNAME_COLUMN_INDEX) {
        return String.class;
      }
      return super.getColumnClass(columnIndex);
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
      if (!ProcessedModulesTable.this.isEnabled()) {
        return false;
      }
      if (columnIndex == DIRNAME_COLUMN_INDEX) {
        return true;
      }
      return false;
    }

    public void clear() {
      myElements.clear();
      myDirNameMap.clear();
      fireTableDataChanged();
    }
  }

  private class MyElementColumnCellRenderer extends DefaultTableCellRenderer {
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
      final Color color = UIUtil.getTableFocusCellBackground();
      Component component;
      final Module module = value instanceof Module? (Module)value : null;
      try {
        UIManager.put(UIUtil.TABLE_FOCUS_CELL_BACKGROUND_PROPERTY, table.getSelectionBackground());
        component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        if (module != null) {
          setText(module.getName() + " (" + FileUtil.toSystemDependentName(module.getModuleFilePath()) + ")");
        }
        if (component instanceof JLabel) {
          ((JLabel)component).setBorder(noFocusBorder);
        }
      }
      finally {
        UIManager.put(UIUtil.TABLE_FOCUS_CELL_BACKGROUND_PROPERTY, color);
      }
      component.setEnabled(ProcessedModulesTable.this.isEnabled());
      if (component instanceof JLabel) {
        final Icon icon = module != null ? ModuleType.get(module).getIcon() : null;
        JLabel label = (JLabel)component;
        label.setIcon(icon);
        label.setDisabledIcon(icon);
      }
      component.setForeground(isSelected ? table.getSelectionForeground() : table.getForeground());
      return component;
    }
  }
}
