package eu.toldi.rss;

import org.xml.sax.helpers.DefaultHandler;

import javax.naming.ldap.SortKey;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.TableRowSorter;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

//TODO: Keresés cikkek között
//TODO: Automatikus frissítés


public class FeedFrame extends JFrame {

    public JTable table;
    private JScrollPane scrollPane;
    private FeedData data;
    private JMenuBar menubar;
    private JTree feeds;
    private TableRowSorter<FeedData> sorter;
    private DefaultMutableTreeNode treeRoot;

    public FeedFrame() {

        setTitle("RSS Feed reader");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setMinimumSize(new Dimension(900, 600));
        initData();
        initComponents();
    }

    private void initData() {
        FeedLoader loader = new FeedLoader();
        data = loader.loadFeedData("feeds.xml");
    }

    private void initTable() {
        table = new JTable();
        scrollPane = new JScrollPane(table);
        table.setModel(data);
        table.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent evt) {
                int row = table.getRowSorter().convertRowIndexToModel(table.rowAtPoint(evt.getPoint()));
                int col = table.columnAtPoint(evt.getPoint());
                if (evt.getClickCount() == 2 && row >= 0) {
                    data.clicked(row);
                }
            }
        });
        sorter = new TableRowSorter<FeedData>(data);

        table.setRowSorter(sorter);
        add(scrollPane, BorderLayout.CENTER);

    }

    private void initMenuBar() {
        menubar = new JMenuBar();
        JMenu menu1 = new JMenu("File");
        JMenuItem addFeed = new JMenuItem("Add Feed");
        addFeed.addActionListener(new AddFeed());
        menu1.add(addFeed);

        JMenuItem save = new JMenuItem("Save Feeds");
        save.addActionListener(new SaveFeeds());
        menu1.add(save);
        menubar.add(menu1);
        this.setJMenuBar(menubar);
    }

    private void expandTree(JTree tree) {
        DefaultMutableTreeNode root =
                (DefaultMutableTreeNode) tree.getModel().getRoot();
        Enumeration e = root.breadthFirstEnumeration();
        while (e.hasMoreElements()) {
            DefaultMutableTreeNode node =
                    (DefaultMutableTreeNode) e.nextElement();
            if (node.isLeaf()) continue;
            int row = tree.getRowForPath(new TreePath(node.getPath()));
            tree.expandRow(row);
        }
    }


    private void initTree() {
        treeRoot = data.getRootNode();
        feeds = new JTree(treeRoot);
        feeds.addMouseListener(new SwitchFeed());
        feeds.setEditable(false);
        feeds.setDragEnabled(true);
        feeds.setDropMode(DropMode.ON_OR_INSERT);
        feeds.setTransferHandler(new TreeTransferHandler());
        feeds.getSelectionModel().setSelectionMode(
                TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
        expandTree(feeds);
        add(feeds, BorderLayout.WEST);

    }

    private void initComponents() {
        this.setLayout(new BorderLayout());
        ImageIcon icon = new ImageIcon(this.getClass().getResource("/icon.png"));
        this.setIconImage(icon.getImage());
        initTable();
        initMenuBar();
        initTree();
    }

    private void updateTeble(){
        sorter.setModel(data);
        table.setRowSorter(sorter);
        table.updateUI();
    }

    private class AddFeed implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            JPanel panel = new JPanel(new GridLayout(0, 1));
            JTextField feedURL = new JTextField("");
            panel.add(new JLabel("Feed URL:"));
            panel.add(feedURL);
            int result = JOptionPane.showConfirmDialog(null, panel, "Add feed",
                    JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
            if (result == JOptionPane.OK_OPTION) {
                Feed f = null;
                try {
                    f = new Feed(new URL(feedURL.getText()));
                } catch (MalformedURLException malformedURLException) {
                    malformedURLException.printStackTrace();
                }
                treeRoot.add(f.getTreeNode());
                data.addFeed(f);
                table.updateUI();
                feeds.updateUI();
            } else {
                System.out.println("Cancelled");
            }
        }
    }

    private class SaveFeeds implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            FeedLoader loader = new FeedLoader();
            loader.setData(data);
            loader.saveFeedData();
        }
    }

    private class SwitchFeed extends MouseAdapter {

        @Override
        public void mousePressed(MouseEvent e) {
            int selRow = feeds.getRowForLocation(e.getX(), e.getY());
            TreePath selPath = feeds.getPathForLocation(e.getX(), e.getY());
            if (selRow != -1) {
                if (e.getClickCount() == 2) {
                    if (selRow == 0) data.limitTo(null);
                    else {
                        data.limitTo((Feed) ((DefaultMutableTreeNode) selPath.getLastPathComponent()).getUserObject());
                    }
                    updateTeble();

                }
            }
        }
    }

    class TreeTransferHandler extends TransferHandler {
        DataFlavor nodesFlavor;
        DataFlavor[] flavors = new DataFlavor[1];
        List<DefaultMutableTreeNode> nodesToRemove;

        public TreeTransferHandler() {
            try {
                String mimeType = DataFlavor.javaJVMLocalObjectMimeType +
                        ";class=\"" +
                        DefaultMutableTreeNode[].class.getName() +
                        "\"";
                nodesFlavor = new DataFlavor(mimeType);
                flavors[0] = nodesFlavor;
            } catch (ClassNotFoundException e) {
                System.out.println("ClassNotFound: " + e.getMessage());
            }
        }

        public boolean canImport(TransferHandler.TransferSupport support) {
            if (!support.isDrop() || support.getDropAction() == COPY) {
                return false;
            }
            support.setShowDropLocation(true);
            if (!support.isDataFlavorSupported(nodesFlavor)) {
                return false;
            }
            // Do not allow a drop on the drag source selections.
            JTree.DropLocation dl =
                    (JTree.DropLocation) support.getDropLocation();
            JTree tree = (JTree) support.getComponent();
            int dropRow = tree.getRowForPath(dl.getPath());
            int[] selRows = tree.getSelectionRows();
            for (int i = 0; i < selRows.length; i++) {
                if (selRows[i] == dropRow) {
                    return false;
                }
            }
            int action = support.getDropAction();

            TreePath dest = dl.getPath();
            DefaultMutableTreeNode target = (DefaultMutableTreeNode) dest.getLastPathComponent();
            TreePath path = tree.getPathForRow(selRows[0]);
            DefaultMutableTreeNode firstNode = (DefaultMutableTreeNode) path.getLastPathComponent();
            if (firstNode.getChildCount() > 0 && target.getLevel() < firstNode.getLevel()) {
                return false;
            }
            if(target.isLeaf() && target.getLevel() > 1)
                return false;
            if (action == MOVE && !firstNode.isLeaf()) {
                return false;
            }
            return true;
        }

        private boolean haveCompleteNode(JTree tree) {
            int[] selRows = tree.getSelectionRows();
            TreePath path = tree.getPathForRow(selRows[0]);
            DefaultMutableTreeNode first =
                    (DefaultMutableTreeNode) path.getLastPathComponent();
            int childCount = first.getChildCount();
            // first has children and no children are selected.
            if (childCount > 0 && selRows.length == 1)
                return false;
            // first may have children.
            for (int i = 1; i < selRows.length; i++) {
                path = tree.getPathForRow(selRows[i]);
                DefaultMutableTreeNode next =
                        (DefaultMutableTreeNode) path.getLastPathComponent();
                if (first.isNodeChild(next)) {
                    // Found a child of first.
                    if (childCount > selRows.length - 1) {
                        // Not all children of first are selected.
                        return false;
                    }
                }
            }
            return true;
        }

        protected Transferable createTransferable(JComponent c) {
            JTree tree = (JTree) c;
            TreePath[] paths = tree.getSelectionPaths();
            if (paths != null) {
                // Make up a node array of copies for transfer and
                // another for/of the nodes that will be removed in
                // exportDone after a successful drop.
                List<DefaultMutableTreeNode> copies = new ArrayList<DefaultMutableTreeNode>();
                nodesToRemove = new ArrayList<DefaultMutableTreeNode>();
                DefaultMutableTreeNode node = (DefaultMutableTreeNode) paths[0].getLastPathComponent();
                DefaultMutableTreeNode copy = copy(node);
                copies.add(copy);
                nodesToRemove.add(node);
                for (int i = 1; i < paths.length; i++) {
                    DefaultMutableTreeNode next =
                            (DefaultMutableTreeNode) paths[i].getLastPathComponent();
                    // Do not allow higher level nodes to be added to list.
                    if (next.getLevel() < node.getLevel()) {
                        break;
                    } else if (next.getLevel() > node.getLevel()) {  // child node
                        copy.add(copy(next));
                        // node already contains child
                    } else {                                        // sibling
                        copies.add(copy(next));
                        nodesToRemove.add(next);
                    }
                }
                DefaultMutableTreeNode[] nodes =
                        copies.toArray(new DefaultMutableTreeNode[copies.size()]);
                return new NodesTransferable(nodes);
            }
            return null;
        }

        /**
         * Defensive copy used in createTransferable.
         */
        private DefaultMutableTreeNode copy(DefaultMutableTreeNode node) {
            return new DefaultMutableTreeNode(node.getUserObject());
        }

        protected void exportDone(JComponent source, Transferable data, int action) {
            if ((action & MOVE) == MOVE) {
                JTree tree = (JTree) source;
                DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
                // Remove nodes saved in nodesToRemove in createTransferable.
                for (int i = 0; i < nodesToRemove.size(); i++) {
                    model.removeNodeFromParent(nodesToRemove.get(i));
                }
            }
            updateTeble();
        }

        public int getSourceActions(JComponent c) {
            return COPY_OR_MOVE;
        }

        private List<DefaultMutableTreeNode> getParents(DefaultMutableTreeNode[] nodes, TreePath[] paths) {
            ArrayList<DefaultMutableTreeNode> results = new ArrayList<DefaultMutableTreeNode>();
            for (int i = 0; i < nodes.length; i++) {
                for (int j = 0; j < paths.length; j++) {
                    if (nodes[i].getUserObject().equals(((DefaultMutableTreeNode)paths[j].getLastPathComponent()).getUserObject())) {
                        results.add((DefaultMutableTreeNode) ((DefaultMutableTreeNode) paths[j].getLastPathComponent()).getParent());
                    }
                }
            }
            return results;
        }

        private int getChildIndex(DefaultMutableTreeNode parent, DefaultMutableTreeNode child){
            for (int i = 0; i < parent.getChildCount(); i++) {
                System.out.println(parent.getChildAt(i));
                if(parent.getChildAt(i).toString().equals(child.toString()))
                    return i;
            }
            return -1;
        }

        public boolean importData(TransferHandler.TransferSupport support) {
            if (!canImport(support)) {
                return false;
            }
            DefaultMutableTreeNode[] nodes = null;
            try {
                Transferable t = support.getTransferable();
                nodes = (DefaultMutableTreeNode[]) t.getTransferData(nodesFlavor);
            } catch (UnsupportedFlavorException ufe) {
                System.out.println("UnsupportedFlavor: " + ufe.getMessage());
            } catch (java.io.IOException ioe) {
                System.out.println("I/O error: " + ioe.getMessage());
            }
            JTree.DropLocation dl =
                    (JTree.DropLocation) support.getDropLocation();
            int childIndex = dl.getChildIndex();
            TreePath dest = dl.getPath();
            DefaultMutableTreeNode parent =
                    (DefaultMutableTreeNode) dest.getLastPathComponent();
            JTree tree = (JTree) support.getComponent();
            DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
            List<DefaultMutableTreeNode> parents = getParents(nodes,tree.getSelectionPaths());

            int index = childIndex;    // DropMode.INSERT
            if (childIndex == -1) {     // DropMode.ON
                if (parent.getLevel() > 1) {
                    return false;
                }
                if (parent.isLeaf()) {
                    FeedGroup fg = new FeedGroup("New Feed Group");
                    fg.addFeed((Feed) parent.getUserObject());
                    DefaultMutableTreeNode newParent = fg.getTreeNode();
                    data.removeFeed((Feed) parent.getUserObject());
                    model.insertNodeInto(newParent, (MutableTreeNode) parent.getParent(), parent.getParent().getChildCount());
                    data.addFeed(fg);
                    model.removeNodeFromParent(parent);
                    parent = newParent;
                }

                index = parent.getChildCount();
            }
            for (int i = 0; i < nodes.length; i++) {
                model.insertNodeInto(nodes[i], parent, index++);
                if (parent != parents.get(i)) {
                    if (!(parents.get(i).isRoot())) {
                        FeedGroup fg = (FeedGroup) parents.get(i).getUserObject();
                        fg.removeFeed((Feed) nodes[i].getUserObject());
                    } else {
                        data.removeFeed((Feed) nodes[i].getUserObject());
                    }
                    if(parent.isRoot()){
                        data.addFeed((Feed) nodes[i].getUserObject());
                    }else{
                        FeedGroup fg = (FeedGroup) parent.getUserObject();
                        fg.addFeed((Feed) nodes[i].getUserObject());
                    }
                    if(parents.get(i).getChildCount() == 1){
                        model.removeNodeFromParent(parents.get(i));
                        data.limitTo(null);
                        data.removeFeed((FeedGroup) parents.get(i).getUserObject());
                    }
                }
                if(parent.isRoot()){
                    data.setLocation((Feed)nodes[i].getUserObject(),getChildIndex(parent,nodes[i]));
                }else{
                    FeedGroup fg = (FeedGroup) parent.getUserObject();
                    fg.setLocation((Feed)nodes[i].getUserObject(),getChildIndex(parent,nodes[i]));
                }
            }

            return true;
        }

        public String toString() {
            return getClass().getName();
        }

        public class NodesTransferable implements Transferable {
            DefaultMutableTreeNode[] nodes;

            public NodesTransferable(DefaultMutableTreeNode[] nodes) {
                this.nodes = nodes;
            }

            public Object getTransferData(DataFlavor flavor)
                    throws UnsupportedFlavorException {
                if (!isDataFlavorSupported(flavor))
                    throw new UnsupportedFlavorException(flavor);
                return nodes;
            }

            public DataFlavor[] getTransferDataFlavors() {
                return flavors;
            }

            public boolean isDataFlavorSupported(DataFlavor flavor) {
                return nodesFlavor.equals(flavor);
            }
        }
    }
}
