/**
 * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.eclipse.hawkbit.ui.common;

import java.util.List;
import java.util.Set;

import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.common.CommonDialogWindow.SaveDialogCloseListener;
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
import org.eclipse.hawkbit.ui.common.builder.TextAreaBuilder;
import org.eclipse.hawkbit.ui.common.builder.TextFieldBuilder;
import org.eclipse.hawkbit.ui.common.builder.WindowBuilder;
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
import org.eclipse.hawkbit.ui.customrenderers.renderers.HtmlButtonRenderer;
import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleNoBorder;
import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
import org.eclipse.hawkbit.ui.utils.SPUIDefinitions;
import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider;
import org.eclipse.hawkbit.ui.utils.UINotification;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import org.vaadin.spring.events.EventBus;
import org.vaadin.spring.events.EventBus.UIEventBus;

import com.vaadin.data.Item;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.event.FieldEvents.TextChangeEvent;
import com.vaadin.event.SelectionEvent;
import com.vaadin.server.FontAwesome;
import com.vaadin.ui.AbstractTextField.TextChangeEventMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.Grid;
import com.vaadin.ui.Grid.SelectionMode;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.renderers.ClickableRenderer.RendererClickEvent;

/**
 * 
 * Abstract pop up layout
 *
 * @param <E>
 *            E id the entity for which metadata is displayed
 * @param <M>
 *            M is the metadata
 * 
 */
public abstract class AbstractMetadataPopupLayout<E extends NamedEntity, M extends MetaData> extends CustomComponent {
    private static final long serialVersionUID = 1L;

    private static final String DELETE_BUTTON = "DELETE_BUTTON";
    private static final int INPUT_DEBOUNCE_TIMEOUT = 250;

    protected static final String VALUE = "value";
    protected static final String KEY = "key";
    protected static final int MAX_METADATA_QUERY = 500;

    protected VaadinMessageSource i18n;
    private final UINotification uiNotification;
    protected transient EventBus.UIEventBus eventBus;

    private TextField keyTextField;
    private TextArea valueTextArea;
    private Button addIcon;
    private Grid metaDataGrid;
    private Label headerCaption;
    private CommonDialogWindow metadataWindow;

    private E selectedEntity;

    private HorizontalLayout mainLayout;
    protected SpPermissionChecker permChecker;

    protected AbstractMetadataPopupLayout(final VaadinMessageSource i18n, final UINotification uiNotification,
            final UIEventBus eventBus, final SpPermissionChecker permChecker) {
        this.i18n = i18n;
        this.uiNotification = uiNotification;
        this.eventBus = eventBus;
        this.permChecker = permChecker;

        createComponents();
        buildLayout();
    }

    /**
     * Save the metadata and never close the window after saving.
     */
    private final class SaveOnDialogCloseListener implements SaveDialogCloseListener {
        @Override
        public void saveOrUpdate() {
            onSave();
        }

        @Override
        public boolean canWindowClose() {
            return false;
        }

        @Override
        public boolean canWindowSaveOrUpdate() {
            return true;
        }

    }

    /**
     * Returns metadata popup.
     * 
     * @param entity
     *            entity for which metadata data is displayed
     * @param metaDatakey
     *            metadata key to be selected
     * @return {@link CommonDialogWindow}
     */
    public CommonDialogWindow getWindow(final E entity, final String metaDatakey) {
        selectedEntity = entity;

        metadataWindow = new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW).caption(getMetadataCaption())
                .content(this).cancelButtonClickListener(event -> onCancel())
                .id(UIComponentIdProvider.METADATA_POPUP_ID).layout(mainLayout).i18n(i18n)
                .saveDialogCloseListener(new SaveOnDialogCloseListener()).buildCommonDialogWindow();

        metadataWindow.setHeight(550, Unit.PIXELS);
        metadataWindow.setWidth(800, Unit.PIXELS);
        metadataWindow.getMainLayout().setSizeFull();
        metadataWindow.getButtonsLayout().setHeight("45px");
        setUpDetails(entity.getId(), metaDatakey);
        return metadataWindow;
    }

    public E getSelectedEntity() {
        return selectedEntity;
    }

    public void setSelectedEntity(final E selectedEntity) {
        this.selectedEntity = selectedEntity;
    }

    protected abstract boolean checkForDuplicate(E entity, String value);

    protected abstract M createMetadata(E entity, String key, String value);

    protected abstract M updateMetadata(E entity, String key, String value);

    protected abstract List<M> getMetadataList();

    protected abstract void deleteMetadata(E entity, String key);

    protected abstract boolean hasCreatePermission();

    protected abstract boolean hasUpdatePermission();

    protected void createComponents() {
        keyTextField = createKeyTextField();
        valueTextArea = createValueTextField();
        metaDataGrid = createMetadataGrid();
        addIcon = createAddIcon();
        headerCaption = createHeaderCaption();
    }

    private void buildLayout() {
        final HorizontalLayout headerLayout = new HorizontalLayout();
        headerLayout.addStyleName(SPUIStyleDefinitions.WIDGET_TITLE);
        headerLayout.setSpacing(false);
        headerLayout.setMargin(false);
        headerLayout.setSizeFull();
        headerLayout.addComponent(headerCaption);
        if (hasCreatePermission()) {
            headerLayout.addComponents(addIcon);
            headerLayout.setComponentAlignment(addIcon, Alignment.MIDDLE_RIGHT);
        }
        headerLayout.setExpandRatio(headerCaption, 1.0F);

        final HorizontalLayout headerWrapperLayout = new HorizontalLayout();
        headerWrapperLayout.addStyleName("bordered-layout" + " " + "no-border-bottom" + " " + "metadata-table-margin");
        headerWrapperLayout.addComponent(headerLayout);
        headerWrapperLayout.setWidth("100%");
        headerLayout.setHeight("30px");

        final VerticalLayout tableLayout = new VerticalLayout();
        tableLayout.setSizeFull();
        tableLayout.setHeight("100%");
        tableLayout.addComponent(headerWrapperLayout);
        tableLayout.addComponent(metaDataGrid);
        tableLayout.addStyleName("table-layout");
        tableLayout.setExpandRatio(metaDataGrid, 1.0F);

        final VerticalLayout metadataFieldsLayout = createMetadataFieldsLayout();

        mainLayout = new HorizontalLayout();
        mainLayout.addComponent(tableLayout);
        mainLayout.addComponent(metadataFieldsLayout);
        mainLayout.setExpandRatio(tableLayout, 0.5F);
        mainLayout.setExpandRatio(metadataFieldsLayout, 0.5F);
        mainLayout.setSizeFull();
        mainLayout.setSpacing(true);
        setCompositionRoot(mainLayout);
        setSizeFull();
    }

    protected VerticalLayout createMetadataFieldsLayout() {
        final VerticalLayout metadataFieldsLayout = new VerticalLayout();
        metadataFieldsLayout.setSizeFull();
        metadataFieldsLayout.setHeight("100%");
        metadataFieldsLayout.addComponent(keyTextField);
        metadataFieldsLayout.addComponent(valueTextArea);
        metadataFieldsLayout.setSpacing(true);
        metadataFieldsLayout.setExpandRatio(valueTextArea, 1F);
        return metadataFieldsLayout;
    }

    private TextField createKeyTextField() {
        final TextField keyField = new TextFieldBuilder(MetaData.KEY_MAX_SIZE).caption(i18n.getMessage("textfield.key"))
                .required(true, i18n).id(UIComponentIdProvider.METADATA_KEY_FIELD_ID).buildTextComponent();
        keyField.addTextChangeListener(this::onKeyChange);
        keyField.setTextChangeEventMode(TextChangeEventMode.LAZY);
        keyField.setTextChangeTimeout(INPUT_DEBOUNCE_TIMEOUT);
        keyField.setWidth("100%");
        return keyField;
    }

    private TextArea createValueTextField() {
        valueTextArea = new TextAreaBuilder(MetaData.VALUE_MAX_SIZE).caption(i18n.getMessage("textfield.value"))
                .required(true, i18n).id(UIComponentIdProvider.METADATA_VALUE_ID).buildTextComponent();
        valueTextArea.setSizeFull();
        valueTextArea.setHeight(100, Unit.PERCENTAGE);
        valueTextArea.addTextChangeListener(this::onValueChange);
        valueTextArea.setTextChangeEventMode(TextChangeEventMode.LAZY);
        valueTextArea.setTextChangeTimeout(INPUT_DEBOUNCE_TIMEOUT);
        return valueTextArea;
    }

    protected Grid createMetadataGrid() {
        final Grid metadataGrid = new Grid();
        metadataGrid.addStyleName(SPUIStyleDefinitions.METADATA_GRID);
        metadataGrid.setImmediate(true);
        metadataGrid.setHeight("100%");
        metadataGrid.setWidth("100%");
        metadataGrid.setId(UIComponentIdProvider.METDATA_TABLE_ID);
        metadataGrid.setSelectionMode(SelectionMode.SINGLE);
        metadataGrid.setColumnReorderingAllowed(true);
        metadataGrid.setContainerDataSource(getMetadataContainer());
        metadataGrid.getColumn(KEY).setHeaderCaption(i18n.getMessage("header.key"));
        metadataGrid.getColumn(VALUE).setHeaderCaption(i18n.getMessage("header.value"));
        metadataGrid.getColumn(VALUE).setHidden(true);
        metadataGrid.addSelectionListener(this::onRowClick);
        metadataGrid.getColumn(DELETE_BUTTON).setHeaderCaption("");
        metadataGrid.getColumn(DELETE_BUTTON).setRenderer(new HtmlButtonRenderer(this::onDelete));
        metadataGrid.getColumn(DELETE_BUTTON).setWidth(50);
        metadataGrid.getColumn(KEY).setExpandRatio(1);
        return metadataGrid;
    }

    private void onDelete(final RendererClickEvent event) {
        final Item item = metaDataGrid.getContainerDataSource().getItem(event.getItemId());
        final String key = (String) item.getItemProperty(KEY).getValue();

        final ConfirmationDialog confirmDialog = new ConfirmationDialog(
                i18n.getMessage("caption.entity.delete.action.confirmbox"),
                i18n.getMessage("message.confirm.delete.metadata", key), i18n.getMessage(UIMessageIdProvider.BUTTON_OK),
                i18n.getMessage(UIMessageIdProvider.BUTTON_CANCEL), ok -> {
                    if (ok) {
                        handleOkDeleteMetadata(event, key);
                    }
                });
        UI.getCurrent().addWindow(confirmDialog.getWindow());
        confirmDialog.getWindow().bringToFront();
    }

    private void handleOkDeleteMetadata(final RendererClickEvent event, final String key) {
        deleteMetadata(getSelectedEntity(), key);
        uiNotification.displaySuccess(i18n.getMessage("message.metadata.deleted.successfully", key));
        final Object selectedRow = metaDataGrid.getSelectedRow();
        metaDataGrid.getContainerDataSource().removeItem(event.getItemId());
        // force grid to refresh
        metaDataGrid.clearSortOrder();
        if (!metaDataGrid.getContainerDataSource().getItemIds().isEmpty()) {
            if (selectedRow != null) {
                if (selectedRow.equals(event.getItemId())) {
                    metaDataGrid.select(metaDataGrid.getContainerDataSource().getIdByIndex(0));
                } else {
                    metaDataGrid.select(selectedRow);
                }
            }
        } else {
            resetFields();
        }
    }

    private void resetFields() {
        clearFields();
        metaDataGrid.select(null);
        if (hasCreatePermission()) {
            enableEditing();
            addIcon.setEnabled(false);
        }
    }

    private Button createAddIcon() {
        addIcon = SPUIComponentProvider.getButton(UIComponentIdProvider.METADTA_ADD_ICON_ID,
                i18n.getMessage("button.save"), null, null, false, FontAwesome.PLUS, SPUIButtonStyleNoBorder.class);
        addIcon.addClickListener(event -> onAdd());
        return addIcon;
    }

    private Label createHeaderCaption() {
        return new LabelBuilder().name(i18n.getMessage("caption.metadata")).buildCaptionLabel();
    }

    private static IndexedContainer getMetadataContainer() {
        final IndexedContainer swcontactContainer = new IndexedContainer();
        swcontactContainer.addContainerProperty(KEY, String.class, "");
        swcontactContainer.addContainerProperty(VALUE, String.class, "");
        swcontactContainer.addContainerProperty(DELETE_BUTTON, String.class, FontAwesome.TRASH_O.getHtml());
        return swcontactContainer;
    }

    protected Item popualateKeyValue(final Object metadataCompositeKey) {
        if (metadataCompositeKey != null) {
            final Item item = metaDataGrid.getContainerDataSource().getItem(metadataCompositeKey);
            keyTextField.setValue((String) item.getItemProperty(KEY).getValue());
            valueTextArea.setValue((String) item.getItemProperty(VALUE).getValue());
            keyTextField.setEnabled(false);
            if (hasUpdatePermission()) {
                valueTextArea.setEnabled(true);
            }
            return item;
        }

        return null;
    }

    private void populateGrid() {
        final List<M> metadataList = getMetadataList();
        for (final M metaData : metadataList) {
            addItemToGrid(metaData);
        }
    }

    protected Item addItemToGrid(final M metaData) {
        final IndexedContainer metadataContainer = (IndexedContainer) metaDataGrid.getContainerDataSource();
        final Item item = metadataContainer.addItem(metaData.getKey());
        item.getItemProperty(VALUE).setValue(metaData.getValue());
        item.getItemProperty(KEY).setValue(metaData.getKey());
        return item;
    }

    protected Item updateItemInGrid(final String key) {
        final IndexedContainer metadataContainer = (IndexedContainer) metaDataGrid.getContainerDataSource();
        final Item item = metadataContainer.getItem(key);
        item.getItemProperty(VALUE).setValue(valueTextArea.getValue());
        return item;
    }

    private void onAdd() {
        metaDataGrid.deselect(metaDataGrid.getSelectedRow());
        clearFields();
        enableEditing();
        addIcon.setEnabled(true);
    }

    protected void clearFields() {
        valueTextArea.clear();
        keyTextField.clear();
    }

    protected void onSave() {
        final String key = keyTextField.getValue();
        final String value = valueTextArea.getValue();
        if (mandatoryCheck()) {
            final E entity = selectedEntity;
            if (metaDataGrid.getSelectedRow() == null) {
                if (!duplicateCheck(entity)) {
                    final M metadata = createMetadata(entity, key, value);
                    uiNotification.displaySuccess(i18n.getMessage("message.metadata.saved", metadata.getKey()));
                    addItemToGrid(metadata);
                    metaDataGrid.scrollToEnd();
                    metaDataGrid.select(metadata.getKey());
                    addIcon.setEnabled(true);
                    metadataWindow.setSaveButtonEnabled(false);
                    if (!hasUpdatePermission()) {
                        valueTextArea.setEnabled(false);
                    }
                }
            } else {
                final M metadata = updateMetadata(entity, key, value);
                uiNotification.displaySuccess(i18n.getMessage("message.metadata.updated", metadata.getKey()));
                updateItemInGrid(metadata.getKey());
                metaDataGrid.select(metadata.getKey());
                addIcon.setEnabled(true);
                metadataWindow.setSaveButtonEnabled(false);
            }
        }
    }

    private boolean mandatoryCheck() {
        if (keyTextField.getValue().isEmpty()) {
            uiNotification.displayValidationError(i18n.getMessage("message.key.missing"));
            return false;
        }
        if (valueTextArea.getValue().isEmpty()) {
            uiNotification.displayValidationError(i18n.getMessage("message.value.missing"));
            return false;
        }
        return true;
    }

    private boolean duplicateCheck(final E entity) {
        if (!checkForDuplicate(entity, keyTextField.getValue())) {
            return false;
        }

        uiNotification
                .displayValidationError(i18n.getMessage("message.metadata.duplicate.check", keyTextField.getValue()));
        return true;
    }

    private String getMetadataCaption() {
        final StringBuilder caption = new StringBuilder();
        caption.append(HawkbitCommonUtil.DIV_DESCRIPTION_START + i18n.getMessage("caption.metadata.popup") + " "
                + HawkbitCommonUtil.getBoldHTMLText(getElementTitle()));
        caption.append(HawkbitCommonUtil.DIV_DESCRIPTION_END);
        return caption.toString();
    }

    protected String getElementTitle() {
        return getSelectedEntity().getName();
    }

    private void onCancel() {
        metadataWindow.close();
        UI.getCurrent().removeWindow(metadataWindow);
    }

    private void onKeyChange(final TextChangeEvent event) {
        if (hasCreatePermission() || hasUpdatePermission()) {
            if (!valueTextArea.getValue().isEmpty() && !event.getText().isEmpty()) {
                metadataWindow.setSaveButtonEnabled(true);
            } else {
                metadataWindow.setSaveButtonEnabled(false);
            }
        }
    }

    protected void onRowClick(final SelectionEvent event) {
        final Set<Object> itemsSelected = event.getSelected();
        if (!itemsSelected.isEmpty()) {
            popualateKeyValue(itemsSelected.iterator().next());
            addIcon.setEnabled(true);
        } else {
            clearFields();
            if (hasCreatePermission()) {
                enableEditing();
                addIcon.setEnabled(false);
            } else {
                keyTextField.setEnabled(false);
                valueTextArea.setEnabled(false);
            }
        }
        metadataWindow.setSaveButtonEnabled(false);
    }

    protected void enableEditing() {
        keyTextField.setEnabled(true);
        valueTextArea.setEnabled(true);
    }

    private void onValueChange(final TextChangeEvent event) {
        if (hasCreatePermission() || hasUpdatePermission()) {
            if (!keyTextField.getValue().isEmpty() && !event.getText().isEmpty()) {
                metadataWindow.setSaveButtonEnabled(true);
            } else {
                metadataWindow.setSaveButtonEnabled(false);
            }
        }
    }

    private void setUpDetails(final Long swId, final String metaDatakey) {
        resetDetails();
        metadataWindow.clearOriginalValues();
        if (swId != null) {
            metaDataGrid.getContainerDataSource().removeAllItems();
            populateGrid();
            metaDataGrid.getSelectionModel().reset();
            if (!metaDataGrid.getContainerDataSource().getItemIds().isEmpty()) {
                if (metaDatakey == null) {
                    metaDataGrid.select(metaDataGrid.getContainerDataSource().getIdByIndex(0));
                } else {
                    metaDataGrid.select(metaDatakey);
                }
            } else if (hasCreatePermission()) {
                enableEditing();
                addIcon.setEnabled(false);
            }
        }
    }

    private void resetDetails() {
        clearFields();
        disableEditing();
        metadataWindow.setSaveButtonEnabled(false);
        addIcon.setEnabled(true);
    }

    protected void disableEditing() {
        keyTextField.setEnabled(false);
        valueTextArea.setEnabled(false);
    }

    protected TextArea getValueTextArea() {
        return valueTextArea;
    }

    protected TextField getKeyTextField() {
        return keyTextField;
    }

    protected CommonDialogWindow getMetadataWindow() {
        return metadataWindow;
    }

}
