package de.renew.propertymanagementgui.gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.border.Border;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import org.mockito.Mockito;

import static org.mockito.Mockito.mock;

/**
 * Unit test class for FooterComponent
 *
 * @author 7kraft
 */
class FooterComponentTest {

    // helpers

    private static Field field(
        @SuppressWarnings("SameParameterValue") Class<?> theClass,
        @SuppressWarnings("SameParameterValue") String name) throws Exception
    {
        Field field = theClass.getDeclaredField(name);
        field.setAccessible(true);
        return field;
    }

    private static Method method(Class<?> theClass, String name, Class<?>... params)
        throws Exception
    {
        Method method = theClass.getDeclaredMethod(name, params);
        method.setAccessible(true);
        return method;
    }

    // test methods

    @Test
    public void testRender() throws Exception {
        //given
        Runnable global = Mockito.mock(Runnable.class);
        Runnable local = Mockito.mock(Runnable.class);
        FooterComponent footerComp = new FooterComponent(global, local);

        //when
        JComponent footer = footerComp.render();

        //then
        Assertions.assertThat(footer).isNotNull();
        Assertions.assertThat(footer.getLayout()).isInstanceOf(BorderLayout.class);

        Assertions.assertThat(footer.getMaximumSize().width).isEqualTo(Integer.MAX_VALUE);

        Assertions.assertThat(footer.getComponentCount()).isEqualTo(1);
        JComponent wrapper = (JComponent) footer.getComponent(0);
        Assertions.assertThat(wrapper.getLayout()).isInstanceOf(BoxLayout.class);

        Border expectedBorder =
            (Border) field(FooterComponent.class, "ACTION_BUTTONS_WRAPPER_BORDER").get(null);
        Assertions.assertThat(wrapper.getBorder()).isSameAs(expectedBorder);
    }

    @Test
    public void testSetLayoutForFooter() throws Exception {
        //given
        Runnable global = Mockito.mock(Runnable.class);
        Runnable local = Mockito.mock(Runnable.class);
        FooterComponent footerComp = new FooterComponent(global, local);

        JPanel panel = new JPanel();
        panel.setPreferredSize(new Dimension(123, 42));

        //when
        method(FooterComponent.class, "setLayoutForFooter", JComponent.class)
            .invoke(footerComp, panel);

        //then
        Assertions.assertThat(panel.getLayout()).isInstanceOf(BorderLayout.class);
        Dimension max = panel.getMaximumSize();
        Assertions.assertThat(max.width).isEqualTo(Integer.MAX_VALUE);
        Assertions.assertThat(max.height).isEqualTo(42);
    }

    @Test
    public void testGetActionButtonsWrapperIn() throws Exception {
        //given
        Runnable global = Mockito.mock(Runnable.class);
        Runnable local = Mockito.mock(Runnable.class);
        FooterComponent footerComp = new FooterComponent(global, local);

        JPanel saveLocalPanel = new JPanel();
        JPanel saveGlobalPanel = new JPanel();
        JPanel closePanel = new JPanel();
        List<Object[]> saveCtorArgs = new ArrayList<>();

        try (@SuppressWarnings("unused")
        MockedConstruction<SaveButtonComponent> mockedSave =
            Mockito.mockConstruction(SaveButtonComponent.class, (mock, ctx) -> {
                saveCtorArgs.add(ctx.arguments().toArray());
                String label = (String) ctx.arguments().get(1);
                if ("Save locally".equals(label)) {
                    Mockito.when(mock.get()).thenReturn(saveLocalPanel);
                } else if ("Save globally".equals(label)) {
                    Mockito.when(mock.get()).thenReturn(saveGlobalPanel);
                }
            }); @SuppressWarnings("unused")
        MockedConstruction<CloseButtonComponent> mockedClose = Mockito.mockConstruction(
            CloseButtonComponent.class,
            (mock, ctx) -> Mockito.when(mock.get()).thenReturn(closePanel))) {

            //when
            JComponent wrapper =
                (JComponent) method(FooterComponent.class, "getActionButtonsWrapperIn")
                    .invoke(footerComp);

            //then
            Assertions.assertThat(wrapper.getLayout()).isInstanceOf(BoxLayout.class);
            Assertions.assertThat(wrapper.getBorder())
                .isSameAs(field(FooterComponent.class, "ACTION_BUTTONS_WRAPPER_BORDER").get(null));

            Assertions.assertThat(wrapper.getComponentCount()).isEqualTo(4);
            Assertions.assertThat(wrapper.getComponent(0)).isSameAs(saveLocalPanel);
            Assertions.assertThat(wrapper.getComponent(1)).isSameAs(saveGlobalPanel);

            JComponent rigid = (JComponent) wrapper.getComponent(2);
            Assertions.assertThat(rigid.getPreferredSize()).isEqualTo(new Dimension(10, 0));
            Assertions.assertThat(rigid.getMinimumSize()).isEqualTo(new Dimension(10, 0));
            Assertions.assertThat(rigid.getMaximumSize()).isEqualTo(new Dimension(10, 0));

            Assertions.assertThat(wrapper.getComponent(3)).isSameAs(closePanel);

            Assertions.assertThat(saveCtorArgs.size()).isEqualTo(2);
            Assertions.assertThat(saveCtorArgs.get(0)[0]).isSameAs(local);
            Assertions.assertThat(saveCtorArgs.get(0)[1]).isEqualTo("Save locally");
            Assertions.assertThat(saveCtorArgs.get(1)[0]).isSameAs(global);
            Assertions.assertThat(saveCtorArgs.get(1)[1]).isEqualTo("Save globally");
        }
    }

    @Test
    public void testSetLayoutForActionButtonsWrapper() throws Exception {
        //given
        Runnable global = Mockito.mock(Runnable.class);
        Runnable local = Mockito.mock(Runnable.class);
        FooterComponent footerComp = new FooterComponent(global, local);

        JPanel wrapper = new JPanel();

        //when
        method(FooterComponent.class, "setLayoutForActionButtonsWrapper", JComponent.class)
            .invoke(footerComp, wrapper);

        //then
        Assertions.assertThat(wrapper.getLayout()).isInstanceOf(BoxLayout.class);
        Border expected =
            (Border) field(FooterComponent.class, "ACTION_BUTTONS_WRAPPER_BORDER").get(null);
        Assertions.assertThat(wrapper.getBorder()).isSameAs(expected);
    }

    @Test
    public void testAddActionButtonsToActionButtonsWrapper() throws Exception {
        //given
        Runnable global = Mockito.mock(Runnable.class);
        Runnable local = Mockito.mock(Runnable.class);
        FooterComponent footerComp = new FooterComponent(global, local);

        JPanel wrapper = new JPanel();

        JPanel saveLocalPanel = new JPanel();
        JPanel saveGlobalPanel = new JPanel();
        JPanel closePanel = new JPanel();

        try (@SuppressWarnings("unused")
        MockedConstruction<SaveButtonComponent> mockedSave =
            Mockito.mockConstruction(SaveButtonComponent.class, (mock, ctx) -> {
                String label = (String) ctx.arguments().get(1);
                if ("Save locally".equals(label)) {
                    Mockito.when(mock.get()).thenReturn(saveLocalPanel);
                } else if ("Save globally".equals(label)) {
                    Mockito.when(mock.get()).thenReturn(saveGlobalPanel);
                }
            }); @SuppressWarnings("unused")
        MockedConstruction<CloseButtonComponent> mockedClose = Mockito.mockConstruction(
            CloseButtonComponent.class,
            (mock, ctx) -> Mockito.when(mock.get()).thenReturn(closePanel))) {

            //when
            method(
                FooterComponent.class, "addActionButtonsToActionButtonsWrapper", JComponent.class)
                .invoke(footerComp, wrapper);

            //then
            Assertions.assertThat(wrapper.getComponentCount()).isEqualTo(4);
            Assertions.assertThat(wrapper.getComponent(0)).isSameAs(saveLocalPanel);
            Assertions.assertThat(wrapper.getComponent(1)).isSameAs(saveGlobalPanel);

            JComponent rigid = (JComponent) wrapper.getComponent(2);
            Assertions.assertThat(rigid.getPreferredSize()).isEqualTo(new Dimension(10, 0));

            Assertions.assertThat(wrapper.getComponent(3)).isSameAs(closePanel);
        }
    }
}