package CH.ifa.draw.gui;

import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;

class ToolPanelLayoutTest {

    private ToolPanelLayout _layout;
    private Container _container;

    private static final int MIN_WIDTH = 50;
    private static final int MIN_WIDTH_SET_TEST = 90;
    private static final int MAX_WIDTH = 100;
    private static final int MAX_WIDTH_SET_TEST = 30;

    private static final int MINIMUM_LAYOUT_DIMENSION_WIDTH = MIN_WIDTH * 3;
    private static final int MINIMUM_LAYOUT_DIMENSION_HEIGHT = 50;

    @BeforeEach
    void setUp() throws Exception {
        SwingUtilities.invokeAndWait(() -> {
            _layout = new ToolPanelLayout(MIN_WIDTH, MAX_WIDTH);
            _container = new JPanel(_layout);
        });
    }

    @Test
    void testToolPanelLayout() throws Exception {
        // given
        JButton tooSmall = new JButton("Small");
        tooSmall.setPreferredSize(new Dimension(10, 20));

        JButton tooLarge = new JButton("Large");
        tooLarge.setPreferredSize(new Dimension(600, 20));

        // when
        SwingUtilities.invokeAndWait(() -> {
            _container.add(tooSmall);
            _container.add(tooLarge);
            _container.setSize(new Dimension(500, 200));
            _container.doLayout();
        });

        // then
        assertThat(tooSmall.getWidth()).isGreaterThanOrEqualTo(MIN_WIDTH);
        assertThat(tooLarge.getWidth()).isLessThanOrEqualTo(MAX_WIDTH);
    }

    @Test
    void testAddLayoutComponent() {
        // given
        JButton button = new JButton("Test");

        // when/then
        assertThatCode(() -> SwingUtilities.invokeAndWait(() -> _container.add(button)))
            .doesNotThrowAnyException();
    }

    @Test
    void testRemoveLayoutComponent() {
        // given
        JButton button = new JButton("Test");

        // when/then
        assertThatCode(() -> SwingUtilities.invokeAndWait(() -> {
            _container.add(button);
            _container.remove(button);
        })).doesNotThrowAnyException();
    }

    @Test
    void testPreferredLayoutSize() throws Exception {
        // given
        JButton button = new JButton("B");
        button.setPreferredSize(new Dimension(80, 40));

        SwingUtilities.invokeAndWait(() -> {
            _container.add(button);
            _container.setSize(new Dimension(200, 200));
        });

        // when
        AtomicReference<Dimension> ref = new AtomicReference<>();
        SwingUtilities.invokeAndWait(() -> ref.set(_container.getPreferredSize()));
        Dimension dim = ref.get();

        // then
        assertThat(dim.width).isEqualTo(200);
        assertThat(dim.height).isEqualTo(40);
    }

    @Test
    void testMinimumLayoutSize() throws Exception {
        AtomicReference<Dimension> ref = new AtomicReference<>();
        SwingUtilities.invokeAndWait(() -> ref.set(_container.getMinimumSize()));

        Dimension dim = ref.get();
        assertThat(dim.width).isEqualTo(MINIMUM_LAYOUT_DIMENSION_WIDTH);
        assertThat(dim.height).isEqualTo(MINIMUM_LAYOUT_DIMENSION_HEIGHT);
    }

    @Test
    void testLayoutContainer() throws Exception {
        // given
        JButton c1 = new JButton("1");
        JButton c2 = new JButton("2");
        JButton c3 = new JButton("3");
        JButton c4 = new JButton("4");

        // test different heights
        c1.setPreferredSize(new Dimension(80, 25));
        c2.setPreferredSize(new Dimension(80, 30));
        c3.setPreferredSize(new Dimension(80, 35));
        c4.setPreferredSize(new Dimension(80, 40));

        JPanel panelWithInsets = new JPanel(_layout) {
            @Override
            public Insets getInsets() {
                return new Insets(10, 5, 0, 5);
            }
        };

        SwingUtilities.invokeAndWait(() -> {
            panelWithInsets.setSize(new Dimension(120, 200)); // ensure that the elements occupy two rows
            panelWithInsets.add(c1);
            panelWithInsets.add(c2);
            panelWithInsets.add(c3);
            panelWithInsets.add(c4);
            panelWithInsets.doLayout();
        });

        // then
        assertThat(c1.getHeight()).isEqualTo(25); // all should be scaled down
        assertThat(c2.getHeight()).isEqualTo(25);
        assertThat(c3.getHeight()).isEqualTo(25);
        assertThat(c4.getHeight()).isEqualTo(25);

        assertThat(c1.getY()).isEqualTo(10);
        assertThat(c2.getY()).isEqualTo(10);

        // these elements should be in the new line
        assertThat(c3.getY()).isGreaterThan(c1.getY());
        assertThat(c4.getY()).isEqualTo(c3.getY());

        // the last button may be larger according to code base
        assertThat(c2.getWidth()).isGreaterThanOrEqualTo(c1.getWidth());
    }

    @Test
    void testSetMaximumComponentWidth() throws Exception {
        // given
        JButton button = new JButton("B");
        button.setPreferredSize(new Dimension(100, 20));
        SwingUtilities.invokeAndWait(() -> {
            _container.add(button);
            _container.setSize(new Dimension(200, 200));
        });

        // when
        SwingUtilities.invokeAndWait(() -> {
            _layout.setMaximumComponentWidth(MAX_WIDTH_SET_TEST);
            _container.doLayout();
        });

        // then
        assertThat(button.getWidth()).isLessThanOrEqualTo(MAX_WIDTH_SET_TEST);
    }

    @Test
    void testSetMinimumComponentWidth() throws Exception {
        // given
        JButton b1 = new JButton("1");
        b1.setPreferredSize(new Dimension(100, 20));
        SwingUtilities.invokeAndWait(() -> {
            _container.add(b1);
            _container.setSize(new Dimension(200, 200));
        });

        // when
        SwingUtilities.invokeAndWait(() -> {
            _layout.setMinimumComponentWidth(MIN_WIDTH_SET_TEST);
            _container.doLayout();
        });

        // then
        assertThat(b1.getWidth()).isGreaterThanOrEqualTo(MIN_WIDTH_SET_TEST);
    }

}
