package magic.ui;

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.nio.file.Files;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Stream;

import javax.activation.MimetypesFileTypeMap;

public class FileDropTargetListener implements DropTargetListener {

    private static final String IMAGE_FORMATS = "bmp gif jpeg jpg png wbmp";

    private final IDragDropListener listener;

    public FileDropTargetListener(final IDragDropListener listener) {
        this.listener = listener;
    }

    /**
     * This method seems to work well on linux, not so Windows
     * where it always returns null.
     */
    private String tryProbeContentType(File aFile) {
        try {
            return Files.probeContentType(aFile.toPath());
        } catch (IOException ex) {
            System.err.println(ex);
            return null;
        }
    }

    private String tryGuessContentTypeFromStream(File aFile) {
        try (final InputStream is = new BufferedInputStream(new FileInputStream(aFile))) {
            return URLConnection.guessContentTypeFromStream(is);
        } catch (IOException ex) {
            System.err.println(ex);
            return null;
        }
    }

    private String tryMimetypesFileTypeMap(File aFile) {
        final MimetypesFileTypeMap mtftp = new MimetypesFileTypeMap();
        mtftp.addMimeTypes("image " + IMAGE_FORMATS);
        mtftp.addMimeTypes("application/zip zip");
        return mtftp.getContentType(aFile);
    }

    private String getMimeType(File aFile) {
        String mimeType = tryProbeContentType(aFile);
        if (mimeType == null) {
            mimeType = tryGuessContentTypeFromStream(aFile);
        }
        if (mimeType == null) {
            mimeType = tryMimetypesFileTypeMap(aFile);
        }
        return mimeType;
    }

    private void doFileAction(File aFile) {

        final String mimeType = getMimeType(aFile);
        System.out.println(mimeType);

        if (mimeType != null) {

            // zip file
            if ("application/zip".equals(mimeType)) {
                listener.onZipFileDropped(aFile);
                return;
            }

            // image file
            // on Windows, dragging an image from the browser directly
            // returns a mime type of "image" thus the need to check parts.
            final String[] parts = mimeType.split("/");
            if ("image".equalsIgnoreCase(parts[0])) {
                if (parts.length == 1 || IMAGE_FORMATS.contains(parts[1].toLowerCase(Locale.ENGLISH))) {
                    listener.onImageFileDropped(aFile);
                    return;
                }
            }

            // game snapshot file
            if (aFile.getName().equals("snapshot.game")) {
                listener.onGameSnapshotDropped(aFile);
                return;
            }

            // delegate to screen instance.
            if (ScreenController.getActiveScreen().doFileDropAction(aFile)) {
                return;
            }
        }

        // unsupported
        MagicSound.BEEP.play();
    }

    @SuppressWarnings("unchecked")
    private Optional<File> getDroppedFile(Transferable trf) {

        // Get the data format of the first dropped item.
        final Optional<DataFlavor> fileFlavor = Stream.of(trf.getTransferDataFlavors())
                .filter(f -> f.isFlavorJavaFileListType())
                .findFirst();

        if (fileFlavor.isPresent()) {
            try {
                final Object data = trf.getTransferData(fileFlavor.get());
                return ((List<File>)data).stream().findFirst();
            } catch (UnsupportedFlavorException | IOException ex) {
                throw new RuntimeException(ex);
            }
        }

        return Optional.empty();
    }

    private void doDropAction(final DropTargetDropEvent event) {

        event.acceptDrop(DnDConstants.ACTION_COPY);

        final Optional<File> file = getDroppedFile(event.getTransferable());
        if (file.isPresent()) {
            doFileAction(file.get());
        } else {
            MagicSound.ALERT.play();
        }

        // Inform that the drop is complete
        event.dropComplete(true);
    }

    @Override
    public void drop(DropTargetDropEvent event) {
        doDropAction(event);
    }

    @Override
    public void dragEnter(DropTargetDragEvent event) {
    }

    @Override
    public void dragExit(DropTargetEvent event) {
    }

    @Override
    public void dragOver(DropTargetDragEvent event) {
    }

    @Override
    public void dropActionChanged(DropTargetDragEvent event) {
    }

}