/*
 * Copyright 2000-2016 Vaadin Ltd.
 *
 * 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.vaadin.client.extensions;

import java.util.HashMap;
import java.util.Map;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.DataTransfer;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.xhr.client.XMLHttpRequest;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.dnd.FileDropTargetClientRpc;
import com.vaadin.shared.ui.dnd.FileDropTargetRpc;
import com.vaadin.shared.ui.dnd.FileDropTargetState;
import com.vaadin.shared.ui.dnd.FileParameters;
import com.vaadin.ui.dnd.FileDropTarget;

import elemental.events.Event;
import elemental.html.File;
import elemental.html.FileList;

/**
 * Extension to add file drop target functionality to a widget. It allows
 * dropping files onto the widget and uploading the dropped files to the server.
 *
 * @author Vaadin Ltd
 * @since 8.1
 */
@Connect(FileDropTarget.class)
public class FileDropTargetConnector extends DropTargetExtensionConnector {

    /**
     * Contains files and their IDs that are waiting to be uploaded.
     */
    private Map<String, File> filesToUpload = new HashMap<>();

    /**
     * Contains file IDs and upload URLs.
     */
    private Map<String, String> uploadUrls = new HashMap<>();

    /**
     * Counting identifier for the files to be uploaded.
     */
    private int fileId = 0;

    /**
     * Indicates whether a file is being uploaded.
     */
    private boolean uploading = false;

    /**
     * Constructs file drop target connector.
     */
    public FileDropTargetConnector() {
        registerRpc(FileDropTargetClientRpc.class,
                (FileDropTargetClientRpc) urls -> {
                    uploadUrls.putAll(urls);
                    uploadNextFile();
                });
    }

    /**
     * Uploads a file from the waiting list in case there are no files being
     * uploaded.
     */
    private void uploadNextFile() {
        Scheduler.get().scheduleDeferred(() -> {
            if (!uploading && uploadUrls.size() > 0) {
                uploading = true;
                String nextId = uploadUrls.keySet().stream().findAny().get();

                String url = uploadUrls.remove(nextId);
                File file = filesToUpload.remove(nextId);

                FileUploadXHR xhr = (FileUploadXHR) FileUploadXHR.create();
                xhr.setOnReadyStateChange(xmlHttpRequest -> {
                    if (xmlHttpRequest.getReadyState() == XMLHttpRequest.DONE) {
                        // Poll server for changes
                        getRpcProxy(FileDropTargetRpc.class).poll();
                        uploading = false;
                        uploadNextFile();
                        xmlHttpRequest.clearOnReadyStateChange();
                    }
                });
                xhr.open("POST", getConnection().translateVaadinUri(url));
                xhr.postFile(file);
            }
        });
    }

    @Override
    protected void onDrop(Event event) {
        DataTransfer dataTransfer = ((NativeEvent) event).getDataTransfer();
        FileList files = getFiles(dataTransfer);

        if (files != null) {
            Map<String, FileParameters> fileParams = new HashMap<>();
            for (int i = 0; i < files.getLength(); i++) {
                File file = files.item(i);

                // Make sure the item is indeed a file and not a folder
                if (isFile(file, i, dataTransfer)) {
                    String id = String.valueOf(++this.fileId);

                    filesToUpload.put(id, file);
                    fileParams.put(id, new FileParameters(file.getName(),
                            (long) file.getSize(), file.getType()));
                }
            }

            // Request a list of upload URLs for the dropped files
            if (fileParams.size() > 0) {
                getRpcProxy(FileDropTargetRpc.class).drop(fileParams);
            }

            event.preventDefault();
            event.stopPropagation();
        }
    }

    @Override
    public FileDropTargetState getState() {
        return (FileDropTargetState) super.getState();
    }

    /**
     * Returns the files parameter of the dataTransfer object.
     *
     * @param dataTransfer
     *         DataTransfer object to retrieve files from.
     * @return {@code DataTransfer.files} parameter of the given dataTransfer
     * object.
     */
    private native FileList getFiles(DataTransfer dataTransfer)
    /*-{
        return dataTransfer.files;
    }-*/;

    /**
     * Checks whether the file on the given index is indeed a file or a folder.
     *
     * @param file
     *         File object to prove it is not a folder.
     * @param fileIndex
     *         Index of the file object.
     * @param dataTransfer
     *         DataTransfer object that contains the list of files.
     * @return {@code true} if the given file at the given index is not a
     * folder, {@code false} otherwise.
     */
    private native boolean isFile(File file, int fileIndex,
            DataTransfer dataTransfer)
    /*-{
        // Chrome >= v21 and Opera >= v?
        if (dataTransfer.items) {
            var item = dataTransfer.items[fileIndex];
            if (typeof item.webkitGetAsEntry == "function") {
                var entry = item.webkitGetAsEntry();
                if (typeof entry !== "undefined" && entry !== null) {
                    return entry.isFile;
                }
            }
        }

        // Zero sized files without a type are also likely to be folders
        if (file.size == 0 && !file.type) {
            return false;
        }

        // TODO Make it detect folders on all browsers

        return true;
    }-*/;

    /**
     * XHR that is used for uploading a file to the server.
     */
    private static class FileUploadXHR extends XMLHttpRequest {

        protected FileUploadXHR() {
        }

        public final native void postFile(File file) /*-{
            this.setRequestHeader('Content-Type', 'multipart/form-data');
            this.send(file);
        }-*/;

    }
}

@@ -4570,24 +4570,75 @@
 );%0A        %7D
+%0A%0A        removeDragOverStyle((NativeEvent) event);
 %0A    %7D%0A%0A    

Remove drag over style on file drop (#9888)