package de.renew.draw.api.ui.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.assertj.core.util.Arrays;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import org.mockito.Mockito;

import CH.ifa.draw.application.VersionInfoCommand;
import CH.ifa.draw.figures.GroupCommand;
import CH.ifa.draw.figures.SelectCommand;
import CH.ifa.draw.figures.UngroupCommand;
import CH.ifa.draw.framework.ChildFigure;
import CH.ifa.draw.framework.ParentFigure;
import CH.ifa.draw.standard.AbstractFigure;
import CH.ifa.draw.standard.AlignCommand;
import CH.ifa.draw.standard.ChangeAttributeCommand;
import CH.ifa.draw.standard.SpreadCommand;
import de.renew.draw.storables.ontology.FigureFilter;
import de.renew.draw.ui.api.services.CommandService;
import de.renew.draw.ui.impl.services.CommandServiceImpl;
import de.renew.draw.ui.ontology.AbstractCommand;
import de.renew.draw.ui.ontology.Alignment;
import de.renew.draw.ui.ontology.Anchor;
import de.renew.draw.ui.ontology.SelectionMode;
import de.renew.draw.ui.ontology.SpreadMode;
import de.renew.plugin.IPlugin;

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

class CommandServiceImplTest {

    private CommandService _commandService;
    private static final String DUMMY_COMMAND_NAME = "DummyCommand";
    Map<AbstractCommand, List<Object>> _capturedArgs;

    @BeforeEach
    void setUp() {

        this._commandService = new CommandServiceImpl();
        this._capturedArgs = new HashMap<>();

    }

    @AfterEach
    void tearDown() {

        this._commandService = null;
        this._capturedArgs = null;

    }

    @Test
    void testCreateCommand() {

        // given
        String name = "testCommand";
        final boolean[] success = { false };

        Runnable runnable = () -> success[0] = true;

        // when
        AbstractCommand command = this._commandService.createCommand(name, runnable);

        // then
        assertThat(command).isNotNull();
        command.execute();
        assertThat(command.getCommandName()).isEqualTo(name);
        assertThat(success[0]).isTrue();

    }

    @Test
    void testCreateVersionInfoCommand() {

        // given
        IPlugin dummyPlugin = Mockito.mock(IPlugin.class);
        Mockito.when(dummyPlugin.getName()).thenReturn("dummyPlugin");
        Mockito.when(dummyPlugin.getVersion()).thenReturn("dummyVersion");
        try (MockedConstruction<VersionInfoCommand> vic =
            Mockito.mockConstruction(VersionInfoCommand.class)) {

            // when
            AbstractCommand versionInfoCommand =
                this._commandService.createVersionInfoCommand(dummyPlugin);

            // then
            assertThat(vic.constructed().size()).isEqualTo(1);
            assertThat(versionInfoCommand).isNotNull();
            assertThat(versionInfoCommand).isEqualTo(vic.constructed().get(0));

        }


    }

    @Test
    void testCreateGroupCommand() {
        //given
        try (MockedConstruction<GroupCommand> mockedGroupCmd = Mockito.mockConstruction(
            GroupCommand.class, (mock, context) -> _capturedArgs
                .put(mock, Arrays.asList(context.arguments().toArray())))) {

            // when
            AbstractCommand groupCmd = this._commandService.createGroupCommand(DUMMY_COMMAND_NAME);

            // then
            assertThat(groupCmd).isNotNull();
            assertThat(mockedGroupCmd.constructed()).hasSize(1);
            assertThat(groupCmd).isEqualTo(mockedGroupCmd.constructed().get(0));

            assertThat(mockedGroupCmd).returns(1, mc -> mc.constructed().size())
                .returns(groupCmd, mc -> mc.constructed().get(0));
            assertThat(_capturedArgs.get(groupCmd)).containsExactly(DUMMY_COMMAND_NAME);

        }
    }

    @Test
    void testCreateUngroupCommand() {
        //given
        try (MockedConstruction<UngroupCommand> mockedUngroupCmd = Mockito.mockConstruction(
            UngroupCommand.class, (mock, context) -> _capturedArgs
                .put(mock, Arrays.asList(context.arguments().toArray())))) {

            // when
            AbstractCommand ungroupCmd =
                this._commandService.createUngroupCommand(DUMMY_COMMAND_NAME);

            // then
            assertThat(ungroupCmd).isNotNull();
            assertThat(mockedUngroupCmd.constructed()).hasSize(1);
            assertThat(ungroupCmd).isEqualTo(mockedUngroupCmd.constructed().get(0));

            assertThat(mockedUngroupCmd).returns(1, mc -> mc.constructed().size())
                .returns(ungroupCmd, mc -> mc.constructed().get(0));
            assertThat(_capturedArgs.get(ungroupCmd)).containsExactly(DUMMY_COMMAND_NAME);
        }
    }

    @Test
    void testSelectCommandWithFigureClass() {
        //given
        try (MockedConstruction<SelectCommand> mockedSelectCmd = Mockito.mockConstruction(
            SelectCommand.class, (mock, context) -> _capturedArgs
                .put(mock, Arrays.asList(context.arguments().toArray())))) {

            // when
            AbstractCommand selectCmd = this._commandService
                .createSelectCommand(DUMMY_COMMAND_NAME, AbstractFigure.class, SelectionMode.ADD);

            // then
            assertThat(selectCmd).isNotNull();
            assertThat(mockedSelectCmd.constructed().size()).isEqualTo(1);
            assertThat(selectCmd).isEqualTo(mockedSelectCmd.constructed().get(0));

            assertThat(mockedSelectCmd).returns(1, mc -> mc.constructed().size())
                .returns(selectCmd, mc -> mc.constructed().get(0));
            assertThat(_capturedArgs.get(selectCmd))
                .containsExactly(DUMMY_COMMAND_NAME, AbstractFigure.class, 1);
        }
    }

    @Test
    void testSelectCommandWithFigureAndParentClass() {
        //given
        try (MockedConstruction<SelectCommand> mockedSelectCmd = Mockito.mockConstruction(
            SelectCommand.class, (mock, context) -> _capturedArgs
                .put(mock, Arrays.asList(context.arguments().toArray())))) {

            // when
            AbstractCommand selectCmd = this._commandService.createSelectCommand(
                DUMMY_COMMAND_NAME, ChildFigure.class, ParentFigure.class, SelectionMode.ADD);

            // then
            assertThat(selectCmd).isNotNull();
            assertThat(mockedSelectCmd.constructed().size()).isEqualTo(1);
            assertThat(selectCmd).isEqualTo(mockedSelectCmd.constructed().get(0));

            assertThat(mockedSelectCmd).returns(1, mc -> mc.constructed().size())
                .returns(selectCmd, mc -> mc.constructed().get(0));
            assertThat(_capturedArgs.get(selectCmd))
                .containsExactly(DUMMY_COMMAND_NAME, ChildFigure.class, ParentFigure.class, 1);
        }
    }

    @Test
    void testSelectCommandWithFigureFilter() {
        //given
        FigureFilter filter = Mockito.mock(FigureFilter.class);
        try (MockedConstruction<SelectCommand> mockedSelectCmd = Mockito.mockConstruction(
            SelectCommand.class, (mock, context) -> _capturedArgs
                .put(mock, Arrays.asList(context.arguments().toArray())))) {

            // when
            AbstractCommand selectCmd = this._commandService
                .createSelectCommand(DUMMY_COMMAND_NAME, filter, SelectionMode.ADD);

            // then
            assertThat(selectCmd).isNotNull();
            assertThat(mockedSelectCmd.constructed().size()).isEqualTo(1);
            assertThat(selectCmd).isEqualTo(mockedSelectCmd.constructed().get(0));

            assertThat(mockedSelectCmd).returns(1, mc -> mc.constructed().size())
                .returns(selectCmd, mc -> mc.constructed().get(0));
            assertThat(_capturedArgs.get(selectCmd)).containsExactly(DUMMY_COMMAND_NAME, filter, 1);
        }
    }

    @Test
    void testCreateAlignCommand() {
        //given
        try (MockedConstruction<AlignCommand> mockedAlignCmd = Mockito.mockConstruction(
            AlignCommand.class, (mock, context) -> _capturedArgs
                .put(mock, Arrays.asList(context.arguments().toArray())))) {

            // when
            AbstractCommand alignCmd =
                this._commandService.createAlignCommand(DUMMY_COMMAND_NAME, Alignment.TOPS);

            // then
            assertThat(alignCmd).isNotNull();
            assertThat(mockedAlignCmd.constructed().size()).isEqualTo(1);
            assertThat(alignCmd).isEqualTo(mockedAlignCmd.constructed().get(0));

            assertThat(mockedAlignCmd).returns(1, mc -> mc.constructed().size())
                .returns(alignCmd, mc -> mc.constructed().get(0));
            assertThat(_capturedArgs.get(alignCmd)).containsExactly(DUMMY_COMMAND_NAME, 3);
        }
    }

    @Test
    void testCreateAlignCommandWithAnchor() {
        //given
        try (MockedConstruction<AlignCommand> mockedAlignCmd = Mockito.mockConstruction(
            AlignCommand.class, (mock, context) -> _capturedArgs
                .put(mock, Arrays.asList(context.arguments().toArray())))) {

            // when
            AbstractCommand alignCmd = this._commandService
                .createAlignCommand(DUMMY_COMMAND_NAME, Alignment.TOPS, Anchor.ANCHOR_BIGGEST);

            // then
            assertThat(alignCmd).isNotNull();
            assertThat(mockedAlignCmd.constructed().size()).isEqualTo(1);
            assertThat(alignCmd).isEqualTo(mockedAlignCmd.constructed().get(0));

            assertThat(mockedAlignCmd).returns(1, mc -> mc.constructed().size())
                .returns(alignCmd, mc -> mc.constructed().get(0));
            assertThat(_capturedArgs.get(alignCmd)).containsExactly(DUMMY_COMMAND_NAME, 3, 2);
        }
    }

    @Test
    void testCreateSpreadCommand() {
        //given
        try (MockedConstruction<SpreadCommand> mockedSpreadCmd = Mockito.mockConstruction(
            SpreadCommand.class, (mock, context) -> _capturedArgs
                .put(mock, Arrays.asList(context.arguments().toArray())))) {

            // when
            AbstractCommand spreadCmd =
                this._commandService.createSpreadCommand(DUMMY_COMMAND_NAME, SpreadMode.LEFTS);

            // then
            assertThat(spreadCmd).isNotNull();
            assertThat(mockedSpreadCmd.constructed().size()).isEqualTo(1);
            assertThat(spreadCmd).isEqualTo(mockedSpreadCmd.constructed().get(0));

            assertThat(mockedSpreadCmd).returns(1, mc -> mc.constructed().size())
                .returns(spreadCmd, mc -> mc.constructed().get(0));
            assertThat(_capturedArgs.get(spreadCmd)).containsExactly(DUMMY_COMMAND_NAME, 0);
        }
    }

    @Test
    void testCreateChangeAttributeCommand() {
        //given
        String dummyAttributeName = "DummyAttribute";
        Object object = Mockito.mock(Object.class);
        try (MockedConstruction<ChangeAttributeCommand> mockedChangeAttributeCmd =
            Mockito.mockConstruction(
                ChangeAttributeCommand.class, (mock, context) -> _capturedArgs
                    .put(mock, Arrays.asList(context.arguments().toArray())))) {

            // when
            AbstractCommand changeAttributeCmd = this._commandService
                .createChangeAttributeCommand(DUMMY_COMMAND_NAME, dummyAttributeName, object);

            // then
            assertThat(changeAttributeCmd).isNotNull();
            assertThat(mockedChangeAttributeCmd.constructed().size()).isEqualTo(1);
            assertThat(changeAttributeCmd).isEqualTo(mockedChangeAttributeCmd.constructed().get(0));

            assertThat(mockedChangeAttributeCmd).returns(1, mc -> mc.constructed().size())
                .returns(changeAttributeCmd, mc -> mc.constructed().get(0));
            assertThat(_capturedArgs.get(changeAttributeCmd))
                .containsExactly(DUMMY_COMMAND_NAME, dummyAttributeName, object);
        }
    }
}
