package CH.ifa.draw.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Semaphore;
import javax.imageio.ImageIO;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;

import bibliothek.gui.DockUI;
import bibliothek.gui.dock.util.laf.LookAndFeelColors;

import de.renew.draw.ui.ontology.Toolbar;
import de.renew.plugin.PluginManager;
import de.renew.windowmanagement.DragCanvas;
import de.renew.windowmanagement.DraggableComponentAdapter;
import de.renew.windowmanagement.DraggableComponentDropTarget;


/**
 * I provide a container for tool buttons.
 * Buttons registered with me can be dragged and dropped within my confines.
 * @deprecated This class is not to be used externally and will later be hidden.
 */
@Deprecated
public class ToolButtonScrollPane extends DragCanvas implements Toolbar {

    private final static String IMAGES = "CH/ifa/draw/images/";
    private final static String LAYOUT_PATH = "CH/ifa/draw/gui/";
    /**
     * Default Layout that is chosen when the ToolButtonScrollPane is loaded or stored.
     */
    private Properties _persistentLayouts;
    private static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(ToolButtonScrollPane.class);

    /**
     * The ToolButtonPanel that gets a ScrollPane.
     */
    private final ToolButtonPanel _content;

    /**
     * All registered buttons are mapped to the group they belong to.
     */
    private final Map<String, Set<AbstractButton>> _registeredButtons = new HashMap<>();

    /**
     * Stores the name and grouping of the button (group is found by searching through a map of all registered buttons)
     */
    private final Map<String, String> _groupingLookup = new HashMap<>();

    /**
     * Used to look up buttons by their name.
     */
    private final Map<String, AbstractButton> _buttonLookup = new HashMap<>();

    /**
     * The tooltips for the original button groupings.
     */
    private final Map<String, String> _tooltips = new HashMap<>();
    /**
     * Menu with Boxes where Tools can be chosen.
     */
    private final JComboBox<String> _chooseToolsComboBox;
    /**
     * Menu with Boxes where the Layout of the ToolButtonScrollPane can be chosen.
     */
    private JComboBox<StorableButtonContainerLayout> _chooseLayoutBox;
    /**
     * Layouts that are known but can currently not be applied because there are buttons missing.
     */
    private final Map<String, StorableButtonContainerLayout> _unavailableLayouts = new HashMap<>();
    /**
     * Manages the layouts, only one layout should be chosen at the time.
     */
    private final Semaphore _semaphore = new Semaphore(1);


    /**
     * Constructor for a ToolButtonPanel.
     * @param panel Usually a new empty panel is used.
     */
    public ToolButtonScrollPane(ToolButtonPanel panel) {

        super(panel);
        this.setMinimumSize(new Dimension(70, 0));
        this.getViewport().setScrollMode(javax.swing.JViewport.BLIT_SCROLL_MODE);
        this.getVerticalScrollBar().setUnitIncrement(20);
        this.getHorizontalScrollBar().setUnitIncrement(20);
        _content = panel;
        _content.setLayout(new BoxLayout(_content, BoxLayout.Y_AXIS));
        _chooseToolsComboBox = new JComboBox<>();
        initComboBox();
        _content.add(_chooseToolsComboBox);
        _content.add(new CreateNewContainerTarget());
        _content.add(createLayoutPanel());

        loadInitialButtonLayouts();
    }

    /**
     * Writes the properties to disk.
     */
    public void storeLayoutsToDisk() {

        try {

            File file =
                new File(PluginManager.getPreferencesLocation(), "renewButtonLayout.properties");
            FileOutputStream outputStream = new FileOutputStream(file);
            _persistentLayouts.store(outputStream, "");
            outputStream.close();

        } catch (IOException e) {
            LOGGER.error("Writing properties failed" + e + "\n");
        }
    }

    private void loadInitialButtonLayouts() {

        try {

            // initialise empty properties
            _persistentLayouts = new Properties();
            File file =
                new File(PluginManager.getPreferencesLocation(), "renewButtonLayout.properties");

            // create new file if it doesn't exist
            if (!file.exists()) {
                //noinspection ResultOfMethodCallIgnored  as we don't care if it existed before or was newly created
                file.createNewFile();
            } else {
                // load existing properties from file
                try (FileInputStream stream = new FileInputStream(file)) {
                    _persistentLayouts.load(stream);
                }
            }

            List<StorableButtonContainerLayout> layouts =
                StorableButtonContainerLayout.createLayoutsFromProperties(_persistentLayouts);
            _semaphore.acquire();
            for (StorableButtonContainerLayout layout : layouts) {
                // Initially there are no buttons, so every layout should be unavailable
                _unavailableLayouts.put(layout._layoutName, layout);
            }
            _semaphore.release();
        } catch (IOException | InterruptedException e) {
            LOGGER.error("Loading button layouts failed" + e + "\n");
        }
    }

    private void initComboBox() {

        _chooseToolsComboBox.addActionListener(e -> {
            JComboBox<?> cb = (JComboBox<?>) e.getSource();
            String id = (String) cb.getSelectedItem();
            reAddTools(id);
        });

        // Used to display tooltips on the items
        DefaultListCellRenderer renderer = new DefaultListCellRenderer() {

            @Override
            public Component getListCellRendererComponent(
                JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
            {

                JComponent comp = (JComponent) super.getListCellRendererComponent(
                    list, value, index, isSelected, cellHasFocus);

                if (index > -1 && value != null && _tooltips != null) {
                    String key = _chooseToolsComboBox.getItemAt(index);
                    String tooltip = _tooltips.get(key);
                    list.setToolTipText(tooltip);
                }
                return comp;
            }
        };
        _chooseToolsComboBox.setRenderer(renderer);
    }

    /**
     * Creates the panel with all options regarding storable layouts of the tools panel.
     */
    private JPanel createLayoutPanel() {

        final JTextField textField = new JTextField();
        textField.setEditable(true);
        final ToolButtonScrollPane handle = this;

        JButton saveButton = new JButton();
        Icon icon = new ImageIcon(loadImage(IMAGES + "buttonlayout_save.png"));
        saveButton.setIcon(icon);
        //saveButton.setText("Save");
        saveButton.setToolTipText("Saves the current layout under the specified name.");
        saveButton.addActionListener(e -> {
            String name = textField.getText();
            switch (name) {
                case "":
                    textField.setText("Enter name");
                case "Enter name":
                    break;
                default:
                    handle.saveLayout(name);
                    break;

            }
        });

        JButton loadButton = new JButton();
        icon = new ImageIcon(loadImage(IMAGES + "buttonlayout_load.png"));
        loadButton.setIcon(icon);
        //loadButton.setText("Load");
        loadButton.setToolTipText("Loads the selected Layout.");
        loadButton.addActionListener(e -> {
            StorableButtonContainerLayout layout =
                (StorableButtonContainerLayout) _chooseLayoutBox.getSelectedItem();
            if (layout != null) {
                loadLayout(layout);
            }

        });

        JButton deleteButton = new JButton();
        icon = new ImageIcon(loadImage(IMAGES + "buttonlayout_delete.png"));
        deleteButton.setIcon(icon);
        deleteButton.addActionListener(e -> {
            StorableButtonContainerLayout layout =
                (StorableButtonContainerLayout) _chooseLayoutBox.getSelectedItem();
            if (layout != null) {
                _chooseLayoutBox.removeItem(layout);
                _persistentLayouts.remove(layout._layoutName);
            }
        });

        _chooseLayoutBox = new JComboBox<>() {

            @Override
            public void removeItem(Object item) {
                super.removeItem(item);

                if (getItemCount() == 0) {
                    setVisible(false);
                }
            }

            @Override
            public void addItem(StorableButtonContainerLayout item) {

                StorableButtonContainerLayout duplicate;
                int count = getItemCount();
                for (int i = 0; i < count; i++) {
                    duplicate = getItemAt(i);
                    if (duplicate != null) {
                        if (duplicate.equals(item)) {
                            removeItemAt(i);
                        }
                    }

                }

                super.addItem(item);
                this.setVisible(true);
                this.revalidate();
            }
        };
        // Initially we have no layouts, so the combobox is empty
        _chooseLayoutBox.setVisible(false);

        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel label = new JLabel("Panel Layout");
        label.setHorizontalAlignment(JLabel.CENTER);
        label.setAlignmentX(Component.CENTER_ALIGNMENT);
        panel.add(label);
        panel.add(textField);

        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new GridLayout(0, 2));
        buttonPanel.add(saveButton);
        buttonPanel.add(loadButton);
        buttonPanel.add(deleteButton);
        panel.add(buttonPanel);
        panel.add(_chooseLayoutBox);
        panel.setBorder(BorderFactory.createLoweredSoftBevelBorder());
        return panel;
    }


    public void addTools(Set<AbstractButton> buttons, String id) {

        addTools(buttons, id, id);
    }

    public void addTools(Set<AbstractButton> buttons, String id, String description) {

        try {
            _semaphore.acquire();
            DraggableComponentAdapter adapter;

            for (AbstractButton button : buttons) {
                adapter = new DraggableComponentAdapter(this, button);
                button.addMouseListener(adapter);
                button.addMouseMotionListener(adapter);

                // This retains information about where a button originally was
                // It is used to later create customizable layouts.
                _groupingLookup.put(button.getName(), id);
                _buttonLookup.put(button.getName(), button);
            }

            _registeredButtons.put(id, buttons);
            _tooltips.put(id, description);
            _chooseToolsComboBox.addItem(id);

            // Check if new layouts are available
            Collection<StorableButtonContainerLayout> layouts =
                new HashSet<>(_unavailableLayouts.values());
            for (StorableButtonContainerLayout layout : layouts) {
                if (layout.isAvailable(_registeredButtons.keySet())) {
                    _chooseLayoutBox.addItem(layout);
                    _unavailableLayouts.remove(layout._layoutName);
                }
            }

            createToolButtonContainer(buttons);
            _semaphore.release();
        } catch (InterruptedException e) {
            LOGGER.error("Add tools failed" + e + "\n");
        }

    }

    private void reAddTools(String id) {
        Set<AbstractButton> buttons = _registeredButtons.get(id);
        if (buttons != null) {
            createToolButtonContainer(buttons);
        }
    }


    public void removeTools(String id) {
        try {
            _semaphore.acquire();

            Set<AbstractButton> buttons = _registeredButtons.remove(id);
            _chooseToolsComboBox.removeItem(id);
            _tooltips.remove(id);

            if (buttons != null) {

                // Adding the buttons to a temporary panel removes them from all other components.
                JPanel tempPanel = new JPanel();
                for (AbstractButton button : buttons) {
                    tempPanel.add(button);
                    _groupingLookup.remove(button.getName());
                    _buttonLookup.remove(button.getName());
                }

                // Check if layouts are still available
                int count = _chooseLayoutBox.getItemCount();
                for (int i = 0; i < count; i++) {
                    StorableButtonContainerLayout layout = _chooseLayoutBox.getItemAt(i);
                    if (!layout.isAvailable(_registeredButtons.keySet())) {
                        _unavailableLayouts.put(layout._layoutName, layout);
                    }
                }
                for (StorableButtonContainerLayout value : _unavailableLayouts.values()) {
                    _chooseLayoutBox.removeItem(value);
                }
                revalidate();
            }
            _semaphore.release();
        } catch (InterruptedException e) {
            LOGGER.error("Remove tools failed" + e + "\n");
        }
    }

    private void createToolButtonContainer(Set<AbstractButton> buttons) {

        ToolButtonContainer container = new ToolButtonContainer(buttons);
        DraggableComponentAdapter adapter = new DraggableComponentAdapter(this, container);
        container.addMouseListener(adapter);
        container.addMouseMotionListener(adapter);

        // Add one before last position
        int count = _content.getComponentCount();
        _content.add(container, count - 1);
        revalidate();
    }

    @Override
    public void dropComponent(Component draggedComponent, Point locationOnScreen) {
        if (_content.isVisible()) {
            _content.dropComponent(draggedComponent, locationOnScreen);
        }
    }

    /**
     * Saves the current layout of the tool panel.
     * @param layoutName name of the layout that is saved
     */
    public void saveLayout(String layoutName) {

        final StorableButtonContainerLayout layout;

        try {
            _semaphore.acquire();
            layout = StorableButtonContainerLayout
                .createLayout(_content.getComponents(), _groupingLookup, layoutName);

            // layout should be available if it has just been created
            _chooseLayoutBox.addItem(layout);

            // remove old layout if it exists
            _unavailableLayouts.remove(layoutName);
            _semaphore.release();

            // We can run this concurrently, no reason to do this on the AWT thread
            new Thread(
                () -> StorableButtonContainerLayout.writeToProperties(_persistentLayouts, layout))
                .start();

        } catch (InterruptedException e) {
            LOGGER.error("Saving layout failed" + e + "\n");
        }

    }

    /**
     * Loads the requested layout.
     *
     * @param layout the requested layout that is loaded
     */
    private void loadLayout(StorableButtonContainerLayout layout) {

        try {
            _semaphore.acquire();
            List<Set<AbstractButton>> buttons =
                StorableButtonContainerLayout.readLayout(layout, _buttonLookup);
            _content.removeAllToolButtonContainers();
            for (Set<AbstractButton> set : buttons) {
                createToolButtonContainer(set);
            }
            _semaphore.release();
        } catch (InterruptedException e) {
            LOGGER.error("Loading layout failed" + e + "\n");
        }

    }

    private class CreateNewContainerTarget extends JPanel implements DraggableComponentDropTarget {

        CreateNewContainerTarget() {

            super();

            final JLabel handle = new JLabel("<html>Mouse over</html>");
            handle.setHorizontalAlignment(JLabel.CENTER);
            this.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseEntered(MouseEvent e) {
                    handle.setText("<html>Drop Button here to create new Container</html>");
                }

                @Override
                public void mouseExited(MouseEvent e) {
                    handle.setText("<html>Mouse over</html>");
                }
            });
            this.setLayout(new BorderLayout());
            this.add(handle, BorderLayout.CENTER);
            this.setBorder(BorderFactory.createLoweredSoftBevelBorder());
        }

        @Override
        public void dropComponent(Component draggedComponent, Point locationOnScreen) {
            if (draggedComponent instanceof AbstractButton) {
                Set<AbstractButton> set = new HashSet<>();
                set.add((AbstractButton) draggedComponent);
                createToolButtonContainer(set);
            }
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);

            Color background = DockUI.getColor(LookAndFeelColors.TITLE_SELECTION_BACKGROUND);
            Color brighter = DockUI.getColor(LookAndFeelColors.TITLE_BACKGROUND);

            Graphics2D g2 = (Graphics2D) g;
            GradientPaint backgroundPaint = new GradientPaint(
                0, 0, background, (float) getWidth() / 2, (float) getHeight() / 2, brighter, true);
            g2.setPaint(backgroundPaint);
            g2.fill(new Rectangle(0, 0, getWidth(), getHeight()));
        }
    }

    /**
     * Tries to load an image from a pathname.
     * Used for later creating icon images.
     */
    private BufferedImage loadImage(final String path) {
        URL url = PluginManager.getInstance().getBottomClassLoader().getResource(path);

        if (url == null) {
            File file = new File(path);
            try {
                url = file.toURI().toURL();
            } catch (MalformedURLException e) {
                LOGGER.error("Loading image failed" + e + "\n");
            }
        }

        if (url != null) {
            try {
                return ImageIO.read(url);
            } catch (IOException e) {
                LOGGER.error("Loading image failed" + e + "\n");
            }
        }

        return null;
    }
}
