/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2011-2021 Broad Institute, Aiden Lab, Rice University, Baylor College of Medicine
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */

package juicebox.windowui;

import com.jidesoft.swing.JideBoxLayout;
import juicebox.DirectoryManager;
import juicebox.MainWindow;
import juicebox.data.HiCFileLoader;
import juicebox.gui.SuperAdapter;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.List;
import java.util.*;

public class LoadDialog extends JDialog implements TreeSelectionListener, ActionListener {

    private static final long serialVersionUID = 9000045;
    public static File LAST_LOADED_HIC_FILE_PATH = DirectoryManager.getUserDirectory();
    private static boolean actionLock = false;
    private final boolean success;
    private final String[] searchHighlightColors = {"#ff0000", "#00ff00", "#0000ff", "#ff00ff", "#00ffff", "#ff9900", "#ff66ff", "#ffff00"};
    private final SuperAdapter superAdapter;
    private JTree tree;
    private JSplitButton openButton;
    private JMenuItem openButton30;
    private JButton cancelButton;
    private JButton localButton;
    private JButton urlButton;
    private JTextField fTextField;
    private boolean control;

    public LoadDialog(MainWindow mainWindow, Properties properties, SuperAdapter superAdapter) {
        super(mainWindow, "Select file(s) to open");

        this.superAdapter = superAdapter;

        //Create the nodes.
        final DefaultMutableTreeNode top =
                new DefaultMutableTreeNode(new ItemInfo("root", "root", ""));

        //System.out.println(properties);
        if (properties != null) {

            if (!createNodes(top, properties)) {
                dispose();
                success = false;
                return;
            }

            //Create a tree that allows one selection at a time.
            tree = new JTree(top);
            tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);

            //Listen for when the selection changes.
            tree.addTreeSelectionListener(this);
            tree.setRootVisible(false);
            tree.addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent mouseEvent) {
                    TreePath selPath = tree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY());
                    if (selPath != null) {
                        if (mouseEvent.getClickCount() == 2) {
                            DefaultMutableTreeNode node = (DefaultMutableTreeNode) selPath.getLastPathComponent();
                            if (node != null && node.isLeaf()) {
                                TreePath[] paths = new TreePath[1];
                                paths[0] = selPath;
                                loadFiles(paths, null);
                            }

                        }
                    }
                }
            });
            //Create the scroll pane and add the tree to it.
            JScrollPane treeView = new JScrollPane(tree);
            treeView.setPreferredSize(new Dimension(400, 400));
            JPanel centerPanel = new JPanel(new BorderLayout());
            centerPanel.add(treeView, BorderLayout.CENTER);
            add(centerPanel, BorderLayout.CENTER);
        } else {
            JLabel label = new JLabel("Can't find properties file; no online maps to load");
            label.setHorizontalAlignment(JLabel.CENTER);
            JPanel panel = new JPanel(new BorderLayout());
            panel.add(label, BorderLayout.CENTER);
            add(panel, BorderLayout.CENTER);
        }
        JPanel buttonPanel = new JPanel();

        openButton = createMAPQ0Button("Open (MAPQ > 0)");
        openButton.setEnabled(false);
        openButton30 = createMAPQ30Menu(openButton, "Open (MAPQ \u2265 30)");

        localButton = new JButton("Local...");
        localButton.addActionListener(this);
        localButton.setPreferredSize(new Dimension((int) localButton.getPreferredSize().getWidth(), (int) openButton.getPreferredSize().getHeight()));

        urlButton = new JButton("URL...");
        urlButton.addActionListener(this);
        urlButton.setPreferredSize(new Dimension((int) urlButton.getPreferredSize().getWidth(), (int) openButton.getPreferredSize().getHeight()));

        cancelButton = new JButton("Cancel");
        cancelButton.addActionListener(this);
        cancelButton.setPreferredSize(new Dimension((int) cancelButton.getPreferredSize().getWidth(), (int) openButton.getPreferredSize().getHeight()));

        buttonPanel.add(openButton);
        buttonPanel.add(localButton);
        buttonPanel.add(urlButton);
        buttonPanel.add(cancelButton);

        add(buttonPanel, BorderLayout.SOUTH);
        Dimension minimumSize = new Dimension(700, 400);
        setMinimumSize(minimumSize);
        setLocation(100, 100);
        pack();
        success = true;

        final JLabel fLabel = new JLabel();
        fTextField = new JTextField();
        fLabel.setText("Filter:");
        fTextField.setToolTipText("Case Sensitive Search");
        fTextField.setPreferredSize(new Dimension((int) cancelButton.getPreferredSize().getWidth(), (int) openButton.getPreferredSize().getHeight()));
        buttonPanel.add(fLabel, JideBoxLayout.FIX);
        buttonPanel.add(fTextField, JideBoxLayout.VARY);

        //*********************SEARCH FILTER*******************************

        fTextField.addKeyListener(new KeyAdapter() {
            public void keyReleased(KeyEvent e) {
                collapseAll(tree);
                @SuppressWarnings("unchecked")
                Enumeration<TreeNode> en = top.preorderEnumeration();
                if (!fTextField.getText().isEmpty()) {
                    String[] searchStrings = fTextField.getText().split(",");
                    colorSearchStrings(searchStrings); //Coloring text that matches input
                    while (en.hasMoreElements()) {
                        TreeNode leaf = en.nextElement();
                        String str = leaf.toString();
                        for (String term : searchStrings) {
                            if (str.contains(term)) {
                                expandToWantedNode(leaf);
                                break;
                            }
                        }
                    }
                }
            }
        });

    }

    @Nullable
    public static TreePath getTreePath(TreeNode treeNode) {
        List<Object> nodes = new ArrayList<>();
        if (treeNode != null) {
            nodes.add(treeNode);
            treeNode = treeNode.getParent();
            while (treeNode != null) {
                nodes.add(0, treeNode);
                treeNode = treeNode.getParent();
            }
        }

        return nodes.isEmpty() ? null : new TreePath(nodes.toArray());
    }

    private JSplitButton createMAPQ0Button(String buttonText) {
        JSplitButton button = new JSplitButton(buttonText);
        button.addActionListener(this);
        return button;
    }

    private JMenuItem createMAPQ30Menu(JSplitButton button, String button30Text) {
        JMenuItem button30 = new JMenuItem(button30Text);
        button30.addActionListener(this);
        button30.setEnabled(false);
        JPopupMenu popupMenu = new JPopupMenu("MAPQ ≥ 30 Menu");
        popupMenu.add(button30);
        popupMenu.setEnabled(false);
        button.setComponentPopupMenu(popupMenu);
        return button30;
    }

    public static TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
        TreeNode[] retNodes;
        if (aNode == null) {
            if (depth == 0)
                return null;
            else
                retNodes = new TreeNode[depth];
        } else {
            depth++;
            retNodes = getPathToRoot(aNode.getParent(), depth);
            retNodes[retNodes.length - depth] = aNode;
        }
        return retNodes;
    }

    private void expandToWantedNode(TreeNode dNode) {
        if (dNode != null) {
            tree.setExpandsSelectedPaths(true);
            TreePath path = new TreePath(getPathToRoot(dNode, 0));
            tree.scrollPathToVisible(path);
            tree.setSelectionPath(path);
        }
    }

    //Overriding in order to change text color
    private void colorSearchStrings(final String[] parts) {
        tree.setCellRenderer(new JBTreeCellRenderer(parts, searchHighlightColors));
    }

    public void setControl(boolean control) {
        this.control = control;
    }

    public boolean getSuccess() {
        return success;
    }

    private boolean createNodes(DefaultMutableTreeNode top, Properties properties) {
        // Enumeration<DefaultMutableTreeNode> enumeration = top.breadthFirstEnumeration();
        // TreeSet is sorted, so properties file is implemented in order
        TreeSet<String> keys = new TreeSet<>(properties.stringPropertyNames());
        HashMap<String, DefaultMutableTreeNode> hashMap = new HashMap<>();
        hashMap.put(((ItemInfo) top.getUserObject()).uid, top);
        //HashMap<String, DefaultMutableTreeNode> tempHash = new HashMap<String, DefaultMutableTreeNode>();
        //tempHash.put(((ItemInfo) top.getUserObject()).uid, top);

        for (String key : keys) {
            String value = properties.getProperty(key);
            DefaultMutableTreeNode node;
            final String[] values = value.split(",");
            if (values.length != 3 && values.length != 2) {
                JOptionPane.showMessageDialog(this, "Improperly formatted properties file; incorrect # of fields", "Error", JOptionPane.ERROR_MESSAGE);
                return true;
            }
            if (values.length == 2) {
                node = new DefaultMutableTreeNode(new ItemInfo(key, values[0], values[1]));
            } else {
                node = new DefaultMutableTreeNode(new ItemInfo(key, values[0], values[1], values[2]));
            }
            hashMap.put(key, node);
        }
        for (String key : keys) {
            DefaultMutableTreeNode node = hashMap.get(key);
            DefaultMutableTreeNode parent = hashMap.get(((ItemInfo) node.getUserObject()).parentKey);

            if (parent == null) {
                JOptionPane.showMessageDialog(this, "Improperly formatted properties file; unable to find parent menu "
                        + ((ItemInfo) hashMap.get(key).getUserObject()).parentKey + " for " +
                        key, "Error", JOptionPane.ERROR_MESSAGE);
                return false;
            } else {
                parent.add(node);
            }
        }
        return true;
    }

    /**
     * Required by TreeSelectionListener interface.
     */
    public void valueChanged(TreeSelectionEvent e) {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode)
                tree.getLastSelectedPathComponent();

        if (node == null) return;

        if (node.isLeaf()) {
            openButton.setEnabled(true);
    
            // maternal paternal
            openButton30.setEnabled(!((ItemInfo) node.getUserObject()).itemName.contains("aternal"));
        } else {
            openButton.setEnabled(false);
            openButton30.setEnabled(false);
        }
    }

    public void actionPerformed(ActionEvent e) {
        if (!actionLock) {
            try {
                //use lock as double click protection.
                actionLock = true;
                if (e.getSource() == openButton) {
                    loadFiles(tree.getSelectionPaths(), null);
                } else if (e.getSource() == openButton30) {
                    loadFiles(tree.getSelectionPaths(), "30");
                } else if (e.getSource() == localButton) {
                    LAST_LOADED_HIC_FILE_PATH = HiCFileLoader.loadMenuItemActionPerformed(superAdapter, control, LAST_LOADED_HIC_FILE_PATH);
                    setVisible(false);
                } else if (e.getSource() == urlButton) {
                    HiCFileLoader.loadFromURLActionPerformed(superAdapter, control);
                    setVisible(false);
                } else if (e.getSource() == cancelButton) {
                    setVisible(false);
                    dispose();
                }
            } finally {
                actionLock = false;
            }
        }
    }

    private void loadFiles(TreePath[] paths, String ext) {
        ArrayList<ItemInfo> filesToLoad = new ArrayList<>();
        StringBuilder title = new StringBuilder();

        for (TreePath path : paths) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
            if (node != null && node.isLeaf()) {
                filesToLoad.add((ItemInfo) node.getUserObject());
                title.append(path.toString().replace("[", "").replace("]", "").replace(",", ""));
                if (ext != null) title.append(" MAPQ \u2265 ").append(ext);
            }
        }


        setVisible(false);
        java.util.List<String> urls = new ArrayList<>();
        for (ItemInfo info : filesToLoad) {
            if (info.itemURL == null || !info.itemURL.endsWith(".hic")) {
                JOptionPane.showMessageDialog(this, info.itemName + " is not a hic file, or the path to the file is not specified.");
                continue;
            }
            String toadd = info.itemURL;
            if (ext != null) {
                toadd = toadd.replace(".hic", "_" + ext + ".hic");
            }
            System.out.println("New URL: " + toadd);
            urls.add(toadd);
        }

        //code to add a recent file to the menu
        superAdapter.safeLoad(urls, control, title.toString());
    }


    private void collapseAll(JTree tree) {
        int row = tree.getRowCount() - 1;
        while (row >= 0) {
            tree.collapseRow(row);
            row--;
        }
    }


    private class ItemInfo {
        final String uid;
        final String itemName;
        final String parentKey;
        String itemURL;

        ItemInfo(String uid, String parentKey, String itemName, String itemURL) {
            this.uid = uid;
            this.parentKey = parentKey;
            this.itemName = itemName.trim();
            this.itemURL = itemURL.trim();
        }

        ItemInfo(String uid, String parentKey, String itemName) {
            this.parentKey = parentKey;
            this.itemName = itemName;
            this.uid = uid;
        }

        public String toString() {
            return itemName;
        }

    }
}