package de.renew.draw.storables.api;

import java.awt.Rectangle;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import CH.ifa.draw.standard.OffsetLocator;
import CH.ifa.draw.standard.StandardDrawing;
import de.renew.draw.storables.impl.services.StorableServiceImpl;
import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.storables.ontology.DrawingChangeEvent;
import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.storables.ontology.FigureChangeEvent;
import de.renew.draw.storables.ontology.FigureEnumeration;
import de.renew.draw.storables.ontology.Locator;
import de.renew.draw.storables.ontology.Storable;
import de.renew.ioontology.ExtensionFileFilter;
import de.renew.ioontology.MultiExtensionFileFilter;
import de.renew.ioontology.exporting.ExportFormat;
import de.renew.ioontology.importing.ImportFormat;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class StorableApiTest {

    private static final CH.ifa.draw.util.StorableInput DUMMY_STORABLE_INPUT =
        mock(CH.ifa.draw.util.StorableInput.class);

    private static final String DUMMY_DRAWING_TYPE = "Dummy-Type";

    private static final CH.ifa.draw.util.StorableOutput DUMMY_STORABLE_OUTPUT =
        mock(CH.ifa.draw.util.StorableOutput.class);

    private static final ExportFormat<Drawing> DUMMY_EXPORT_FORMAT = mock(ExportFormat.class);
    private static final ImportFormat<Drawing> DUMMY_IMPORT_FORMAT = mock(ImportFormat.class);

    private StorableServiceImpl _mockedService;
    private Drawing _mockedDrawing;

    @BeforeEach
    void setUp() throws NoSuchFieldException, IllegalAccessException {
        _mockedService = mock(StorableServiceImpl.class);
        _mockedDrawing = mock(Drawing.class);
        Field serviceField = StorableApi.class.getDeclaredField("_service");
        serviceField.setAccessible(true);
        serviceField.set(null, _mockedService);
    }

    @AfterEach
    void tearDown() {
        _mockedService = null;
        _mockedDrawing = null;
    }

    @Test
    void testCreateStorableInputForInputStream() {
        // given
        InputStream is = mock(InputStream.class);
        when(_mockedService.createStorableInput(any(InputStream.class)))
            .thenReturn(DUMMY_STORABLE_INPUT);

        // when
        de.renew.draw.storables.ontology.StorableInput storableInput =
            StorableApi.createStorableInput(is);

        // then
        assertThat(storableInput).isEqualTo(DUMMY_STORABLE_INPUT);
        verify(_mockedService).createStorableInput(is);
    }

    @Test
    void testCreateStorableInputForInputStreamAndBoolean() {
        // given
        InputStream is = mock(InputStream.class);
        when(_mockedService.createStorableInput(any(InputStream.class), anyBoolean()))
            .thenReturn(DUMMY_STORABLE_INPUT);

        // when
        de.renew.draw.storables.ontology.StorableInput storableInput =
            StorableApi.createStorableInput(is, false);

        // then
        assertThat(storableInput).isEqualTo(DUMMY_STORABLE_INPUT);
        verify(_mockedService).createStorableInput(is, false);
    }

    @Test
    void testCreateStorableInputForURLAndBoolean() throws IOException {
        // given
        URL url = mock(URL.class);
        when(_mockedService.createStorableInput(any(URL.class), anyBoolean()))
            .thenReturn(DUMMY_STORABLE_INPUT);

        // when
        de.renew.draw.storables.ontology.StorableInput storableInput =
            StorableApi.createStorableInput(url, false);

        // then
        assertThat(storableInput).isEqualTo(DUMMY_STORABLE_INPUT);
        verify(_mockedService).createStorableInput(url, false);
    }

    @Test
    void testCreateStorableInputForString() {
        // given
        String stringStream = "test";
        when(_mockedService.createStorableInput(any(String.class)))
            .thenReturn(DUMMY_STORABLE_INPUT);

        // when
        de.renew.draw.storables.ontology.StorableInput storableInput =
            StorableApi.createStorableInput(stringStream);

        // then
        assertThat(storableInput).isEqualTo(DUMMY_STORABLE_INPUT);
        verify(_mockedService).createStorableInput(stringStream);
    }

    @Test
    void testCreatePatchingStorableInputForInputStream() {
        // given
        InputStream is = mock(InputStream.class);
        when(_mockedService.createPatchingStorableInput(any(InputStream.class)))
            .thenReturn(DUMMY_STORABLE_INPUT);

        // when
        de.renew.draw.storables.ontology.StorableInput storableInput =
            StorableApi.createPatchingStorableInput(is);

        // then
        assertThat(storableInput).isEqualTo(DUMMY_STORABLE_INPUT);
        verify(_mockedService).createPatchingStorableInput(is);
    }

    @Test
    void testCreatePatchingStorableInputForURLAndBoolean() throws IOException {
        // given
        URL url = mock(URL.class);
        when(_mockedService.createPatchingStorableInput(any(URL.class), anyBoolean()))
            .thenReturn(DUMMY_STORABLE_INPUT);


        // when
        de.renew.draw.storables.ontology.StorableInput storableInput =
            StorableApi.createPatchingStorableInput(url, false);

        // then
        assertThat(storableInput).isEqualTo(DUMMY_STORABLE_INPUT);
        verify(_mockedService).createPatchingStorableInput(url, false);
    }

    @Test
    void testCreateStorableOutputForOutputStream() {
        // given
        OutputStream os = mock(OutputStream.class);
        when(_mockedService.createStorableOutput(any(OutputStream.class)))
            .thenReturn(DUMMY_STORABLE_OUTPUT);

        // when
        de.renew.draw.storables.ontology.StorableOutput storableOutput =
            StorableApi.createStorableOutput(os);

        // then
        assertThat(storableOutput).isEqualTo(DUMMY_STORABLE_OUTPUT);
        verify(_mockedService).createStorableOutput(os);
    }

    @Test
    void testCreateStorableOutputForFile() throws FileNotFoundException {
        // given
        File file = new File("Dummy");
        when(_mockedService.createStorableOutput(any(File.class)))
            .thenReturn(DUMMY_STORABLE_OUTPUT);

        // when
        de.renew.draw.storables.ontology.StorableOutput storableOutput =
            StorableApi.createStorableOutput(file);

        // then
        assertThat(storableOutput).isEqualTo(DUMMY_STORABLE_OUTPUT);
        verify(_mockedService).createStorableOutput(file);
    }

    @Test
    void testCloneStorable() throws Exception {
        //given
        Storable dummyDrawing1 = mock(StandardDrawing.class);
        Storable dummyDrawing2 = mock(StandardDrawing.class);
        when(_mockedService.cloneStorable(any(Storable.class))).thenReturn(dummyDrawing1);

        //when
        Storable clonedDrawing = StorableApi.cloneStorable(dummyDrawing2);

        //then
        assertThat(clonedDrawing).isSameAs(dummyDrawing1);
        assertThat(clonedDrawing).isInstanceOf(StandardDrawing.class);
        verify(_mockedService).cloneStorable(dummyDrawing2);
    }

    @Test
    void testCreateDrawingChangeEvent() {
        //given
        Rectangle r = mock(Rectangle.class);
        DrawingChangeEvent drawingChangeEvent = mock(DrawingChangeEvent.class);
        when(_mockedService.createDrawingChangeEvent(_mockedDrawing, r))
            .thenReturn(drawingChangeEvent);

        //when
        DrawingChangeEvent res = StorableApi.createDrawingChangeEvent(_mockedDrawing, r);

        //then
        assertThat(res).isEqualTo(drawingChangeEvent);
        verify(_mockedService).createDrawingChangeEvent(_mockedDrawing, r);
    }

    @Test
    void testCreateFigureChangeEventWithFigure() {
        //given
        Figure figure = mock(Figure.class);
        FigureChangeEvent expected = mock(FigureChangeEvent.class);
        when(_mockedService.createFigureChangeEvent(any(Figure.class))).thenReturn(expected);

        //when
        FigureChangeEvent actual = StorableApi.createFigureChangeEvent(figure);

        //then
        assertThat(actual).isEqualTo(expected);
        verify(_mockedService).createFigureChangeEvent(figure);
    }

    @Test
    void testCreateFigureChangeEventWithFigureAndRectangle() {
        //given
        Figure figure = mock(Figure.class);
        Rectangle rectangle = new Rectangle();
        FigureChangeEvent expected = mock(FigureChangeEvent.class);
        when(_mockedService.createFigureChangeEvent(any(Figure.class), any(Rectangle.class)))
            .thenReturn(expected);

        //when
        FigureChangeEvent actual = StorableApi.createFigureChangeEvent(figure, rectangle);

        //then
        assertThat(actual).isEqualTo(expected);
        verify(_mockedService).createFigureChangeEvent(figure, rectangle);
    }



    @Test
    void testCreateFilteredFigureEnumerator() {
        // given
        FigureEnumeration enumeration = mock(FigureEnumeration.class);
        Predicate<Figure> predicate = mock(Predicate.class);

        FigureEnumeration filteredEnumeration = mock(FigureEnumeration.class);
        when(
            _mockedService
                .createFilteredFigureEnumerator(any(FigureEnumeration.class), any(Predicate.class)))
            .thenReturn(filteredEnumeration);

        // when
        FigureEnumeration res = StorableApi.createFilteredFigureEnumerator(enumeration, predicate);

        // then
        assertThat(res).isEqualTo(filteredEnumeration);
        verify(_mockedService).createFilteredFigureEnumerator(enumeration, predicate);
    }

    @Test
    void testCreateMergedFigureEnumerator() {
        // given
        FigureEnumeration enumerationA = mock(FigureEnumeration.class);
        FigureEnumeration enumerationB = mock(FigureEnumeration.class);

        FigureEnumeration mergedEnumeration = mock(FigureEnumeration.class);
        when(_mockedService.createMergedFigureEnumerator(enumerationA, enumerationB))
            .thenReturn(mergedEnumeration);

        // when
        FigureEnumeration res =
            StorableApi.createMergedFigureEnumerator(enumerationA, enumerationB);

        // then
        assertThat(res).isEqualTo(mergedEnumeration);
        verify(_mockedService).createMergedFigureEnumerator(enumerationA, enumerationB);
    }

    @Test
    void testIsDrawingNullDrawing() {
        // given
        Drawing drawing = mock(Drawing.class);

        // when
        StorableApi.isDrawingNullDrawing(drawing);

        // then
        verify(_mockedService).isDrawingNullDrawing(drawing);
    }

    @Test
    void testCreateElbowTextLocator() {
        // given/when
        StorableApi.createElbowTextLocator();
        //then
        verify(_mockedService).createElbowTextLocator();
    }

    @Test
    void testCreateOffsetLocatorLocator() {
        // given
        Locator locator = mock(Locator.class);
        OffsetLocator offsetLocator = mock(OffsetLocator.class);
        when(_mockedService.createOffsetLocator(any(Locator.class))).thenReturn(offsetLocator);
        // when
        Locator testOffsetLocator = StorableApi.createOffsetLocator(locator);
        // then
        verify(_mockedService).createOffsetLocator(locator);
        assertThat(testOffsetLocator).isEqualTo(offsetLocator);

    }

    @ParameterizedTest
    @MethodSource("provideLocalOffset")
    void testCreateOffsetLocatorLocatorWithOffset(int offsetX, int offsetY) {
        // given
        Locator locator = mock(Locator.class);
        OffsetLocator offsetLocator = mock(OffsetLocator.class);
        when(_mockedService.createOffsetLocator(locator, offsetX, offsetY))
            .thenReturn(offsetLocator);
        // when
        Locator testOffsetLocator = StorableApi.createOffsetLocator(locator, offsetX, offsetY);
        // then
        verify(_mockedService).createOffsetLocator(locator, offsetX, offsetY);
        assertThat(testOffsetLocator).isEqualTo(offsetLocator);
    }

    private static Stream<Arguments> provideLocalOffset() {
        return Stream.of(Arguments.of(1, 1), Arguments.of(1, -2), Arguments.of(-300, -20));
    }

    @Test
    void testGetKnownDrawingTypesFileFilter() {
        // given
        MultiExtensionFileFilter expectedFilter = mock(MultiExtensionFileFilter.class);
        when(_mockedService.getKnownDrawingTypesFileFilter()).thenReturn(expectedFilter);

        // when
        MultiExtensionFileFilter actualFilter = StorableApi.getKnownDrawingTypesFileFilter();

        // then
        assertThat(actualFilter).isEqualTo(expectedFilter);
        verify(_mockedService).getKnownDrawingTypesFileFilter();
    }

    @Test
    void testGetExtensionFileFilterByDrawingType() {
        // given
        ExtensionFileFilter expectedFilter = mock(ExtensionFileFilter.class);
        when(_mockedService.getExtensionFileFilterByDrawingType(any())).thenReturn(expectedFilter);

        // when
        ExtensionFileFilter actualFilter =
            StorableApi.getExtensionFileFilterByDrawingType(DUMMY_DRAWING_TYPE);

        // then
        assertThat(actualFilter).isEqualTo(expectedFilter);
        verify(_mockedService).getExtensionFileFilterByDrawingType(DUMMY_DRAWING_TYPE);
    }

    @Test
    void testRegisterDrawingType() {
        // given
        ExtensionFileFilter filter = mock(ExtensionFileFilter.class);

        // when
        StorableApi.registerDrawingType(DUMMY_DRAWING_TYPE, filter);

        // then
        verify(_mockedService).registerDrawingType(DUMMY_DRAWING_TYPE, filter);
    }

    @Test
    void testUnregisterDrawingType() {
        // when
        StorableApi.unregisterDrawingType(DUMMY_DRAWING_TYPE);

        // then
        verify(_mockedService).unregisterDrawingType(DUMMY_DRAWING_TYPE);
    }

    @Test
    void testCreateDrawingForDrawingType() {
        // given
        when(_mockedService.createDrawingForDrawingType(any())).thenReturn(_mockedDrawing);

        // when
        Drawing actualDrawing = StorableApi.createDrawingForDrawingType(DUMMY_DRAWING_TYPE);

        // then
        assertThat(actualDrawing).isSameAs(_mockedDrawing);
        verify(_mockedService).createDrawingForDrawingType(DUMMY_DRAWING_TYPE);
    }

    @Test
    void testAddExportFormat() {
        // when
        StorableApi.addExportFormat(DUMMY_EXPORT_FORMAT);

        // then
        verify(_mockedService).addExportFormat(DUMMY_EXPORT_FORMAT);
    }

    @Test
    void testRemoveExportFormat() {
        // when
        StorableApi.removeExportFormat(DUMMY_EXPORT_FORMAT);

        // then
        verify(_mockedService).removeExportFormat(DUMMY_EXPORT_FORMAT);
    }

    @Test
    void testGetExportFormats() {
        // given
        List<ExportFormat<Drawing>> expected = List.of(DUMMY_EXPORT_FORMAT);
        when(_mockedService.getExportFormats()).thenReturn(expected);

        // when
        List<ExportFormat<Drawing>> actual = StorableApi.getExportFormats();

        // then
        assertThat(actual).isEqualTo(expected);
        verify(_mockedService).getExportFormats();
    }

    @Test
    void testAddImportFormat() {
        // when
        StorableApi.addImportFormat(DUMMY_IMPORT_FORMAT);

        // then
        verify(_mockedService).addImportFormat(DUMMY_IMPORT_FORMAT);
    }

    @Test
    void testRemoveImportFormat() {
        // when
        StorableApi.removeImportFormat(DUMMY_IMPORT_FORMAT);

        // then
        verify(_mockedService).removeImportFormat(DUMMY_IMPORT_FORMAT);
    }

    @Test
    void testGetImportFormats() {
        // given
        List<ImportFormat<Drawing>> expected = List.of(DUMMY_IMPORT_FORMAT);
        when(_mockedService.getImportFormats()).thenReturn(expected);

        // when
        List<ImportFormat<Drawing>> actual = StorableApi.getImportFormats();

        // then
        assertThat(actual).isEqualTo(expected);
        verify(_mockedService).getImportFormats();
    }


}