package CH.ifa.draw.util;

import java.awt.Color;
import java.io.ByteArrayOutputStream;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;

import de.renew.draw.storables.ontology.Storable;
import de.renew.draw.storables.ontology.StorableInput;
import de.renew.draw.storables.ontology.StorableOutput;

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

class StorableOutputTest {
    private static CH.ifa.draw.util.StorableOutput _storableOutput;
    private static ByteArrayOutputStream _outputStream;

    @BeforeEach
    public void setUp() {
        _outputStream = new ByteArrayOutputStream();
        _storableOutput = new CH.ifa.draw.util.StorableOutput(_outputStream);
    }

    @Test
    void testWriteStorable() {
        //given
        Storable s = new Storable() {
            @Override
            public void write(StorableOutput dw) {
                dw.writeString("dummy");
            }

            @Override
            public void read(StorableInput dr) {

            }
        };
        //when
        _storableOutput.writeStorable(s);
        _storableOutput.close();
        //then "dummy"
        String actual = _outputStream.toString().replace("\r\n", "\n");
        assertThat(actual).isEqualTo("\n    CH.ifa.draw.util.StorableOutputTest$1 \"dummy\"  ");
    }

    @Test
    void testWriteStorableAsNull() {
        //given:
        //when:
        _storableOutput.writeStorable(null);
        _storableOutput.close();
        //then:
        assertThat(_outputStream.toString()).isEqualTo("NULL ");
    }

    @Test
    void testWriteStorableMapped() {
        //given
        Storable s = new Storable() {
            @Override
            public void write(StorableOutput dw) {
                dw.writeString("dummy");
            }

            @Override
            public void read(StorableInput dr) {

            }
        };
        //when:
        _storableOutput.map(s);
        _storableOutput.writeStorable(s);
        _storableOutput.close();
        String result = _outputStream.toString();
        //then:
        assertThat(_storableOutput.mapped(s)).isTrue();
        assertThat(result).isEqualTo("REF 0 ");

    }

    @Test
    void testWriteStorableMappedAsNull() {
        //given:
        _storableOutput.map(null);
        _storableOutput.writeStorable(null);
        _storableOutput.close();
        String result2 = _outputStream.toString();

        //then:
        assertThat(_storableOutput.mapped(null)).isTrue();
        assertThat(result2).isEqualTo("NULL ");
    }

    @Test
    void testWriteInt() {
        //given/when
        _storableOutput.writeInt(34);
        _storableOutput.writeInt(-11);
        _storableOutput.writeInt(0);
        _storableOutput.writeInt(Integer.MAX_VALUE);
        _storableOutput.writeInt(Integer.MIN_VALUE);
        _storableOutput.close();

        //then
        assertThat(_outputStream.toString()).isEqualTo("34 -11 0 2147483647 -2147483648 ");
    }

    @ParameterizedTest
    @ValueSource(ints = { 255, 0, 5, 100 })
    void testWriteColorRed(int rValue) {
        //given
        Color color = new Color(rValue, 0, 0);
        //when
        _storableOutput.writeColor(color);
        _storableOutput.close();
        //then
        assertThat(_outputStream.toString()).isEqualTo((rValue + " 0 0 "));
    }

    @ParameterizedTest
    @ValueSource(ints = { 255, 0, 5, 100 })
    void testWriteColorBlue(int bValue) {
        //given
        Color color = new Color(0, 0, bValue);
        //when
        _storableOutput.writeColor(color);
        _storableOutput.close();
        //then
        assertThat(_outputStream.toString()).isEqualTo("0 0 " + bValue + " ");
    }

    @ParameterizedTest
    @ValueSource(ints = { 255, 0, 5, 100 })
    void testWriteColorGreen(int gValue) {
        //given
        Color color = new Color(0, gValue, 0);
        //when
        _storableOutput.writeColor(color);
        _storableOutput.close();
        //then
        assertThat(_outputStream.toString()).isEqualTo("0 " + gValue + " 0 ");
    }

    @ParameterizedTest
    @NullSource
    void testWriteColorAsNull(Color color) {

        assertThatThrownBy(() -> _storableOutput.writeColor(color))
            .isInstanceOf(NullPointerException.class);
    }

    @ParameterizedTest
    @ValueSource(
        doubles = {
            34.33, -24.33, Double.MAX_VALUE, Double.MIN_VALUE, Double.POSITIVE_INFINITY,
            Double.NEGATIVE_INFINITY, Double.NaN })
    void testWriteDouble(Double doubleValue) {
        //given/when
        _storableOutput.writeDouble(doubleValue);
        _storableOutput.close();

        //then
        assertThat(_outputStream.toString()).isEqualTo(doubleValue + " ");
    }

    @Test
    void testWriteBooleanTrue() {
        // test if true is written as 1:
        //given/when
        _storableOutput.writeBoolean(true);
        _storableOutput.close();
        String result = _outputStream.toString();

        //then:
        assertThat(result).isEqualTo("1 ");

    }

    @Test
    void testWriteBooleanFalse() {
        //test if false is written as 0
        //given/when
        _storableOutput.writeBoolean(false);
        _storableOutput.close();
        String fResult = _outputStream.toString();
        //then
        assertThat(fResult).isEqualTo("0 ");
    }

    @Test
    void testWriteString() {
        //given/when
        //test n and //
        _storableOutput.writeString("Hello\n and \\test and \" and \t and blob and äöü€");
        _storableOutput.close();
        String result = _outputStream.toString();
        //then
        assertThat(result)
            .isEqualTo("\"Hello\\n and \\\\test and \\\" and \\\t and blob and äöü€\" ");

    }

    @Test
    void testClose() {

        //given/when:
        //write something before closing:
        _storableOutput.writeInt(34);
        _storableOutput.close();
        String before = _outputStream.toString();

        // Attempt to write more
        _storableOutput.writeInt(100);
        String after = _outputStream.toString();

        //then:
        // Ensure nothing new was added
        assertThat(before).isEqualTo(after);

    }
}