package de.renew.gui;

import java.awt.Adjustable;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollBar;

import CH.ifa.draw.DrawPlugin;
import CH.ifa.draw.util.Animatable;
import CH.ifa.draw.util.Animator;
import CH.ifa.draw.util.GUIProperties;
import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.ui.api.MenuApi;
import de.renew.draw.ui.api.UndoRedoApi;
import de.renew.draw.ui.ontology.DrawingEditor;
import de.renew.draw.ui.ontology.DrawingView;


public class AutomaticLayoutFrame implements AdjustmentListener, Animatable {
    private static final int width = 200;
    private static final String[] buttonTexts = {
        "Spring Length", "Spring Strength", "Repulsion Distance", "Repulsion Strength",
        "Torque Strength", "Friction" };
    private final DrawingEditor editor;
    private Animator animator;
    private GraphLayout graphLayout = null;
    private final JFrame frame;
    private final JScrollBar[] scroller;
    private final JButton startButton;
    private final JButton stopButton;

    public AutomaticLayoutFrame(DrawingEditor editor) {
        this.editor = editor;

        frame = new JFrame("Automatic Net Layout");
        if (!GUIProperties.avoidFrameReshape()) {
            frame.setSize(600, 200);
        }
        GridBagLayout gridBag = new GridBagLayout();
        frame.getContentPane().setLayout(gridBag);

        GridBagConstraints c = new GridBagConstraints();
        c.fill = GridBagConstraints.HORIZONTAL;
        c.weighty = 1;

        scroller = new JScrollBar[buttonTexts.length];
        for (int i = 0; i < buttonTexts.length; i++) {
            c.gridwidth = 1;
            c.weightx = 0;
            JLabel label = new JLabel(buttonTexts[i]);
            label.setPreferredSize(new Dimension(150, 20));
            gridBag.setConstraints(label, c);
            frame.getContentPane().add(label);

            c.gridwidth = GridBagConstraints.REMAINDER;
            c.weightx = 1;
            scroller[i] = new JScrollBar(Adjustable.HORIZONTAL, width / 2, 10, 0, width);
            scroller[i].addAdjustmentListener(this);
            scroller[i].setPreferredSize(new Dimension(150, 20));
            gridBag.setConstraints(scroller[i], c);
            frame.getContentPane().add(scroller[i]);
        }

        JPanel panel = new JPanel();
        gridBag.setConstraints(panel, c);
        frame.getContentPane().add(panel);

        gridBag = new GridBagLayout();
        panel.setLayout(gridBag);
        panel.setPreferredSize(new Dimension(300, 40));
        c = new GridBagConstraints();
        c.weightx = 1;
        c.fill = GridBagConstraints.BOTH;
        c.gridwidth = 1;

        startButton = new JButton("Start");
        startButton.addActionListener(e -> startAnimation());
        gridBag.setConstraints(startButton, c);
        panel.add(startButton);

        stopButton = new JButton("Stop");
        stopButton.addActionListener(e -> stopAnimation());
        gridBag.setConstraints(stopButton, c);
        panel.add(stopButton);

        JButton autoButton = new JButton("Random");
        autoButton.addActionListener(e -> annealingLayout());
        gridBag.setConstraints(autoButton, c);
        panel.add(autoButton);

        JButton button = new JButton("Close");
        button.addActionListener(e -> {
            stopAnimation();
            frame.setVisible(false);
            DrawPlugin.getCurrent().getMenuManager().getWindowsMenu().removeFrame(frame);
        });
        c.gridwidth = GridBagConstraints.REMAINDER;
        gridBag.setConstraints(button, c);
        panel.add(button);

        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent w) {
                stopAnimation();
                frame.setVisible(false);
                DrawPlugin.getCurrent().getMenuManager().getWindowsMenu().removeFrame(frame);
            }
        });

        frame.pack();
    }

    public Dimension getPreferredSize() {
        return new Dimension(600, 200);
    }

    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

    public void showFrame() {
        startButton.setEnabled(true);
        stopButton.setEnabled(false);
        frame.setTitle("Automatic Layout");
        frame.setVisible(true);
        DrawPlugin.getCurrent().getMenuManager().getWindowsMenu()
            .addFrame(MenuApi.getWindowsCategoryNameTools(), frame);
    }

    public void stopAnimation() {
        startButton.setEnabled(true);
        stopButton.setEnabled(false);
        if (animator != null) {
            animator.end();
            animator = null;
        }
    }

    public void startAnimation() {
        stopAnimation();
        startButton.setEnabled(false);
        stopButton.setEnabled(true);

        DrawingView view = editor.view();
        Drawing drawing = view.drawing();
        if (drawing instanceof LayoutableDrawing layoutableDrawing) {
            // Setup the graphics.
            frame.setTitle("Automatic Layout - " + drawing.getName());

            //Take Undo-Snapshot.
            UndoRedoApi.prepareUndoSnapshot();
            UndoRedoApi.commitUndoSnapshot();

            // Setup the layout algorithm.
            graphLayout = new GraphLayout();
            layoutableDrawing.fillInGraph(graphLayout);
            fillInScrollerValues();

            // Make sure the layout is relaxed periodically.
            animator = new Animator(this, view);
            new Thread(animator).start();
        }
    }

    public void annealingLayout() {
        Drawing drawing = editor.view().drawing();
        if (drawing instanceof LayoutableDrawing layoutableDrawing) {
            new AnnealingGraphLayout().annealingLayout(layoutableDrawing);
        }
    }


    /**
     * This method allows programmers to layout their drawings with given
     * parameters for a certain time, without explicitly opening the
     * Automatic Layout frame. If the given time is below below or equal to 0
     * the frame is opened anyways.
     *
     * @param springLength                        Value range from 0.0 to 0.9025 where 0.25 is standard value
     * @param springStrength                Value range from 0.0 to 0.9025 where 0.25 is standard value
     * @param repulsionDistance                Value range from 0.0 to 0.9025 where 0.25 is standard value
     * @param repulsionStrength                Value range from 0.0 to 0.9025 where 0.25 is standard value
     * @param torqueStrength                Value range from 0.0 to 0.9025 where 0.25 is standard value
     * @param friction                                Value range from 0.0 to 0.9025 where 0.25 is standard value
     * @param timeInMillis  For numbers less or equal to 0 the Automatic Layout Menu is opened.
     *                                                 For numbers from 0-999999 the drawing will be layouted for exactly that
     *                                                 time in milliseconds. Greater numbers will cause exceptions.
     */
    public void createLayout(
        double springLength, double springStrength, double repulsionDistance,
        double repulsionStrength, double torqueStrength, double friction, long timeInMillis)
    {
        stopAnimation();
        startButton.setEnabled(false);
        stopButton.setEnabled(true);

        DrawingView view = editor.view();
        Drawing drawing = view.drawing();
        if (drawing instanceof LayoutableDrawing layoutableDrawing) {
            //Take Undo-Snapshot.
            UndoRedoApi.prepareUndoSnapshot();
            UndoRedoApi.commitUndoSnapshot();

            // Setup the layout algorithm.
            graphLayout = new GraphLayout();
            layoutableDrawing.fillInGraph(graphLayout);
            fillInScrollerValues();

            // Make sure the layout is relaxed periodically.
            animator = new Animator(this, view);
            new Thread(animator).start();

            graphLayout.lengthFactor = GraphLayout.DEFAULT_LENGTH_FACTOR * 4 * springLength;
            graphLayout.springStrength = GraphLayout.DEFAULT_SPRING_STRENGTH * 4 * springStrength;
            graphLayout.repulsionLimit =
                GraphLayout.DEFAULT_REPULSION_LIMIT * 4 * repulsionDistance;
            graphLayout.repulsionStrength =
                GraphLayout.DEFAULT_REPULSION_STRENGTH * 4 * repulsionStrength;
            graphLayout.torqueStrength = GraphLayout.DEFAULT_TORQUE_STRENGTH * 4 * torqueStrength;
            graphLayout.frictionFactor = 1 - friction;

            if (timeInMillis <= 0) {
                // Setup the graphics.
                frame.setTitle("Automatic Layout - " + drawing.getName());
                frame.setVisible(true);
                DrawPlugin.getCurrent().getMenuManager().getWindowsMenu()
                    .addFrame(MenuApi.getWindowsCategoryNameTools(), frame);
            } else {
                try {
                    Thread.sleep(timeInMillis);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            stopAnimation();
        }
    }

    private static double calcFactor(int value) {
        double n = value / (double) width;
        return n * n;
    }

    @Override
    public void adjustmentValueChanged(AdjustmentEvent e) {
        fillInScrollerValues();
    }

    private void fillInScrollerValues() {
        int num = 0;
        if (graphLayout != null) {
            graphLayout.lengthFactor =
                GraphLayout.DEFAULT_LENGTH_FACTOR * 4 * calcFactor(scroller[num++].getValue());
            graphLayout.springStrength =
                GraphLayout.DEFAULT_SPRING_STRENGTH * 4 * calcFactor(scroller[num++].getValue());
            graphLayout.repulsionLimit =
                GraphLayout.DEFAULT_REPULSION_LIMIT * 4 * calcFactor(scroller[num++].getValue());
            graphLayout.repulsionStrength =
                GraphLayout.DEFAULT_REPULSION_STRENGTH * 4 * calcFactor(scroller[num++].getValue());
            graphLayout.torqueStrength =
                GraphLayout.DEFAULT_TORQUE_STRENGTH * 4 * calcFactor(scroller[num++].getValue());
            graphLayout.frictionFactor = 1 - calcFactor(scroller[num].getValue());
        }
    }

    @Override
    public synchronized void animationStart() {
        // Nothing to do.
    }

    @Override
    public synchronized void animationStop() {
        graphLayout.remove();
        graphLayout = null;
    }

    @Override
    public synchronized void animationStep() {
        if (graphLayout != null) {
            graphLayout.relax();
        }
    }
}