/**
 * 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.artifacts.upload;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.servlet.MultipartConfigElement;

import org.apache.commons.io.FileUtils;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent;
import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent.SoftwareModuleEventType;
import org.eclipse.hawkbit.ui.artifacts.event.UploadArtifactUIEvent;
import org.eclipse.hawkbit.ui.artifacts.event.UploadFileStatus;
import org.eclipse.hawkbit.ui.artifacts.event.UploadStatusEvent;
import org.eclipse.hawkbit.ui.artifacts.event.UploadStatusEvent.UploadStatusEventType;
import org.eclipse.hawkbit.ui.artifacts.state.ArtifactUploadState;
import org.eclipse.hawkbit.ui.artifacts.state.CustomFile;
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
import org.eclipse.hawkbit.ui.dd.criteria.ServerItemIdClientCriterion;
import org.eclipse.hawkbit.ui.dd.criteria.ServerItemIdClientCriterion.Mode;
import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmall;
import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
import org.eclipse.hawkbit.ui.utils.SPUIDefinitions;
import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions;
import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
import org.eclipse.hawkbit.ui.utils.UINotification;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.vaadin.spring.events.EventBus;
import org.vaadin.spring.events.EventBus.UIEventBus;
import org.vaadin.spring.events.EventScope;
import org.vaadin.spring.events.annotation.EventBusListenerMethod;

import com.google.common.base.Strings;
import com.vaadin.event.dd.DragAndDropEvent;
import com.vaadin.event.dd.DropHandler;
import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
import com.vaadin.event.dd.acceptcriteria.Not;
import com.vaadin.server.FontAwesome;
import com.vaadin.server.Page;
import com.vaadin.server.StreamVariable;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.DragAndDropWrapper;
import com.vaadin.ui.DragAndDropWrapper.WrapperTransferable;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Html5File;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI;
import com.vaadin.ui.Upload;
import com.vaadin.ui.VerticalLayout;

/**
 * Upload files layout.
 */
public class UploadLayout extends VerticalLayout {
    private static final String HTML_DIV = "</div>";

    private static final long serialVersionUID = -566164756606779220L;

    private static final Logger LOG = LoggerFactory.getLogger(UploadLayout.class);

    private final UploadStatusInfoWindow uploadInfoWindow;

    private final VaadinMessageSource i18n;

    private final UINotification uiNotification;

    private final transient EventBus.UIEventBus eventBus;

    private final ArtifactUploadState artifactUploadState;

    private final transient MultipartConfigElement multipartConfigElement;

    private final List<String> duplicateFileNamesList = new ArrayList<>();

    private Button processBtn;

    private Button discardBtn;

    private UploadConfirmationWindow currentUploadConfirmationwindow;

    private final UI ui;

    private HorizontalLayout fileUploadLayout;

    private DragAndDropWrapper dropAreaWrapper;

    private boolean hasDirectory;

    private Button uploadStatusButton;

    private static AcceptCriterion acceptAllExceptBlacklisted = new Not(new ServerItemIdClientCriterion(Mode.PREFIX,
            UIComponentIdProvider.UPLOAD_SOFTWARE_MODULE_TABLE, UIComponentIdProvider.UPLOAD_TYPE_BUTTON_PREFIX));

    private final transient ArtifactManagement artifactManagement;

    public UploadLayout(final VaadinMessageSource i18n, final UINotification uiNotification, final UIEventBus eventBus,
            final ArtifactUploadState artifactUploadState, final MultipartConfigElement multipartConfigElement,
            final ArtifactManagement artifactManagement) {
        this.uploadInfoWindow = new UploadStatusInfoWindow(eventBus, artifactUploadState, i18n);
        this.i18n = i18n;
        this.uiNotification = uiNotification;
        this.eventBus = eventBus;
        this.artifactUploadState = artifactUploadState;
        this.multipartConfigElement = multipartConfigElement;
        this.artifactManagement = artifactManagement;

        createComponents();
        buildLayout();
        restoreState();
        eventBus.subscribe(this);
        ui = UI.getCurrent();
    }

    @EventBusListenerMethod(scope = EventScope.UI)
    void onEvent(final UploadArtifactUIEvent event) {
        if (event == UploadArtifactUIEvent.DELETED_ALL_SOFWARE) {
            ui.access(this::updateActionCount);
        } else if (event == UploadArtifactUIEvent.MINIMIZED_STATUS_POPUP) {
            ui.access(this::showUploadStatusButton);
        } else if (event == UploadArtifactUIEvent.MAXIMIZED_STATUS_POPUP) {
            ui.access(this::maximizeStatusPopup);
        } else if (event == UploadArtifactUIEvent.ARTIFACT_RESULT_POPUP_CLOSED) {
            ui.access(this::closeUploadStatusPopup);
        }
    }

    @EventBusListenerMethod(scope = EventScope.UI)
    void onEvent(final UploadStatusEvent event) {
        if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_STARTED) {
            ui.access(this::onStartOfUpload);
        } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_FAILED) {
            ui.access(() -> onUploadFailure(event));
        } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_FINISHED) {
            ui.access(this::onUploadCompletion);
        } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_SUCCESSFUL) {
            ui.access(() -> onUploadSuccess(event));
        } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_STREAMING_FAILED) {
            ui.access(() -> onUploadStreamingFailure(event));
        } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_STREAMING_FINISHED) {
            ui.access(this::onUploadStreamingSuccess);
        }
    }

    private void createComponents() {
        createUploadStatusButton();
        createProcessButton();
        createDiscardBtn();
    }

    private void buildLayout() {

        final Upload upload = new Upload();
        final UploadHandler uploadHandler = new UploadHandler(null, 0, this, multipartConfigElement.getMaxFileSize(),
                upload, null, null);
        upload.setButtonCaption(i18n.getMessage("upload.file"));
        upload.setImmediate(true);
        upload.setReceiver(uploadHandler);
        upload.addSucceededListener(uploadHandler);
        upload.addFailedListener(uploadHandler);
        upload.addFinishedListener(uploadHandler);
        upload.addProgressListener(uploadHandler);
        upload.addStartedListener(uploadHandler);
        upload.addStyleName(SPUIStyleDefinitions.ACTION_BUTTON);

        fileUploadLayout = new HorizontalLayout();
        fileUploadLayout.setSpacing(true);
        fileUploadLayout.addStyleName(SPUIStyleDefinitions.FOOTER_LAYOUT);
        fileUploadLayout.addComponent(upload);
        fileUploadLayout.setComponentAlignment(upload, Alignment.MIDDLE_LEFT);
        fileUploadLayout.addComponent(processBtn);
        fileUploadLayout.setComponentAlignment(processBtn, Alignment.MIDDLE_RIGHT);
        fileUploadLayout.addComponent(discardBtn);
        fileUploadLayout.setComponentAlignment(discardBtn, Alignment.MIDDLE_RIGHT);
        fileUploadLayout.addComponent(uploadStatusButton);
        fileUploadLayout.setComponentAlignment(uploadStatusButton, Alignment.MIDDLE_RIGHT);
        setMargin(false);

        /* create drag-drop wrapper for drop area */
        dropAreaWrapper = new DragAndDropWrapper(createDropAreaLayout());
        dropAreaWrapper.setDropHandler(new DropAreahandler());
        setSizeFull();
        setSpacing(true);
    }

    private void restoreState() {
        updateActionCount();

        if (!artifactUploadState.getFileSelected().isEmpty() && artifactUploadState.isUploadCompleted()) {
            processBtn.setEnabled(true);
        }
        if (artifactUploadState.isStatusPopupMinimized()) {
            showUploadStatusButton();
            if (artifactUploadState.isUploadCompleted()) {
                setUploadStatusButtonIconToFinished();
            }
        }
        if (artifactUploadState.isUploadCompleted()) {
            artifactUploadState.getNumberOfFilesActuallyUpload().set(0);
            artifactUploadState.getNumberOfFileUploadsExpected().set(0);
            artifactUploadState.getNumberOfFileUploadsFailed().set(0);
        }
    }

    public DragAndDropWrapper getDropAreaWrapper() {
        return dropAreaWrapper;
    }

    private class DropAreahandler implements DropHandler {

        private static final long serialVersionUID = 1L;

        @Override
        public AcceptCriterion getAcceptCriterion() {
            return acceptAllExceptBlacklisted;
        }

        @Override
        public void drop(final DragAndDropEvent event) {
            if (validate(event)) {
                final Html5File[] files = ((WrapperTransferable) event.getTransferable()).getFiles();
                // selected software module at the time of file drop is
                // considered for upload

                artifactUploadState.getSelectedBaseSoftwareModule().ifPresent(selectedSw -> {
                    // reset the flag
                    hasDirectory = false;
                    for (final Html5File file : files) {
                        processFile(file, selectedSw);
                    }
                    if (artifactUploadState.getNumberOfFileUploadsExpected().get() > 0) {
                        processBtn.setEnabled(false);
                    } else {
                        // If the upload is not started, it signifies all
                        // dropped files as either duplicate or directory.So
                        // display message accordingly
                        displayCompositeMessage();
                    }
                });
            }
        }

        private boolean validate(final DragAndDropEvent event) {
            // check if drop is valid.If valid ,check if software module is
            // selected.
            if (!isFilesDropped(event)) {
                uiNotification.displayValidationError(i18n.getMessage("message.action.not.allowed"));
                return false;
            }
            return checkIfSoftwareModuleIsSelected();
        }

        private boolean isFilesDropped(final DragAndDropEvent event) {
            if (event.getTransferable() instanceof WrapperTransferable) {
                final Html5File[] files = ((WrapperTransferable) event.getTransferable()).getFiles();
                return files != null;
            }
            return false;
        }

        private void processFile(final Html5File file, final SoftwareModule selectedSw) {
            if (!isDirectory(file)) {
                if (!checkForDuplicate(file.getFileName(), selectedSw)) {

                    eventBus.publish(this, new UploadStatusEvent(UploadStatusEventType.RECEIVE_UPLOAD,
                            new UploadFileStatus(file.getFileName(), 0, -1, selectedSw)));

                    artifactUploadState.getNumberOfFileUploadsExpected().incrementAndGet();
                    file.setStreamVariable(createStreamVariable(file, selectedSw));
                }
            } else {
                hasDirectory = Boolean.TRUE;
            }
        }

        private StreamVariable createStreamVariable(final Html5File file, final SoftwareModule selectedSw) {
            return new UploadHandler(file.getFileName(), file.getFileSize(), UploadLayout.this,
                    multipartConfigElement.getMaxFileSize(), null, file.getType(), selectedSw);
        }

        private boolean isDirectory(final Html5File file) {
            return Strings.isNullOrEmpty(file.getType()) && file.getFileSize() % 4096 == 0;
        }
    }

    private void displayCompositeMessage() {
        final String duplicateMessage = getDuplicateFileValidationMessage();
        final StringBuilder compositeMessage = new StringBuilder();
        if (!Strings.isNullOrEmpty(duplicateMessage)) {
            compositeMessage.append(duplicateMessage);
        }
        if (hasDirectory) {
            if (compositeMessage.length() > 0) {
                compositeMessage.append("<br>");
            }
            compositeMessage.append(i18n.getMessage("message.no.directory.upload"));
        }
        if (!compositeMessage.toString().isEmpty()) {
            uiNotification.displayValidationError(compositeMessage.toString());
        }
    }

    private VerticalLayout createDropAreaLayout() {
        final VerticalLayout dropAreaLayout = new VerticalLayout();
        final Label dropHereLabel = new Label("Drop files to upload");
        dropHereLabel.setWidth(null);

        final Label dropIcon = new Label(FontAwesome.ARROW_DOWN.getHtml(), ContentMode.HTML);
        dropIcon.addStyleName("drop-icon");
        dropIcon.setWidth(null);

        dropAreaLayout.addComponent(dropIcon);
        dropAreaLayout.setComponentAlignment(dropIcon, Alignment.BOTTOM_CENTER);
        dropAreaLayout.addComponent(dropHereLabel);
        dropAreaLayout.setComponentAlignment(dropHereLabel, Alignment.TOP_CENTER);
        dropAreaLayout.setSizeFull();
        dropAreaLayout.setStyleName("upload-drop-area-layout-info");
        dropAreaLayout.setSpacing(false);
        return dropAreaLayout;
    }

    private void createProcessButton() {
        processBtn = SPUIComponentProvider.getButton(UIComponentIdProvider.UPLOAD_PROCESS_BUTTON,
                SPUILabelDefinitions.PROCESS, SPUILabelDefinitions.PROCESS, null, false, null,
                SPUIButtonStyleSmall.class);
        processBtn.setIcon(FontAwesome.BELL);
        processBtn.addStyleName(SPUIStyleDefinitions.ACTION_BUTTON);
        processBtn.addClickListener(this::displayConfirmWindow);
        processBtn.setHtmlContentAllowed(true);
        processBtn.setEnabled(false);
    }

    private void createDiscardBtn() {
        discardBtn = SPUIComponentProvider.getButton(UIComponentIdProvider.UPLOAD_DISCARD_BUTTON,
                SPUILabelDefinitions.DISCARD, SPUILabelDefinitions.DISCARD, null, false, null,
                SPUIButtonStyleSmall.class);
        discardBtn.setIcon(FontAwesome.TRASH_O);
        discardBtn.addStyleName(SPUIStyleDefinitions.ACTION_BUTTON);
        discardBtn.addClickListener(this::discardUploadData);
    }

    boolean checkForDuplicate(final String filename, final SoftwareModule selectedSw) {
        final Boolean isDuplicate = checkIfFileIsDuplicate(filename, selectedSw);
        if (isDuplicate) {
            getDuplicateFileNamesList().add(filename);
        }
        return isDuplicate;
    }

    /**
     * Save uploaded file details.
     *
     * @param stream
     *            read from uploaded file
     * @param name
     *            file name
     * @param size
     *            file size
     * @param mimeType
     *            the mimeType of the file
     * @param selectedSw
     * @throws IOException
     *             in case of upload errors
     */
    OutputStream saveUploadedFileDetails(final String name, final long size, final String mimeType,
            final SoftwareModule selectedSw) {
        File tempFile = null;
        try {
            tempFile = File.createTempFile("spUiArtifactUpload", null);

            // we return the outputstream so we cannot close it here
            @SuppressWarnings("squid:S2095")
            final OutputStream out = new FileOutputStream(tempFile);

            final String currentBaseSoftwareModuleKey = HawkbitCommonUtil.getFormattedNameVersion(selectedSw.getName(),
                    selectedSw.getVersion());

            final CustomFile customFile = new CustomFile(name, size, tempFile.getAbsolutePath(), selectedSw.getName(),
                    selectedSw.getVersion(), mimeType);

            artifactUploadState.getFileSelected().add(customFile);
            processBtn.setEnabled(false);

            if (!artifactUploadState.getBaseSwModuleList().keySet().contains(currentBaseSoftwareModuleKey)) {
                artifactUploadState.getBaseSwModuleList().put(currentBaseSoftwareModuleKey, selectedSw);
            }
            return out;
        } catch (final FileNotFoundException e) {
            LOG.error("Upload failed {}", e);
            throw new ArtifactUploadFailedException(i18n.getMessage("message.file.not.found"));
        } catch (final IOException e) {
            LOG.error("Upload failed {}", e);
            throw new ArtifactUploadFailedException(i18n.getMessage("message.upload.failed"));
        }

    }

    boolean checkIfSoftwareModuleIsSelected() {
        if (!artifactUploadState.getSelectedBaseSwModuleId().isPresent()) {
            uiNotification.displayValidationError(i18n.getMessage("message.error.noSwModuleSelected"));
            return false;
        }
        return true;
    }

    /**
     * Check if file selected is duplicate.i,e already selected for upload for
     * same software module.
     *
     * @param name
     *            file name
     * @param selectedSoftwareModule
     *            the current selected sm module
     * @return Boolean
     */
    Boolean checkIfFileIsDuplicate(final String name, final SoftwareModule selectedSoftwareModule) {
        Boolean isDuplicate = false;
        final String currentBaseSoftwareModuleKey = HawkbitCommonUtil
                .getFormattedNameVersion(selectedSoftwareModule.getName(), selectedSoftwareModule.getVersion());

        for (final CustomFile customFile : artifactUploadState.getFileSelected()) {
            final String fileSoftwareModuleKey = HawkbitCommonUtil.getFormattedNameVersion(
                    customFile.getBaseSoftwareModuleName(), customFile.getBaseSoftwareModuleVersion());
            if (customFile.getFileName().equals(name) && currentBaseSoftwareModuleKey.equals(fileSoftwareModuleKey)) {
                isDuplicate = true;
                break;
            }
        }
        return isDuplicate;
    }

    private void decreaseNumberOfFileUploadsExpected() {
        artifactUploadState.getNumberOfFileUploadsExpected().decrementAndGet();
    }

    private List<String> getDuplicateFileNamesList() {
        return duplicateFileNamesList;
    }

    /**
     * Update pending action count.
     */
    private void updateActionCount() {
        if (!artifactUploadState.getFileSelected().isEmpty()) {
            processBtn.setCaption(SPUILabelDefinitions.PROCESS + "<div class='unread'>"
                    + artifactUploadState.getFileSelected().size() + HTML_DIV);
        } else {
            processBtn.setCaption(SPUILabelDefinitions.PROCESS);
        }
    }

    private void displayDuplicateValidationMessage() {
        // check if streaming of all dropped files are completed
        if (artifactUploadState.getNumberOfFilesActuallyUpload().intValue() == artifactUploadState
                .getNumberOfFileUploadsExpected().intValue()) {
            displayCompositeMessage();
            duplicateFileNamesList.clear();
        }
    }

    private String getDuplicateFileValidationMessage() {
        final StringBuilder message = new StringBuilder();
        if (!duplicateFileNamesList.isEmpty()) {
            final String fileNames = StringUtils.collectionToCommaDelimitedString(duplicateFileNamesList);
            if (duplicateFileNamesList.size() == 1) {
                message.append(i18n.getMessage("message.no.duplicateFile") + fileNames);

            } else if (duplicateFileNamesList.size() > 1) {
                message.append(i18n.getMessage("message.no.duplicateFiles"));
            }
        }
        return message.toString();
    }

    void increaseNumberOfFileUploadsExpected() {
        artifactUploadState.getNumberOfFileUploadsExpected().incrementAndGet();
    }

    private void updateFileSize(final String name, final long size, final SoftwareModule selectedSoftwareModule) {
        final String currentBaseSoftwareModuleKey = HawkbitCommonUtil
                .getFormattedNameVersion(selectedSoftwareModule.getName(), selectedSoftwareModule.getVersion());

        for (final CustomFile customFile : artifactUploadState.getFileSelected()) {
            final String fileSoftwareModuleKey = HawkbitCommonUtil.getFormattedNameVersion(
                    customFile.getBaseSoftwareModuleName(), customFile.getBaseSoftwareModuleVersion());
            if (customFile.getFileName().equals(name) && currentBaseSoftwareModuleKey.equals(fileSoftwareModuleKey)) {
                customFile.setFileSize(size);
                break;
            }
        }
    }

    private void increaseNumberOfFilesActuallyUpload() {
        artifactUploadState.getNumberOfFilesActuallyUpload().incrementAndGet();
    }

    private void increaseNumberOfFileUploadsFailed() {
        artifactUploadState.getNumberOfFileUploadsFailed().incrementAndGet();
    }

    /**
     * Enable process button once upload is completed.
     */
    private boolean enableProcessBtn() {
        if (artifactUploadState.getNumberOfFilesActuallyUpload().intValue() >= artifactUploadState
                .getNumberOfFileUploadsExpected().intValue() && !getFileSelected().isEmpty()) {
            processBtn.setEnabled(true);
            artifactUploadState.getNumberOfFilesActuallyUpload().set(0);
            artifactUploadState.getNumberOfFileUploadsExpected().set(0);
            return true;
        }
        return false;
    }

    Set<CustomFile> getFileSelected() {
        return artifactUploadState.getFileSelected();
    }

    private void discardUploadData(final Button.ClickEvent event) {
        if (event.getButton().equals(discardBtn)) {
            if (artifactUploadState.getFileSelected().isEmpty()) {
                uiNotification.displayValidationError(i18n.getMessage("message.error.noFileSelected"));
            } else {
                clearUploadedFileDetails();
            }
        }
    }

    protected void clearUploadedFileDetails() {
        clearFileList();
        closeUploadStatusPopup();
    }

    private void closeUploadStatusPopup() {
        uploadInfoWindow.clearWindow();
        hideUploadStatusButton();
        artifactUploadState.setStatusPopupMinimized(false);
    }

    /**
     * Clear details.
     */
    void clearFileList() {
        // delete file system zombies
        artifactUploadState.getFileSelected()
                .forEach(customFile -> FileUtils.deleteQuietly(new File(customFile.getFilePath())));

        artifactUploadState.getFileSelected().clear();
        artifactUploadState.getBaseSwModuleList().clear();
        processBtn.setCaption(SPUILabelDefinitions.PROCESS);
        /* disable when there is no files to upload. */
        processBtn.setEnabled(false);
        artifactUploadState.getNumberOfFilesActuallyUpload().set(0);
        artifactUploadState.getNumberOfFileUploadsExpected().set(0);
        artifactUploadState.getNumberOfFileUploadsFailed().set(0);
        duplicateFileNamesList.clear();
    }

    private void setConfirmationPopupHeightWidth(final float newWidth, final float newHeight) {
        if (currentUploadConfirmationwindow != null) {
            currentUploadConfirmationwindow.getUploadArtifactDetails().setWidth(HawkbitCommonUtil
                    .getArtifactUploadPopupWidth(newWidth, SPUIDefinitions.MIN_UPLOAD_CONFIRMATION_POPUP_WIDTH),
                    Unit.PIXELS);
            currentUploadConfirmationwindow.getUploadDetailsTable().setHeight(HawkbitCommonUtil
                    .getArtifactUploadPopupHeight(newHeight, SPUIDefinitions.MIN_UPLOAD_CONFIRMATION_POPUP_HEIGHT),
                    Unit.PIXELS);
        }
    }

    /**
     * Set artifact upload result pop up size changes.
     *
     * @param newWidth
     *            new width of result pop up
     * @param newHeight
     *            new height of result pop up
     */
    void setResultPopupHeightWidth(final float newWidth, final float newHeight) {
        if (currentUploadConfirmationwindow != null
                && currentUploadConfirmationwindow.getCurrentUploadResultWindow() != null) {
            final UploadResultWindow uploadResultWindow = currentUploadConfirmationwindow
                    .getCurrentUploadResultWindow();
            uploadResultWindow.getUploadResultsWindow().setWidth(HawkbitCommonUtil.getArtifactUploadPopupWidth(newWidth,
                    SPUIDefinitions.MIN_UPLOAD_CONFIRMATION_POPUP_WIDTH), Unit.PIXELS);
            uploadResultWindow.getUploadResultTable().setHeight(HawkbitCommonUtil.getArtifactUploadPopupHeight(
                    newHeight, SPUIDefinitions.MIN_UPLOAD_CONFIRMATION_POPUP_HEIGHT), Unit.PIXELS);
        }
    }

    private void displayConfirmWindow(final Button.ClickEvent event) {
        if (event.getComponent().getId().equals(UIComponentIdProvider.UPLOAD_PROCESS_BUTTON)) {
            if (artifactUploadState.getFileSelected().isEmpty()) {
                uiNotification.displayValidationError(i18n.getMessage("message.error.noFileSelected"));
            } else {
                currentUploadConfirmationwindow = new UploadConfirmationWindow(this, artifactUploadState, eventBus,
                        artifactManagement);
                UI.getCurrent().addWindow(currentUploadConfirmationwindow.getUploadConfrimationWindow());
                setConfirmationPopupHeightWidth(Page.getCurrent().getBrowserWindowWidth(),
                        Page.getCurrent().getBrowserWindowHeight());
            }
        }
    }

    /**
     * @return
     */
    VaadinMessageSource getI18n() {
        return i18n;
    }

    void setCurrentUploadConfirmationwindow(final UploadConfirmationWindow currentUploadConfirmationwindow) {
        this.currentUploadConfirmationwindow = currentUploadConfirmationwindow;
    }

    private void onStartOfUpload() {
        setUploadStatusButtonIconToInProgress();
        if (artifactUploadState.isStatusPopupMinimized()) {
            updateStatusButtonCount();
        }
    }

    private void onUploadStreamingSuccess() {
        increaseNumberOfFilesActuallyUpload();
        updateUploadCounts();
        enableProcessBtn();
        if (isUploadComplete()) {
            uploadInfoWindow.uploadSessionFinished();
            setUploadStatusButtonIconToFinished();
        }
        // display the duplicate message after streaming all files
        displayDuplicateValidationMessage();
    }

    private void onUploadStreamingFailure(final UploadStatusEvent event) {
        /**
         * If upload interrupted because of duplicate file,do not remove the
         * file already in upload list
         **/
        if (getDuplicateFileNamesList().isEmpty()
                || !getDuplicateFileNamesList().contains(event.getUploadStatus().getFileName())) {
            final SoftwareModule sw = event.getUploadStatus().getSoftwareModule();
            if (sw != null) {
                getFileSelected()
                        .remove(new CustomFile(event.getUploadStatus().getFileName(), sw.getName(), sw.getVersion()));
            }
            // failed reason to be updated only if there is error other than
            // duplicate file error
            uploadInfoWindow.uploadFailed(event.getUploadStatus().getFileName(),
                    event.getUploadStatus().getFailureReason(), event.getUploadStatus().getSoftwareModule());
            increaseNumberOfFileUploadsFailed();
        }
        decreaseNumberOfFileUploadsExpected();
        updateUploadCounts();
        enableProcessBtn();
        // check if we are finished
        if (isUploadComplete()) {
            uploadInfoWindow.uploadSessionFinished();
            setUploadStatusButtonIconToFinished();
        }
        displayDuplicateValidationMessage();
    }

    private void onUploadSuccess(final UploadStatusEvent event) {
        updateFileSize(event.getUploadStatus().getFileName(), event.getUploadStatus().getContentLength(),
                event.getUploadStatus().getSoftwareModule());
        // recorded that we now one more uploaded
        increaseNumberOfFilesActuallyUpload();
    }

    private void onUploadCompletion() {
        // check if we are finished
        if (isUploadComplete()) {
            uploadInfoWindow.uploadSessionFinished();
            setUploadStatusButtonIconToFinished();
            displayDuplicateValidationMessage();
        }
        updateUploadCounts();
        enableProcessBtn();
    }

    private boolean isUploadComplete() {
        final int uploadedCount = artifactUploadState.getNumberOfFilesActuallyUpload().intValue();
        final int expectedUploadsCount = artifactUploadState.getNumberOfFileUploadsExpected().intValue();
        return uploadedCount == expectedUploadsCount;
    }

    private void onUploadFailure(final UploadStatusEvent event) {
        /**
         * If upload interrupted because of duplicate file,do not remove the
         * file already in upload list
         **/
        if (getDuplicateFileNamesList().isEmpty()
                || !getDuplicateFileNamesList().contains(event.getUploadStatus().getFileName())) {
            final SoftwareModule sw = event.getUploadStatus().getSoftwareModule();
            if (sw != null) {
                getFileSelected()
                        .remove(new CustomFile(event.getUploadStatus().getFileName(), sw.getName(), sw.getVersion()));
            }
            // failed reason to be updated only if there is error other than
            // duplicate file error
            uploadInfoWindow.uploadFailed(event.getUploadStatus().getFileName(),
                    event.getUploadStatus().getFailureReason(), event.getUploadStatus().getSoftwareModule());
            increaseNumberOfFileUploadsFailed();
            decreaseNumberOfFileUploadsExpected();
        }
    }

    /**
     * @param selectedBaseSoftwareModule
     */
    void refreshArtifactDetailsLayout(final SoftwareModule selectedBaseSoftwareModule) {
        eventBus.publish(this,
                new SoftwareModuleEvent(SoftwareModuleEventType.ARTIFACTS_CHANGED, selectedBaseSoftwareModule));
    }

    /**
     * @return the fileUploadLayout
     */
    public HorizontalLayout getFileUploadLayout() {
        return fileUploadLayout;
    }

    public UINotification getUINotification() {
        return uiNotification;
    }

    public void setHasDirectory(final Boolean hasDirectory) {
        this.hasDirectory = hasDirectory;
    }

    private void createUploadStatusButton() {
        uploadStatusButton = SPUIComponentProvider.getButton(UIComponentIdProvider.UPLOAD_STATUS_BUTTON, "", "", "",
                false, null, SPUIButtonStyleSmall.class);
        uploadStatusButton.setStyleName(SPUIStyleDefinitions.ACTION_BUTTON);
        uploadStatusButton.addStyleName(SPUIStyleDefinitions.UPLOAD_PROGRESS_INDICATOR_STYLE);
        uploadStatusButton.setWidth("100px");
        uploadStatusButton.setHtmlContentAllowed(true);
        uploadStatusButton.addClickListener(event -> onClickOfUploadStatusButton());
        uploadStatusButton.setVisible(false);
    }

    private void updateStatusButtonCount() {
        final int uploadsPending = artifactUploadState.getNumberOfFileUploadsExpected().get()
                - artifactUploadState.getNumberOfFilesActuallyUpload().get();
        final int uploadsFailed = artifactUploadState.getNumberOfFileUploadsFailed().get();
        final StringBuilder builder = new StringBuilder("");
        if (uploadsFailed != 0) {
            if (uploadsPending != 0) {
                builder.append("<div class='error-count error-count-color'>" + uploadsFailed + HTML_DIV);
            } else {
                builder.append("<div class='unread error-count-color'>" + uploadsFailed + HTML_DIV);
            }
        }
        if (uploadsPending != 0) {
            builder.append("<div class='unread'>" + uploadsPending + HTML_DIV);
        }
        uploadStatusButton.setCaption(builder.toString());
    }

    private void onClickOfUploadStatusButton() {
        artifactUploadState.setStatusPopupMinimized(false);
        eventBus.publish(this, UploadArtifactUIEvent.MAXIMIZED_STATUS_POPUP);
    }

    private void showUploadStatusButton() {
        if (uploadStatusButton == null) {
            return;
        }
        uploadStatusButton.setVisible(true);
        updateStatusButtonCount();
    }

    private void hideUploadStatusButton() {
        if (uploadStatusButton == null) {
            return;
        }
        uploadStatusButton.setVisible(false);
    }

    private void maximizeStatusPopup() {
        hideUploadStatusButton();
        uploadInfoWindow.maximizeStatusPopup();
    }

    private void setUploadStatusButtonIconToFinished() {
        if (uploadStatusButton == null) {
            return;
        }
        uploadStatusButton.removeStyleName(SPUIStyleDefinitions.UPLOAD_PROGRESS_INDICATOR_STYLE);
        uploadStatusButton.setIcon(FontAwesome.UPLOAD);
    }

    private void setUploadStatusButtonIconToInProgress() {
        if (uploadStatusButton == null) {
            return;
        }
        uploadStatusButton.addStyleName(SPUIStyleDefinitions.UPLOAD_PROGRESS_INDICATOR_STYLE);
        uploadStatusButton.setIcon(null);
    }

    protected void updateUploadCounts() {
        updateActionCount();
        updateStatusButtonCount();
    }
}
