package com.cxplan.projection.ui.util;

import com.cxplan.projection.ui.component.WindowMeta;
import com.cxplan.projection.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.io.File;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created on 2017/5/6.
 *
 * @author kenny
 */
public class GUIUtil {

    private static final Logger logger = LoggerFactory.getLogger(GUIUtil.class);

    public static JFrame mainFrame;
    public static Image appLogo;

    private static Map<Class<? extends Window>, Set<Window>> windowMap = new ConcurrentHashMap<>();
    public static FileFilter[] imageFilters;

    static {
        imageFilters = new FileFilter[6];
        imageFilters[0] = new ImageFileFilter(new String[]{"jpg", "png", "jpeg", "bmp", "gif"});
        imageFilters[1] = new ImageFileFilter("png");
        imageFilters[2] = new ImageFileFilter("jpeg");
        imageFilters[3] = new ImageFileFilter("bmp");
        imageFilters[4] = new ImageFileFilter("gif");
        imageFilters[5] = new ImageFileFilter("jpg");
    }

    public static <T extends Window> T getWindow(Class<? extends Window> clazz) {
        WindowMeta wm = clazz.getAnnotation(WindowMeta.class);
        if (wm != null && wm.instance() == WindowMeta.MULTI) {
            throw new RuntimeException("The window is allowed more than one instance.");
        }
        Set<Window> windowSet = windowMap.get(clazz);
        if (windowSet == null || windowSet.size() == 0) {
            return null;
        } else {
            for (Window window : windowSet) {
                return (T) window;
            }
        }

        return null;
    }

    public static void setWindow(Class<? extends Window> clazz, Window window) {
        WindowMeta wm = window.getClass().getAnnotation(WindowMeta.class);
        boolean multiInstance = false;
        if (wm != null) {
            multiInstance = wm.instance() == WindowMeta.MULTI;
        }

        Set<Window> windowSet = windowMap.get(clazz);
        if (windowSet == null) {
            synchronized (windowMap) {
                windowSet = new HashSet<>();
                windowMap.put(clazz, windowSet);
            }
        }

        if (multiInstance) {
            windowSet.add(window);
        } else {
            windowSet.clear();
            windowSet.add(window);
        }
    }

    public static boolean removeWindow(Window window) {
        Set<Window> windowSet = windowMap.get(window.getClass());
        if (windowSet == null) {
            return false;
        }
        return windowSet.remove(window);
    }

    public static void resetWindows() {
        Iterator<Map.Entry<Class<? extends Window>, Set<Window>>> it = windowMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<Class<? extends Window>, Set<Window>> entry = it.next();
            Set<Window> windowSet = entry.getValue();
            boolean isMain = false;
            if (windowSet != null) {
                for (Window window : windowSet) {
                    if (window == mainFrame) {
                        window.setVisible(false);
                        isMain = true;
                    } else {
                        window.dispose();
                    }
                }
            }
            if (!isMain) {
                it.remove();
                if (windowSet != null) {
                    windowSet.clear();
                }
            }
        }
        logger.info("The windowMap size: {}", windowMap.size());
    }

    /**
     * Place frame container at the center of owner container.
     *
     * @param owner the referred container.
     * @param frame the container need be modify position.
     */
    public static void centerFrameToFrame(Container owner, Container frame) {
        if (owner == null) {
            Dimension rect = Toolkit.getDefaultToolkit().getScreenSize();
            frame.setBounds((int) ((rect.getWidth() - frame.getWidth()) / 2),
                    (int) ((rect.getHeight() - frame.getHeight()) / 2), frame
                            .getWidth(), frame.getHeight());

            return;
        }
        Rectangle rect = owner.getBounds();
        frame
                .setBounds((int) (rect.getX() + (rect.getWidth() - frame
                        .getWidth()) / 2), (int) (rect.getY() + (rect
                        .getHeight() - frame.getHeight()) / 2), frame
                        .getWidth(), frame.getHeight());
    }

    public static void centerToOwnerWindow(Window frame) {
        if (frame == null)
            return;
        Container owner = frame.getOwner();
        if (owner == null)
            return;
        centerFrameToFrame(owner, frame);
    }

    public static void showWindowCenterToOwner(Window frame) {
        centerToOwnerWindow(frame);
        frame.setVisible(true);
    }


    public static void showErrorMessageDialog(String message) {
        showMessageDialog(findLikelyOwnerWindow(), message, "ERROR", JOptionPane.ERROR_MESSAGE);
    }

    public static void showErrorMessageDialog(String message, String title) {
        showMessageDialog(findLikelyOwnerWindow(), message, title, JOptionPane.ERROR_MESSAGE);
    }

    public static void showInfoMessageDialog(String message) {
        showInfoMessageDialog(message, "INFO");
    }

    public static void showInfoMessageDialog(String message, String title) {
        showMessageDialog(findLikelyOwnerWindow(), message, title, JOptionPane.INFORMATION_MESSAGE);
    }

    public static void showMessageDialog(final Component parent, final String message, final String title, final int messageType) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JOptionPane.showMessageDialog(parent, message, title, messageType);
            }
        });
    }

    public static boolean showConfirmDialog(String message) {
        return showConfirmDialog(findLikelyOwnerWindow(), message);
    }

    public static boolean showConfirmDialog(Component parent, String message) {
        int ret = JOptionPane.showConfirmDialog(parent, message, "Confirm Dialog", JOptionPane.YES_NO_OPTION);
        return ret == JOptionPane.YES_OPTION;
    }

    /**
     * Will invoke the specified action later in EDT in case it is called from non-EDT thread.
     * Otherwise action will be performed immediately.
     *
     * @param runnable runnable
     */
    public static void invokeLater ( final Runnable runnable ) {
        if ( SwingUtilities.isEventDispatchThread () )
        {
            runnable.run ();
        }
        else
        {
            SwingUtilities.invokeLater ( runnable );
        }
    }

    /**
     * Return the string content in clipboard
     */
    public static String getClipboardText() {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable content = clipboard.getContents(null);
        if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
            String text = null;//从数据中获取文本值
            try {
                text = (String) content.getTransferData(DataFlavor.stringFlavor);
            } catch (Exception e1) {
                logger.error(e1.getMessage(), e1);
            }
            return text;
        } else {
            return null;
        }
    }

    public static String showInputDialog(String message) {
        return showInputDialog(message, null);
    }

    /**
     * Null indicates user cancel input.
     */
    public static String showInputDialog(String message, String initValue) {
        String value;
        if (initValue != null) {
            value = JOptionPane.showInputDialog(findLikelyOwnerWindow(), message, initValue);
        } else {
            value = JOptionPane.showInputDialog(findLikelyOwnerWindow(), message);
        }
        if (value == null) {
            return null;
        }
        value = value.trim();
        if (StringUtil.isEmpty(value)) {
            return showInputDialog(message, initValue);
        }
        return value;
    }

    /**
     * Returns the focused Window, if the focused Window is in the same context
     * as the calling thread. The focused Window is the Window that is or
     * contains the focus owner.
     *
     * @return the focused Window
     */
    public static Window findLikelyOwnerWindow() {
        Window result = KeyboardFocusManager.getCurrentKeyboardFocusManager()
                .getFocusedWindow();

        return result;
    }

    public static <T extends Container> T getTopParent(Container com) {
        return (T) getUpParent(com, Window.class);
    }

    /**
     * @see #getUpParent(Container, Class, boolean)
     */
    public static <T> T getUpParent(Container com, Class<T> parent) {
        return getUpParent(com, parent, true);
    }

    /**
     * Retrieve the parent component matched specified type.
     *
     * @param com           current component.
     * @param parent        The class type of  parent component
     * @param isMustVisible specify whether returned parent component must be visible.
     * @return the parent component object.
     */
    public static <T> T getUpParent(Container com, Class<T> parent,
                                    boolean isMustVisible) {
        if (com == null || parent == null)
            return null;
        if (parent == com.getClass())
            return (T) com;
        Container con = com.getParent();
        for (; con != null && !parent.isAssignableFrom(con.getClass()); con = con
                .getParent())
            ;
        if (con != null && !con.isVisible() && isMustVisible) {
            con = (Container) getUpParent(con, parent, isMustVisible);
        }
        return (T) con;
    }

    /**
     * Return the text content on clipboard.
     */
    public static String getClipText() {
        Transferable transfer = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
        if (!transfer.isDataFlavorSupported(DataFlavor.stringFlavor)) {
            return null;
        }
        String dataObj;
        try {
            dataObj = (String) transfer.getTransferData(DataFlavor.stringFlavor);
            return dataObj;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * Copy specified text to clipboard.
     */
    public static void copyText2Clipboard(String text) {
        if (text != null) {
            Transferable tText = new StringSelection(text);
            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(tText, null);
        }
    }

    /**
     * Return the file list on clipboard.
     */
    public static java.util.List<File> getClipFile() {
        Transferable transfer = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
        if (!transfer.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
            return null;
        }
        java.util.List<File> fileList;
        try {
            fileList = (java.util.List<File>) transfer.getTransferData(DataFlavor.javaFileListFlavor);
            return fileList;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return null;
        }
    }

    public static String lastSelectedDir;

    /**
     * Select a file location to save data.
     *
     * @param con        the parent container.
     * @param sampleName the initial name of file.
     * @return a path of file, null value will be returned if nothing is selected.
     */
    public static File saveFile(Container con, String sampleName) {
        File[] ret = selectFileByFilter(con, null, null, new String[]{sampleName},
                lastSelectedDir == null ? "" : lastSelectedDir, false, false, false);
        if (ret == null || ret.length == 0) {
            return null;
        }
        return ret[0];
    }

    /**
     * open a image, single file only
     */
    public static File selectImageFile(Container con) {
        File[] ret = selectImageFile(con, false);
        if (ret == null) {
            return null;
        } else {
            return ret[0];
        }
    }

    public static File[] selectImageFile(Container con, boolean isMulti) {
        File[] ret = selectFileByFilter(con, imageFilters[0], imageFilters, null,
                lastSelectedDir == null ? "" : lastSelectedDir, isMulti, true, false);
        return ret;
    }

    public static File[] selectFile(Container container, String selectedFile, boolean isMultiSelect) {
        File[] ret = selectFileByFilter(container, null, null, selectedFile != null ? new String[]{selectedFile} : null,
                lastSelectedDir == null ? "" : lastSelectedDir, isMultiSelect, false, false);
        if (ret == null) {
            return null;
        } else {
            return ret;
        }
    }

    public static File[] selectFile(Container container, boolean isMultiSelect,
                                    FileFilter initialFilter, FileFilter filters[], boolean isOpen) {
        File[] ret = selectFileByFilter(container, initialFilter, filters, null,
                lastSelectedDir == null ? "" : lastSelectedDir, isMultiSelect, isOpen, false);
        if (ret == null) {
            return null;
        } else {
            return ret;
        }
    }

    public static File[] selectDirectory(Container container, String currentDir, boolean isMultiSelect, boolean isOpen) {
        if (currentDir == null) {
            currentDir = lastSelectedDir;
        }
        JFileChooser fc = new JFileChooser(currentDir);
        fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        fc.setMultiSelectionEnabled(isMultiSelect);

        int select = isOpen ? fc.showOpenDialog(container) : fc.showSaveDialog(container);
        if (select == JFileChooser.APPROVE_OPTION) {
            File[] files;
            if (fc.isMultiSelectionEnabled()) {
                files = fc.getSelectedFiles();
                lastSelectedDir = files[0].getParent();
                return files;
            } else {
                files = new File[]{fc.getSelectedFile()};
                lastSelectedDir = files[0].getParent();
                return files;
            }
        } else {
            return null;
        }
    }

    public static File[] selectFileByFilter(Container con, FileFilter initialFilter, FileFilter filters[], String[] selectedFiles,
                                            String currentDir, boolean isMutiSelectable, boolean isOpen, boolean isPromptOnExist) {
        JFileChooser fc = new JFileChooser(currentDir);

        if (filters != null) {
            for (int i = 0; i < filters.length; i++) {
                if (filters[i] == null)
                    continue;
                fc.addChoosableFileFilter(filters[i]);
            }
        }
        if (selectedFiles != null && selectedFiles.length > 0) {
            File[] tmpFiles = new File[selectedFiles.length];
            for (int i = 0; i < tmpFiles.length; i++) {
                tmpFiles[i] = new File(currentDir, selectedFiles[i]);
            }
            fc.setSelectedFiles(tmpFiles);
        }
        if (initialFilter != null)
            fc.setFileFilter(initialFilter);
        fc.setMultiSelectionEnabled(isMutiSelectable);
        int select = isOpen ? fc.showOpenDialog(con) : fc.showSaveDialog(con);
        if (select == JFileChooser.APPROVE_OPTION) {
            File[] tmp;
            if (isMutiSelectable)
                tmp = fc.getSelectedFiles();
            else {
                tmp = new File[]{fc.getSelectedFile()};
            }
            if (tmp != null && tmp.length == 1) {//do only  when one file is selected .
                if (isPromptOnExist && !isOpen && tmp[0].exists()) {
                    int result = JOptionPane.showConfirmDialog(con,
                            "The file exists already, overw" +
                                    "" +
                                    "rite it?",
                            "Confirm!", JOptionPane.YES_NO_OPTION);
                    if (result == JOptionPane.NO_OPTION)
                        return selectFileByFilter(con, initialFilter, filters, selectedFiles, currentDir, isMutiSelectable, isOpen, isPromptOnExist);
                }
            }

            if (tmp != null && tmp.length > 0) {
                lastSelectedDir = tmp[0].getParent();
            }
            return tmp;
        } else
            return null;
    }

    private static class ImageFileFilter extends FileFilter {

        private String[] name;

        public ImageFileFilter(String name) {
            this.name = new String[1];
            this.name[0] = name;
        }

        public ImageFileFilter(String[] name) {
            this.name = name;
        }

        @Override
        public boolean accept(File f) {
            if (f.isDirectory()) {
                return true;
            }
            String lowName = f.getName().toLowerCase();
            for (String tmp : name) {
                if (lowName.endsWith("." + tmp)) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public String getDescription() {
            return "image:" + getNameString();
        }

        private String getNameString() {
            if (name.length == 1) {
                return name[0];
            }
            StringBuilder sb = new StringBuilder();
            for (String tmp : name) {
                sb.append(tmp).append(",");
            }
            sb.deleteCharAt(sb.length() - 1);
            return sb.toString();
        }
    }

}