package net.amygdalum.testrecorder.types;

import static net.amygdalum.extensions.assertj.conventions.DefaultEquality.defaultEquality;
import static net.amygdalum.testrecorder.values.SerializedLiteral.literal;
import static org.assertj.core.api.Assertions.assertThat;

import java.io.PrintStream;
import java.lang.reflect.Type;

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

import net.amygdalum.testrecorder.values.SerializedLiteral;
import net.amygdalum.testrecorder.values.SerializedNull;
import net.amygdalum.testrecorder.values.SerializedObject;

public class SerializedOutputTest {

	private static final SerializedLiteral STRING_LITERAL = literal("Hello");
	private static final SerializedObject PRINTSTREAM_OBJECT = new SerializedObject(PrintStream.class);
	private static final SerializedValue VOID = SerializedNull.VOID;

	private SerializedOutput output;
	private SerializedOutput outputNoResult;

	@BeforeEach
	void before() throws Exception {
		output = new SerializedOutput(41, printStreamAppend())
			.updateArguments(STRING_LITERAL)
			.updateResult(PRINTSTREAM_OBJECT);
		outputNoResult = new SerializedOutput(41, printStreamPrintln())
			.updateArguments(STRING_LITERAL)
			.updateResult(VOID);
	}

	@Test
	void testGetId() throws Exception {
		assertThat(output.getId()).isEqualTo(41);
	}

	@Test
	void testGetDeclaringClass() throws Exception {
		assertThat(output.getDeclaringClass()).isSameAs(PrintStream.class);
	}

	@Test
	void testGetName() throws Exception {
		assertThat(output.getMethodName()).isSameAs("append");
		assertThat(outputNoResult.getMethodName()).isSameAs("println");
	}

	@Test
	void testGetTypes() throws Exception {
		assertThat(output.getArgumentTypes()).containsExactly(CharSequence.class);
		assertThat(outputNoResult.getArgumentTypes()).containsExactly(String.class);
	}

	@Test
	void testGetArguments() throws Exception {
		assertThat(output.getArguments()).extracting(SerializedArgument::getValue).containsExactly(STRING_LITERAL);
	}

	@Test
	void testGetResultType() throws Exception {
		assertThat(output.getResultType()).isSameAs(PrintStream.class);
		assertThat(outputNoResult.getResultType()).isSameAs(void.class);
	}

	@Test
	void testGetResult() throws Exception {
		assertThat(output.getResult().getValue()).isInstanceOf(SerializedObject.class);
		assertThat(outputNoResult.getResult().getValue()).isSameAs(VOID);
	}

	@Test
	void testEquals() throws Exception {
		assertThat(outputNoResult).satisfies(defaultEquality()
			.andEqualTo(new SerializedOutput(41, printStreamPrintln())
				.updateArguments(STRING_LITERAL)
				.updateResult(VOID))
			.andNotEqualTo(output)
			.andNotEqualTo(new SerializedOutput(41, printStreamPrintln())
				.updateArguments(STRING_LITERAL)
				.updateResult(null))
			.andNotEqualTo(new SerializedOutput(42, printStreamPrintln())
				.updateArguments(STRING_LITERAL)
				.updateResult(VOID))
			.andNotEqualTo(new SerializedOutput(41, printStreamAppend())
				.updateArguments(STRING_LITERAL)
				.updateResult(VOID))
			.andNotEqualTo(new SerializedOutput(41, printStreamPrintln())
				.updateArguments(literal("Hello World"))
				.updateResult(VOID))
			.conventions());

		assertThat(output).satisfies(defaultEquality()
			.andEqualTo(new SerializedOutput(41, printStreamAppend())
				.updateArguments(STRING_LITERAL)
				.updateResult(PRINTSTREAM_OBJECT))
			.andNotEqualTo(outputNoResult)
			.andNotEqualTo(new SerializedOutput(41, printStreamAppend())
				.updateArguments(STRING_LITERAL)
				.updateResult(null))
			.andNotEqualTo(new SerializedOutput(41, printStreamAppend())
				.updateArguments(STRING_LITERAL)
				.updateResult(VOID))
			.conventions());
	}

	@Test
	void testToString() throws Exception {
		assertThat(output.toString()).contains("PrintStream", "append", "Hello");

		assertThat(outputNoResult.toString()).contains("PrintStream", "println", "Hello");
	}

	@Test
	void testUpdateArguments() throws Exception {
		output.updateArguments((SerializedValue[]) null);

		assertThat(output.getArguments()).isEmpty();
	}

	@Nested
	class testIsComplete {
		@Test
		void onComplete() {
			assertThat(output.isComplete()).isTrue();
			assertThat(outputNoResult.isComplete()).isTrue();
		}

		@Test
		void onMissingResult() throws Exception {
			output.result = null;
			assertThat(output.isComplete()).isFalse();
		}

		@Test
		void onNullArguments() throws Exception {
			output.arguments = null;
			assertThat(output.isComplete()).isFalse();
		}

		@Test
		void onMissingArguments() throws Exception {
			output.arguments = new SerializedArgument[0];
			assertThat(output.isComplete()).isFalse();
		}
	}

	@Nested
	class testHasResult {
		@Test
		void onMethod() throws Exception {
			assertThat(output.hasResult()).isTrue();
		}

		@Test
		void onVoidMethod() throws Exception {
			assertThat(outputNoResult.hasResult()).isFalse();
		}

		@Test
		void withoutResultType() throws Exception {
			output.signature.resultType = null;
			assertThat(output.hasResult()).isFalse();
		}

		@Test
		void withoutResult() throws Exception {
			output.result = null;
			assertThat(output.hasResult()).isFalse();
		}
	}

	private MethodSignature printStreamAppend() {
		return new MethodSignature(PrintStream.class, PrintStream.class, "append", new Type[] {CharSequence.class});
	}

	private MethodSignature printStreamPrintln() {
		return new MethodSignature(PrintStream.class, void.class, "println", new Type[] {String.class});
	}

}
