package com.airlock.iam.plugin.configeditor.ui.panel;

import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.configeditor.ConfigEditorApplication;
import com.airlock.iam.plugin.framework.application.configuration.MutablePluginConfiguration;
import com.airlock.iam.plugin.framework.misc.config.search.SearchScope;
import com.vaadin.data.HasValue.ValueChangeEvent;
import com.vaadin.event.selection.SelectionEvent;
import com.vaadin.server.Sizeable.Unit;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.Grid;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Grid.Column;
import com.vaadin.ui.Grid.SelectionMode;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

final class SearchPanel implements PanelContainer {
   private final Panel panel;
   private String lastQuery = "";
   private final TextField searchString = new TextField();
   private final CheckBox searchInDescription = new CheckBox("Description", false);
   private final CheckBox searchInComment = new CheckBox("Comment", true);
   private final CheckBox searchInPluginName = new CheckBox("Plugin name", true);
   private final CheckBox searchInTechnicalId = new CheckBox("ID", true);
   private final CheckBox searchInDisplayName = new CheckBox("Display Name", true);
   private final CheckBox searchInPropertyDescriptions = new CheckBox("Property descriptions", false);
   private final CheckBox searchInPropertyNames = new CheckBox("Property names", true);
   private final CheckBox searchInContexts = new CheckBox("Contexts", true);
   private final CheckBox searchInPropertyValues = new CheckBox("Property values", true);
   private final Label noResultsFoundMessage = new Label();
   private final Grid<MutablePluginConfiguration> searchResultsGrid = new Grid();
   private final List<MutablePluginConfiguration> searchResults = new ArrayList<>();

   public SearchPanel() {
      VerticalLayout searchInputs = this.createSearchInputs();
      VerticalLayout searchResult = this.createSearchResult();
      VerticalLayout vl = new VerticalLayout();
      vl.setSizeFull();
      vl.addComponent(searchInputs);
      vl.addComponent(searchResult);
      vl.setExpandRatio(searchResult, 1.0F);
      this.panel = new Panel();
      this.panel.setSizeFull();
      this.panel.addStyleName("minimal");
      this.panel.setContent(vl);
      this.panel.setId("searchPanel");
   }

   private VerticalLayout createSearchInputs() {
      HorizontalLayout hl = new HorizontalLayout();
      hl.setWidth(100.0F, Unit.PERCENTAGE);
      this.searchString.setPlaceholder("Search...");
      this.searchString.setId("queryField");
      this.searchString.setValue(this.lastQuery);
      this.searchString.setWidth(100.0F, Unit.PERCENTAGE);
      this.searchString.addValueChangeListener(this::searchQueryChange);
      GridLayout advancedOptionsGrid = this.makeAdvacedOptionsGrid();
      hl.addComponent(this.searchString);
      Button searchButton = new Button("Search");
      searchButton.setId("searchButton");
      searchButton.addClickListener(event -> this.searchButtonClick());
      Button advancedSearch = this.makeAdvancedSearchButton(advancedOptionsGrid);
      searchButton.setClickShortcut(13, new int[0]);
      hl.addComponent(searchButton);
      hl.addComponent(advancedSearch);
      hl.setComponentAlignment(searchButton, Alignment.BOTTOM_LEFT);
      hl.setComponentAlignment(advancedSearch, Alignment.BOTTOM_LEFT);
      hl.setExpandRatio(this.searchString, 1.0F);
      VerticalLayout searchInputs = new VerticalLayout();
      searchInputs.setMargin(false);
      searchInputs.addComponent(hl);
      searchInputs.addComponent(advancedOptionsGrid);
      return searchInputs;
   }

   private VerticalLayout createSearchResult() {
      this.configureTable();
      this.noResultsFoundMessage.setWidth(100.0F, Unit.PERCENTAGE);
      this.noResultsFoundMessage.setVisible(false);
      this.noResultsFoundMessage.setContentMode(ContentMode.HTML);
      VerticalLayout searchResult = new VerticalLayout();
      searchResult.setSizeFull();
      searchResult.setMargin(false);
      searchResult.addComponent(this.searchResultsGrid);
      searchResult.addComponent(this.noResultsFoundMessage);
      return searchResult;
   }

   private void configureTable() {
      this.searchResultsGrid.setId("searchResultsTable");
      this.searchResultsGrid.setItems(this.searchResults);
      Column<MutablePluginConfiguration, String> displayName = this.searchResultsGrid
         .addColumn(PluginConfiguration::getPluginClassDisplayName)
         .setCaption("Name");
      this.searchResultsGrid.addColumn(PluginConfiguration::getUuid).setCaption("ID");
      this.searchResultsGrid.addColumn(PluginConfiguration::getPluginDisplayName).setCaption("Display Name");
      this.searchResultsGrid.addColumn(PluginConfiguration::getPluginClassName).setCaption("Type");
      this.searchResultsGrid.setSelectionMode(SelectionMode.SINGLE);
      this.searchResultsGrid.addSelectionListener(this::selectionChange);
      this.searchResultsGrid.sort(displayName);
      this.searchResultsGrid.setSizeFull();
      this.searchResultsGrid.setVisible(false);
      this.searchResultsGrid.setColumnReorderingAllowed(true);
      this.searchResultsGrid.setStyleGenerator(item -> "with-hand");
   }

   private GridLayout makeAdvacedOptionsGrid() {
      GridLayout advancedOptionsGrid = new GridLayout(4, 2);
      advancedOptionsGrid.setId("advancedSearchGrid");
      advancedOptionsGrid.setWidth("100%");
      advancedOptionsGrid.setVisible(false);
      advancedOptionsGrid.setSpacing(true);
      this.configureCheckBox(this.searchInPluginName, "searchPanelPluginNameCheckbox");
      advancedOptionsGrid.addComponent(this.searchInPluginName);
      this.configureCheckBox(this.searchInTechnicalId, "searchPanelTechnicalIdentifierCheckbox");
      advancedOptionsGrid.addComponent(this.searchInTechnicalId);
      this.configureCheckBox(this.searchInDisplayName, "searchPanelDisplayNameCheckbox");
      advancedOptionsGrid.addComponent(this.searchInDisplayName);
      this.configureCheckBox(this.searchInDescription, "searchPanelDescriptionCheckbox");
      advancedOptionsGrid.addComponent(this.searchInDescription);
      this.configureCheckBox(this.searchInComment, "searchPanelCommentCheckbox");
      advancedOptionsGrid.addComponent(this.searchInComment);
      this.configureCheckBox(this.searchInContexts, "searchPanelContextsCheckbox");
      advancedOptionsGrid.addComponent(this.searchInContexts);
      this.configureCheckBox(this.searchInPropertyNames, "searchPanelPropertyNamesCheckbox");
      advancedOptionsGrid.addComponent(this.searchInPropertyNames);
      this.configureCheckBox(this.searchInPropertyValues, "searchPanelPropertyValuesCheckbox");
      advancedOptionsGrid.addComponent(this.searchInPropertyValues);
      this.configureCheckBox(this.searchInPropertyDescriptions, "Property descriptions");
      advancedOptionsGrid.addComponent(this.searchInPropertyDescriptions);
      return advancedOptionsGrid;
   }

   private void configureCheckBox(CheckBox checkBox, String id) {
      checkBox.setId(id);
      checkBox.addValueChangeListener(event -> this.update());
   }

   private Button makeAdvancedSearchButton(GridLayout advancedOptionsGrid) {
      Button advancedSearch = new Button("Options");
      advancedSearch.addClickListener(new SearchPanel.AdvancedSearchButtonClickListener(advancedOptionsGrid));
      advancedSearch.setId("advancedSearch");
      return advancedSearch;
   }

   private void searchButtonClick() {
      this.lastQuery = this.searchString.getValue();
      this.update();
   }

   @Override
   public void update() {
      ConfigEditorApplication configEditorApplication = this.getConfigEditorApplication();
      if (configEditorApplication.getConfigContainer() != null) {
         String[] searchTerms = StringUtils.split(this.lastQuery);
         if (ArrayUtils.isEmpty(searchTerms)) {
            this.searchResults.clear();
            return;
         }

         SearchScope scope = SearchScope.nowhere();
         scope.setConfigurationEnvironment(configEditorApplication.getSelectedConfigurationEnvironment());
         scope.setSearchInPluginName(this.searchInPluginName.getValue());
         scope.setSearchInTechnicalIdentifier(this.searchInTechnicalId.getValue());
         scope.setSearchInDisplayName(this.searchInDisplayName.getValue());
         scope.setSearchInDescription(this.searchInDescription.getValue());
         scope.setSearchInComment(this.searchInComment.getValue());
         scope.setSearchInConfigurationContexts(this.searchInContexts.getValue());
         scope.setSearchInPropertyNames(this.searchInPropertyNames.getValue());
         scope.setSearchInPropertyValues(this.searchInPropertyValues.getValue());
         scope.setSearchInPropertyDescriptions(this.searchInPropertyDescriptions.getValue());
         Set<MutablePluginConfiguration> results = configEditorApplication.getConfigurationDictionary() == null
            ? new HashSet<>()
            : configEditorApplication.getConfigurationDictionary()
               .getMatchingPluginConfigurations(scope, searchTerms)
               .stream()
               .map(MutablePluginConfiguration.class::cast)
               .collect(Collectors.toSet());
         this.searchResults.clear();
         if (results.isEmpty()) {
            this.noResultsFoundMessage.setVisible(true);
            this.noResultsFoundMessage.setValue("No results found for: <b>" + this.lastQuery + "</b>");
            this.searchResultsGrid.setVisible(false);
         } else {
            this.noResultsFoundMessage.setVisible(false);
            this.searchResultsGrid.setVisible(true);
            this.searchResults.addAll(results);
         }

         this.searchResultsGrid.getDataProvider().refreshAll();
      }
   }

   private void selectionChange(SelectionEvent<MutablePluginConfiguration> event) {
      event.getFirstSelectedItem()
         .ifPresent(selectedPluginConfiguration -> this.getConfigEditorApplication().selectConfigurationButUnselectTreeItems(selectedPluginConfiguration));
   }

   private void searchQueryChange(ValueChangeEvent<String> event) {
      if (event.getSource() == this.searchString) {
         String query = StringUtils.trimToEmpty((String)event.getValue());
         if (StringUtils.isBlank(query)) {
            this.searchResultsGrid.setVisible(false);
            this.noResultsFoundMessage.setVisible(false);
         } else {
            this.searchResultsGrid.setVisible(true);
         }

         if (!query.equals(this.lastQuery)) {
            this.lastQuery = query;
            this.update();
         }
      }
   }

   @Override
   public Component getContent() {
      return this.panel;
   }

   private ConfigEditorApplication getConfigEditorApplication() {
      return (ConfigEditorApplication)UI.getCurrent();
   }

   private static final class AdvancedSearchButtonClickListener implements ClickListener {
      private final GridLayout advancedOptionsGrid;
      private boolean visibleState;

      private AdvancedSearchButtonClickListener(GridLayout advancedOptionsGrid) {
         this.advancedOptionsGrid = advancedOptionsGrid;
      }

      public void buttonClick(ClickEvent event) {
         this.advancedOptionsGrid.setVisible(!this.visibleState);
         this.visibleState = !this.visibleState;
      }
   }
}
