package CH.ifa.draw.standard;

import java.awt.Color;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.event.ChangeListener;

import bibliothek.gui.dock.common.action.CAction;
import bibliothek.gui.dock.common.action.CButton;
import bibliothek.gui.dock.common.action.CDropDownButton;
import bibliothek.gui.dock.common.action.CMenu;
import bibliothek.gui.dock.common.action.CPanelPopup;
import bibliothek.gui.dock.common.action.CSeparator;
import bibliothek.gui.dock.common.action.panel.PanelPopupWindow;
import bibliothek.gui.dock.common.intern.action.CDecorateableAction;

import CH.ifa.draw.DrawPlugin;
import CH.ifa.draw.figures.AttributeFigure;
import CH.ifa.draw.figures.PolyLineFigure;
import CH.ifa.draw.figures.TextFigure;
import CH.ifa.draw.framework.AlphaChangeCommand;
import CH.ifa.draw.framework.DrawingViewDecoration;
import CH.ifa.draw.util.ColorMap;
import CH.ifa.draw.util.ExtendedFont;
import CH.ifa.draw.util.GUIProperties;
import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.ui.ontology.AbstractCommand;


/**
 * Creates menus for changing the attributes of figures.
 * Can be used to decorate DrawingViews.
 * @deprecated This class is not to be used externally and will later be hidden.
 */
@Deprecated(since = "5.0", forRemoval = true)
@SuppressWarnings("removal")
public class AttributeMenuDecoration implements SelectionChangeListener, DrawingViewDecoration {

    private static final String IMAGES = "/CH/ifa/draw/images/";
    private static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(AttributeMenuDecoration.class);
    private static final List<String> BASIC_FONTS = new ArrayList<>() {
        {
            add("Courier New");
            add("Andale Mono");
            add("Monaco");
            add("Profont");
            add("Monofur");
            add("Droid Sans Mono");
            add("DejaVu Sans Mono");
            add("Consolas");
            add("Inconsolata");
            add("Arial");
            add("Calibri");
            add("Lucida Sans");
        }
    };


    final List<SelectionChangeListener> _selectionChangeListeners = new LinkedList<>();

    private LinkedList<CAction> _menus;
    private final int _maxFontMenuEntries = 15;

    public LinkedList<CAction> getDecorations() {
        if (_menus == null) {
            _menus = createMenus();
        }

        return _menus;
    }

    /**
     * Used to lazily create the decorations provided by this class.
     * The menus are saved for later reuse, so we don't create a lot of unnecessary objects.
     */
    private LinkedList<CAction> createMenus() {
        LinkedList<CAction> result = new LinkedList<>();
        result.add(createColorMenu("Fill Color", "FillColor", "fill_color"));
        result.add(
            createTransparencyMenu(
                "Fill Color Transparency", "FillColor", "transparency_fill.png"));
        result.add(createColorMenu("Pen Color", "FrameColor", "pen_color"));
        result.add(
            createTransparencyMenu("Pen Color Transparency", "FrameColor", "transparency_pen.png"));
        result.add(createArrowMenu());
        result.add(createLineStyleMenu());
        result.add(createLineShapeMenu());
        result.add(createLineWidthMenu());

        result.add(CSeparator.SEPARATOR);
        result.add(createColorMenu("Text Color", "TextColor", "text_color"));
        result.add(
            createTransparencyMenu(
                "Text Color Transparency", "TextColor", "transparency_text.png"));
        result.add(createFontSizeMenu());
        result.add(createFontStyleMenu());
        result.add(createTextAlignmentMenu());

        return result;
    }

    private CAction createTextAlignmentMenu() {
        CMenu menu = new CMenu();
        CommandExecutor button;
        ImageIcon icon;
        AbstractCommand command;

        menu.setTooltip("Text Alignment");

        command = new ChangeAttributeCommand("Left", TextFigure.ALIGN_ATTR, TextFigure.LEFT);
        button = new CommandExecutor(command);
        button.setTooltip("Align Left");
        icon = new ImageIcon(loadImage(IMAGES + "textalign_left.png"));
        button.setIcon(icon);
        menu.add(button);

        command = new ChangeAttributeCommand("Center", TextFigure.ALIGN_ATTR, TextFigure.CENTER);
        button = new CommandExecutor(command);
        button.setTooltip("Align Center");
        icon = new ImageIcon(loadImage(IMAGES + "textalign_center.png"));
        button.setIcon(icon);
        menu.setIcon(icon);
        menu.add(button);

        command = new ChangeAttributeCommand("Right", TextFigure.ALIGN_ATTR, TextFigure.RIGHT);
        button = new CommandExecutor(command);
        button.setTooltip("Align Right");
        icon = new ImageIcon(loadImage(IMAGES + "textalign_right.png"));
        button.setIcon(icon);
        menu.add(button);

        setEnabledIfCommandExecutable(menu, command);

        return menu;
    }


    private CAction createFontStyleMenu() {
        CMenu menu = new CMenu();
        ImageIcon icon = new ImageIcon(loadImage(IMAGES + "TEXT.gif"));
        menu.setIcon(icon);
        menu.setTooltip("Font Style");
        menu.add(createFontStyleButton("Plain", Font.PLAIN, "TEXT.gif"));
        menu.add(createFontStyleButton("Italic", Font.ITALIC, "italic.png"));
        menu.add(createFontStyleButton("Bold", Font.BOLD, "bold.png"));
        menu.add(createFontStyleButton("Underlined", ExtendedFont.UNDERLINED, "underlined.png"));
        menu.addSeparator();
        menu.add(createFontMenu());

        setEnabledIfCommandExecutable(
            menu, new ChangeAttributeCommand("", "FontStyle", Font.PLAIN));

        return menu;
    }

    private CAction createFontStyleButton(String title, Integer type, String iconpath) {

        ChangeAttributeCommand command = new ChangeAttributeCommand(title, "FontStyle", type);
        CommandExecutor button = new CommandExecutor(command);

        button.setTooltip("Font Style: " + title);
        ImageIcon icon = new ImageIcon(loadImage(IMAGES + iconpath));
        button.setIcon(icon);
        button.setShowTextOnButtons(false);

        return button;
    }

    private CAction createFontSizeMenu() {
        final CPanelPopup popup = new CPanelPopup();
        popup.setCloseOnFocusLost(true);
        popup.setTooltip("Font Size");

        Integer[] sizes = { 9, 10, 11, 12, 14, 18, 24, 36, 48, 72 };
        JComboBox<Integer> comboBox = new JComboBox<>(sizes);
        comboBox.setEditable(true);
        comboBox.addActionListener(e -> {
            JComboBox<?> box = (JComboBox<?>) e.getSource();
            new ChangeAttributeCommand("", "FontSize", box.getSelectedItem()).execute();
        });

        popup.setContent(comboBox);
        ImageIcon icon = new ImageIcon(loadImage(IMAGES + "fontsize.png"));
        popup.setIcon(icon);

        setEnabledIfCommandExecutable(popup, new ChangeAttributeCommand("", "FontSize", 10));
        return popup;
    }

    private CAction createFontMenu() {
        final CMenu menu = new CMenu();
        String[] fonts =
            GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
        CommandExecutor button;
        menu.setTooltip("Font Menu");
        menu.setText("Select Font");
        menu.setShowTextOnButtons(true);

        String defaultFontName = GUIProperties.defaultFontName();
        button = new CommandExecutor(
            new ChangeAttributeCommand(
                defaultFontName + " (default)", "FontName", defaultFontName));
        menu.add(button);

        int k = 0;
        for (int i = 0; (i < fonts.length) && (k < _maxFontMenuEntries); i++) {

            if (BASIC_FONTS.contains(fonts[i]) && fonts[i] != defaultFontName) {
                button =
                    new CommandExecutor(new ChangeAttributeCommand(fonts[i], "FontName", fonts[i]));
                menu.add(button);
                k++;
            }

        }
        button = new CommandExecutor(
            new ChooseFontCommand("Font Name", "other...", "FontName", String.class));
        menu.add(button);

        setEnabledIfCommandExecutable(
            menu, new ChangeAttributeCommand(fonts[0], "FontName", fonts[0]));

        return menu;
    }

    private CAction createLineWidthMenu() {
        final CPanelPopup popup = new CPanelPopup();

        JSlider slider = new JSlider(JSlider.HORIZONTAL, 1, 10, 10);
        slider.setMajorTickSpacing(1);
        slider.setPaintLabels(true);
        slider.addChangeListener(e -> {
            JSlider s = (JSlider) e.getSource();
            if (!s.getValueIsAdjusting()) {
                int value = s.getValue();
                new SetLineWidthCommand("", value).execute();
            }

        });

        popup.setCloseOnFocusLost(true);
        popup.setContent(slider);
        popup.setTooltip("Line Width");

        ImageIcon icon = new ImageIcon(loadImage(IMAGES + "linewidth.png"));
        popup.setIcon(icon);
        setEnabledIfCommandExecutable(popup, new SetLineWidthCommand("", 0));

        return popup;
    }

    private CAction createLineShapeMenu() {
        CMenu menu = new CMenu();
        CommandExecutor button;
        AbstractCommand command;
        menu.setTooltip("Line Shape");
        ImageIcon icon = new ImageIcon(loadImage(IMAGES + "lineshape_both.png"));
        menu.setIcon(icon);

        command = new ChangeAttributeCommand("straight", "LineShape", PolyLineFigure.LINE_SHAPE);
        button = new CommandExecutor(command);
        button.setTooltip("Straight");
        button.setText("Straight");
        icon = new ImageIcon(loadImage(IMAGES + "lineshape_straight.png"));
        button.setIcon(icon);
        menu.add(button);

        command = new SplineAttributeCommand("standard", "standard", 0);
        button = new CommandExecutor(command);
        button.setTooltip("Curved");
        button.setText("Curved");
        icon = new ImageIcon(loadImage(IMAGES + "lineshape_curved.png"));
        button.setIcon(icon);
        menu.add(button);

        setEnabledIfCommandExecutable(menu, command);

        return menu;
    }

    private CAction createLineStyleMenu() {
        CMenu menu = new CMenu();
        menu.setTooltip("Line Style");
        menu.setIcon(createIcon("line_styles.gif"));
        CommandExecutor button;
        AbstractCommand command;

        command =
            new ChangeAttributeCommand("normal", "LineStyle", AttributeFigure.LINE_STYLE_NORMAL);
        button = new CommandExecutor(command);
        button.setIcon(createIcon("linestyle_normal.gif"));
        menu.add(button);

        command =
            new ChangeAttributeCommand("dotted", "LineStyle", AttributeFigure.LINE_STYLE_DOTTED);
        button = new CommandExecutor(command);
        button.setIcon(createIcon("linestyle_dotted.gif"));
        menu.add(button);

        command =
            new ChangeAttributeCommand("dashed", "LineStyle", AttributeFigure.LINE_STYLE_DASHED);
        button = new CommandExecutor(command);
        button.setIcon(createIcon("linestyle_dashed.gif"));
        menu.add(button);

        command = new ChangeAttributeCommand(
            "medium dashed", "LineStyle", AttributeFigure.LINE_STYLE_MEDIUM_DASHED);
        button = new CommandExecutor(command);
        button.setIcon(createIcon("linestyle_mediumdashed.gif"));
        menu.add(button);

        command = new ChangeAttributeCommand(
            "long dashed", "LineStyle", AttributeFigure.LINE_STYLE_LONG_DASHED);
        button = new CommandExecutor(command);
        button.setIcon(createIcon("linestyle_longdashed.gif"));
        menu.add(button);

        command = new ChangeAttributeCommand(
            "dash-dotted", "LineStyle", AttributeFigure.LINE_STYLE_DASH_DOTTED);
        button = new CommandExecutor(command);
        button.setIcon(createIcon("linestyle_dashdotted.gif"));
        menu.add(button);

        setEnabledIfCommandExecutable(menu, command);

        command = new QueryAttributeCommand(
            "Line style (sequence of dash/gap lengths)", "other...", "LineStyle", String.class);
        button = new CommandExecutor(command);
        button.setIcon(createIcon("line_styles.gif"));
        menu.add(button);

        return menu;
    }

    private CAction createArrowMenu() {
        CDropDownButton menu = new CDropDownButton();
        menu.setIcon(createIcon("arrow_end.png"));
        menu.setTooltip("Arrows");
        CommandExecutor button;
        AbstractCommand command;

        command = new ChangeAttributeCommand("none", "ArrowMode", PolyLineFigure.ARROW_TIP_NONE);
        button = new CommandExecutor(command);
        button.setIcon(createIcon("arrow_none.png"));
        menu.add(button);

        command =
            new ChangeAttributeCommand("at Start", "ArrowMode", PolyLineFigure.ARROW_TIP_START);
        button = new CommandExecutor(command);
        button.setIcon(createIcon("arrow_start.png"));
        menu.add(button);

        command = new ChangeAttributeCommand("at End", "ArrowMode", PolyLineFigure.ARROW_TIP_END);
        button = new CommandExecutor(command);
        button.setIcon(createIcon("arrow_end.png"));
        menu.add(button);

        command =
            new ChangeAttributeCommand("at Start/End", "ArrowMode", PolyLineFigure.ARROW_TIP_BOTH);
        button = new CommandExecutor(command);
        button.setIcon(createIcon("arrow_both.png"));
        menu.add(button);

        setEnabledIfCommandExecutable(menu, command);

        return menu;
    }

    private CAction createTransparencyMenu(String title, final String attribute, String iconpath) {

        final TransparencySlider slider = new TransparencySlider();

        slider.addChangeListener(e -> {
            if (!slider.getValueIsAdjusting()) {
                int value = slider.getValue();
                new AlphaChangeCommand("", attribute, (int) Math.round(value * 2.55)).execute();

                slider.adjustLabels(value);
            }
        });

        final CPanelPopup popup = new TransparencySliderCPanelPopup(attribute, slider);
        popup.setCloseOnFocusLost(true);
        popup.setTooltip(title);
        popup.setMenuBehavior(CPanelPopup.MenuBehavior.SUBMENU);

        ImageIcon icon = new ImageIcon(loadImage(IMAGES + iconpath));
        popup.setIcon(icon);

        setEnabledIfCommandExecutable(popup, new AlphaChangeCommand("", attribute, 0));

        return popup;
    }

    private CAction createColorMenu(String title, String attribute, String iconname) {
        CDropDownButton menu = new CDropDownButton();
        ImageIcon icon = new ImageIcon(loadImage(IMAGES + iconname + "_base.png"));
        menu.setIcon(icon);
        menu.setTooltip(title);
        CommandExecutor executor;
        Color color;


        for (int i = 0; i < ColorMap.size(); i++) {

            color = ColorMap.color(i);
            executor =
                new CommandExecutor(new ChangeAttributeCommand(ColorMap.name(i), attribute, color));

            BufferedImage img = loadImage(IMAGES + iconname + "_color.png");
            if (img != null) {
                changeColor(img, color);
                icon = new ImageIcon(img);
                executor.setIcon(icon);
            }
            menu.add(executor);

        }

        executor =
            new CommandExecutor(new ChooseColorCommand(title, "other...", attribute, Color.class));
        menu.add(executor);

        setEnabledIfCommandExecutable(menu, new ChangeAttributeCommand("", attribute, Color.BLUE));

        return menu;
    }

    @Override
    public void selectionStateChanged() {
        for (SelectionChangeListener listener : _selectionChangeListeners) {
            listener.selectionStateChanged();
        }
    }

    /**
     * A {@link CButton} that executes a {@link AbstractCommand} when pressed.
     */
    private static class CommandExecutor extends CButton {

        final AbstractCommand _command;

        public CommandExecutor(AbstractCommand command) {
            _command = command;
            this.setText(command.getCommandName());
            this.setShowTextOnButtons(true);
            this.addActionListener(e -> {
                if (_command.isExecutable()) {
                    _command.execute();
                }
            });
        }
    }

    /**
     * A {@link CPanelPopup} that adjusts it's contained slider when opened.
     */
    private static class TransparencySliderCPanelPopup extends CPanelPopup {

        final TransparencySlider _slider;
        final String _attribute;

        public TransparencySliderCPanelPopup(String attribute, TransparencySlider slider) {
            super();
            this._attribute = attribute;
            this._slider = slider;
            setContent(slider);
        }

        @Override
        public void openPopup(PanelPopupWindow window) {
            int value = 100;
            if (DrawPlugin.getCurrentDrawPlugin().isGuiPresent()) {
                Vector<Figure> v = DrawPlugin.getDrawPluginGui().view().selection();
                if (!v.isEmpty()) {
                    Figure f = v.get(0);
                    Color color = (Color) f.getAttribute(_attribute);
                    double alpha = color.getAlpha() / 2.55;
                    value = (int) alpha;
                }
            }

            // This is kind of hacky, but probably the simplest solution.
            ChangeListener[] listeners = _slider.getChangeListeners();
            _slider.removeChangeListener(listeners[0]);
            _slider.setValue(value);
            _slider.adjustLabels(value);
            _slider.addChangeListener(listeners[0]);

            super.openPopup(window);
        }
    }

    /**
     * A {@link JSlider} used to adjust transparency.
     * Provides a method to adjust the labels.
     */
    private static class TransparencySlider extends JSlider {

        public TransparencySlider() {
            super(JSlider.HORIZONTAL, 0, 100, 100);
            this.setMajorTickSpacing(20);
            this.setMinorTickSpacing(10);
            this.setPaintTicks(true);
            this.setPaintLabels(true);
        }

        public void adjustLabels(int value) {
            // Create new labels so we can show the current value
            Hashtable<Integer, JComponent> labels = this.createStandardLabels(20);
            labels.put(value, new JLabel("" + value));

            // Remove the closest regular label so they don't overlap
            int toRemove = (int) Math.round((double) value / 20) * 20;
            labels.remove(toRemove);
            this.setLabelTable(labels);
        }
    }

    /**
     * Helper method to create icons.
     *
     * @param name
     * @return
     */
    private ImageIcon createIcon(String name) {
        BufferedImage img = loadImage(IMAGES + name);
        return new ImageIcon(img);
    }

    /**
     * Tries to load an image from a pathname.
     * Used for later creating icon images.
     */
    private BufferedImage loadImage(final String path) {

        BufferedImage result = null;

        URL url = AttributeMenuDecoration.class.getResource(path);
        if (url == null) {
            File file = new File(path);
            try {
                url = file.toURI().toURL();
            } catch (MalformedURLException e) {
                LOGGER.error(e.getMessage() + e);
            }
        }
        if (url != null) {
            try {
                result = ImageIO.read(url);
                return result;
            } catch (IOException e) {
                LOGGER.error(e.getMessage() + e);
            }
        }

        return result;
    }

    private void setEnabledIfCommandExecutable(
        final CDecorateableAction<?> toggable, AbstractCommand command)
    {

        final AbstractCommand commandCopy = command;

        // The command is never executed.
        // It is solely used to see if the Action is activated.
        _selectionChangeListeners.add(() -> toggable.setEnabled(commandCopy.isExecutable()));
    }


    /**
     * Changes the color of an icon.
     * This method iterates over every pixel, so only use on small icon images.
     */
    private void changeColor(BufferedImage img, Color color) {
        Color standard = new Color(255, 0, 255);
        changeColor(img, standard, color);
    }

    /**
     * Changes the color of an icon.
     * This method iterates over every pixel, so only use on small icon images.
     */
    private void changeColor(BufferedImage img, Color from, Color to) {
        final int oldRGB = from.getRGB();
        final int newRGB = to.getRGB();
        for (int x = 0; x < img.getWidth(); x++) {
            for (int y = 0; y < img.getHeight(); y++) {
                if (img.getRGB(x, y) == oldRGB)
                    img.setRGB(x, y, newRGB);
            }
        }
    }
}
