package net.corda.serialization.internal.amqp

import net.corda.core.contracts.Attachment
import net.corda.core.contracts.AttachmentConstraint
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.Crypto
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.serialization.SerializedBytes
import net.corda.serialization.internal.amqp.testutils.*
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.testutils.ProjectStructure.projectRootDir
import net.corda.serialization.internal.carpenter.ClassCarpenterImpl
import org.junit.Test
import java.math.BigInteger
import java.net.URI
import java.util.*
import kotlin.test.assertEquals

data class TestContractState(
        override val participants: List<AbstractParty>
) : ContractState

class TestAttachmentConstraint : AttachmentConstraint {
    override fun isSatisfiedBy(attachment: Attachment) = true
}

class GenericsTests {
    companion object {
        const val VERBOSE = true

        @Suppress("UNUSED")
        var localPath: URI = projectRootDir.toUri().resolve(
                "serialization-tests/src/test/resources/net/corda/serialization/internal/amqp")

        private val MINI_CORP_NAME = CordaX500Name("Notary Service", "Zurich", "CH")
        private val MINI_CORP_PARTY = Party(MINI_CORP_NAME, Crypto.deriveKeyPairFromEntropy(Crypto.DEFAULT_SIGNATURE_SCHEME, BigInteger.valueOf(20)).public)
    }

    private fun printSeparator() = if (VERBOSE) println("\n\n-------------------------------------------\n\n") else Unit

    private fun <T : Any> BytesAndSchemas<T>.printSchema() = if (VERBOSE) println("${this.schema}\n") else Unit

    @Test(timeout=300_000)
	fun twoDifferentTypesSameParameterizedOuter() {
        data class G<A>(val a: A)

        val factory = testDefaultFactoryNoEvolution()

        val bytes1 = SerializationOutput(factory).serializeAndReturnSchema(G("hi")).apply { printSchema() }

        val bytes2 = SerializationOutput(factory).serializeAndReturnSchema(G(121)).apply { printSchema() }

        listOf(factory, testDefaultFactory()).forEach { f ->
            DeserializationInput(f).deserialize(bytes1.obj).apply { assertEquals("hi", this.a) }
            DeserializationInput(f).deserialize(bytes2.obj).apply { assertEquals(121, this.a) }
        }
    }

    @Test(timeout=300_000)
	fun doWeIgnoreMultipleParams() {
        data class G1<out T>(val a: T)
        data class G2<out T>(val a: T)
        data class Wrapper<out T>(val a: Int, val b: G1<T>, val c: G2<T>)

        val factory = testDefaultFactoryNoEvolution()
        val factory2 = testDefaultFactoryNoEvolution()

        val bytes = SerializationOutput(factory).serializeAndReturnSchema(Wrapper(1, G1("hi"), G2("poop"))).apply { printSchema() }
        printSeparator()
        DeserializationInput(factory2).deserialize(bytes.obj)
    }

    @Test(timeout=300_000)
	fun nestedSerializationOfGenerics() {
        data class G<out T>(val a: T)
        data class Wrapper<out T>(val a: Int, val b: G<T>)

        val factory = testDefaultFactoryNoEvolution()
        val altContextFactory = testDefaultFactoryNoEvolution()
        val ser = SerializationOutput(factory)

        val bytes = ser.serializeAndReturnSchema(G("hi")).apply { printSchema() }

        assertEquals("hi", DeserializationInput(factory).deserialize(bytes.obj).a)
        assertEquals("hi", DeserializationInput(altContextFactory).deserialize(bytes.obj).a)

        val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, G("hi"))).apply { printSchema() }

        printSeparator()

        DeserializationInput(factory).deserialize(bytes2.obj).apply {
            assertEquals(1, a)
            assertEquals("hi", b.a)
        }

        DeserializationInput(altContextFactory).deserialize(bytes2.obj).apply {
            assertEquals(1, a)
            assertEquals("hi", b.a)
        }
    }

    @Test(timeout=300_000)
	fun nestedGenericsReferencesByteArrayViaSerializedBytes() {
        data class G(val a: Int)
        data class Wrapper<T : Any>(val a: Int, val b: SerializedBytes<T>)

        val factory = SerializerFactoryBuilder.build(AllWhitelist,
                ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
        )
        val factory2 = SerializerFactoryBuilder.build(AllWhitelist,
                ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
        )
        val ser = SerializationOutput(factory)

        val gBytes = ser.serialize(G(1))
        val bytes2 = ser.serializeAndReturnSchema(Wrapper<G>(1, gBytes))

        DeserializationInput(factory).deserialize(bytes2.obj).apply {
            assertEquals(1, a)
            assertEquals(1, DeserializationInput(factory).deserialize(b).a)
        }
        DeserializationInput(factory2).deserialize(bytes2.obj).apply {
            assertEquals(1, a)
            assertEquals(1, DeserializationInput(factory).deserialize(b).a)
        }
    }

    @Test(timeout=300_000)
	fun nestedSerializationInMultipleContextsDoesntColideGenericTypes() {
        data class InnerA(val a_a: Int)
        data class InnerB(val a_b: Int)
        data class InnerC(val a_c: String)
        data class Container<T>(val b: T)
        data class Wrapper<T : Any>(val c: Container<T>)

        val factory = SerializerFactoryBuilder.build(AllWhitelist,
                ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
        )
        val factories = listOf(factory, SerializerFactoryBuilder.build(AllWhitelist,
                ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
        ))
        val ser = SerializationOutput(factory)

        ser.serialize(Wrapper(Container(InnerA(1)))).apply {
            factories.forEach {
                DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_a) }
            }
        }

        ser.serialize(Wrapper(Container(InnerB(1)))).apply {
            factories.forEach {
                DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_b) }
            }
        }

        ser.serialize(Wrapper(Container(InnerC("Ho ho ho")))).apply {
            factories.forEach {
                DeserializationInput(it).deserialize(this).apply { assertEquals("Ho ho ho", c.b.a_c) }
            }
        }
    }

    @Test(timeout=300_000)
	fun nestedSerializationWhereGenericDoesntImpactFingerprint() {
        data class Inner(val a: Int)
        data class Container<T : Any>(val b: Inner)
        data class Wrapper<T : Any>(val c: Container<T>)

        val factorys = listOf(
                SerializerFactoryBuilder.build(AllWhitelist,
                        ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
                ),
                SerializerFactoryBuilder.build(AllWhitelist,
                        ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
                ))

        val ser = SerializationOutput(factorys[0])

        ser.serialize(Wrapper<Int>(Container(Inner(1)))).apply {
            factorys.forEach {
                assertEquals(1, DeserializationInput(it).deserialize(this).c.b.a)
            }
        }

        ser.serialize(Wrapper<String>(Container(Inner(1)))).apply {
            factorys.forEach {
                assertEquals(1, DeserializationInput(it).deserialize(this).c.b.a)
            }
        }
    }

    data class ForceWildcard<out T>(val t: T)

    private fun forceWildcardSerialize(
            a: ForceWildcard<*>,
            factory: SerializerFactory = SerializerFactoryBuilder.build(AllWhitelist,
                    ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
            )): SerializedBytes<*> {
        val bytes = SerializationOutput(factory).serializeAndReturnSchema(a)
        bytes.printSchema()
        return bytes.obj
    }

    @Suppress("UNCHECKED_CAST")
    private fun forceWildcardDeserializeString(
            bytes: SerializedBytes<*>,
            factory: SerializerFactory = SerializerFactoryBuilder.build(AllWhitelist,
                    ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
            )) {
        DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<String>>)
    }

    @Suppress("UNCHECKED_CAST")
    private fun forceWildcardDeserializeDouble(
            bytes: SerializedBytes<*>,
            factory: SerializerFactory = SerializerFactoryBuilder.build(AllWhitelist,
                    ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
            )) {
        DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<Double>>)
    }

    @Suppress("UNCHECKED_CAST")
    private fun forceWildcardDeserialize(
            bytes: SerializedBytes<*>,
            factory: SerializerFactory = SerializerFactoryBuilder.build(AllWhitelist,
                    ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
            )) {
        DeserializationInput(factory).deserialize(bytes as SerializedBytes<ForceWildcard<*>>)
    }

    @Test(timeout=300_000)
	fun forceWildcard() {
        forceWildcardDeserializeString(forceWildcardSerialize(ForceWildcard("hello")))
        forceWildcardDeserializeDouble(forceWildcardSerialize(ForceWildcard(3.0)))
    }

    @Test(timeout=300_000)
	fun forceWildcardSharedFactory() {
        val f = SerializerFactoryBuilder.build(AllWhitelist,
                ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
        )
        forceWildcardDeserializeString(forceWildcardSerialize(ForceWildcard("hello"), f), f)
        forceWildcardDeserializeDouble(forceWildcardSerialize(ForceWildcard(3.0), f), f)
    }

    @Test(timeout=300_000)
	fun forceWildcardDeserialize() {
        forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard("hello")))
        forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(10)))
        forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(20.0)))
    }

    @Test(timeout=300_000)
	fun forceWildcardDeserializeSharedFactory() {
        val f = SerializerFactoryBuilder.build(AllWhitelist,
                ClassCarpenterImpl(AllWhitelist, ClassLoader.getSystemClassLoader())
        )
        forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard("hello"), f), f)
        forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(10), f), f)
        forceWildcardDeserialize(forceWildcardSerialize(ForceWildcard(20.0), f), f)
    }

    @Test(timeout=300_000)
	fun loadGenericFromFile() {
        val resource = "${javaClass.simpleName}.${testName()}"
        val sf = testDefaultFactory()

        // Uncomment to re-generate test files
        // File(URI("$localPath/$resource")).writeBytes(forceWildcardSerialize(ForceWildcard("wibble")).bytes)

        assertEquals("wibble",
                DeserializationInput(sf).deserialize(SerializedBytes<ForceWildcard<*>>(
                        GenericsTests::class.java.getResource(resource).readBytes())).t)
    }

    data class StateAndString(val state: TransactionState<*>, val ref: String)
    data class GenericStateAndString<out T: ContractState>(val state: TransactionState<T>, val ref: String)

    //
    // If this doesn't blow up all is fine
    private fun fingerprintingDiffersStrip(state: Any) {
        class cl : ClassLoader()

        val m = ClassLoader::class.java.getDeclaredMethod("findLoadedClass", String::class.java)
        m.isAccessible = true

        val factory1 = testDefaultFactory()
        factory1.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
        val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state)

        // attempt at having a class loader without some of the derived non core types loaded and thus
        // possibly altering how we serialise things
        val altClassLoader = cl()

        val factory2 = SerializerFactoryBuilder.build(AllWhitelist,
                ClassCarpenterImpl(AllWhitelist, altClassLoader)
        )
        factory2.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
        val ser2 = TestSerializationOutput(VERBOSE, factory2).serializeAndReturnSchema(state)

        //  now deserialise those objects
        val factory3 = testDefaultFactory()
        factory3.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
        DeserializationInput(factory3).deserializeAndReturnEnvelope(ser1.obj)

        val factory4 = SerializerFactoryBuilder.build(AllWhitelist,
                ClassCarpenterImpl(AllWhitelist, cl())
        )
        factory4.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
        DeserializationInput(factory4).deserializeAndReturnEnvelope(ser2.obj)
    }

    @Test(timeout=300_000)
	fun fingerprintingDiffers() {
        val state = TransactionState(
                TestContractState(listOf(MINI_CORP_PARTY)),
                "wibble",MINI_CORP_PARTY,
                encumbrance = null,
                constraint = TestAttachmentConstraint())

        val sas = StateAndString(state, "wibble")

        fingerprintingDiffersStrip(sas)
    }

    @Test(timeout=300_000)
	fun fingerprintingDiffersList() {
        val state = TransactionState(
                TestContractState(listOf(MINI_CORP_PARTY)),
                "wibble", MINI_CORP_PARTY,
                encumbrance = null,
                constraint = TestAttachmentConstraint())

        val sas = StateAndString(state, "wibble")

        fingerprintingDiffersStrip(Collections.singletonList(sas))
    }


    //
    // Force object to be serialised as Example<T> and deserialized as Example<?>
    //
    @Test(timeout=300_000)
	fun fingerprintingDiffersListLoaded() {
        //
        // using this wrapper class we force the object to be serialised as
        //      net.corda.core.contracts.TransactionState<T>
        //
        data class TransactionStateWrapper<out T : ContractState> (val o: List<GenericStateAndString<T>>)

        val state = TransactionState<TestContractState> (
                TestContractState(listOf(MINI_CORP_PARTY)),
                "wibble", MINI_CORP_PARTY,
                encumbrance = null,
                constraint = TestAttachmentConstraint())

        val sas = GenericStateAndString(state, "wibble")

        val factory1 = testDefaultFactoryNoEvolution()
        val factory2 = testDefaultFactory()

        factory1.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)
        factory2.register(net.corda.serialization.internal.amqp.custom.PublicKeySerializer)

        val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(
                TransactionStateWrapper(Collections.singletonList(sas)))

        val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)

        assertEquals(sas.ref, des1.obj.o.firstOrNull()?.ref ?: "WILL NOT MATCH")
    }

    @Test(timeout=300_000)
	fun nestedGenericsWithBound() {
        open class BaseState(val a : Int)
        class DState(a: Int) : BaseState(a)
        data class LTransactionState<out T : BaseState> constructor(val data: T)
        data class StateWrapper<out T : BaseState>(val state: LTransactionState<T>)

        val factory1 = testDefaultFactoryNoEvolution()

        val state = LTransactionState(DState(1020304))
        val stateAndString = StateWrapper(state)

        val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)

        //val factory2 = testDefaultFactoryNoEvolution()
        val factory2 = testDefaultFactory()
        val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)

        assertEquals(state.data.a, des1.obj.state.data.a)
    }

    @Test(timeout=300_000)
	fun nestedMultiGenericsWithBound() {
        open class BaseState(val a : Int)
        class DState(a: Int) : BaseState(a)
        class EState(a: Int, val msg: String) : BaseState(a)

        data class LTransactionState<out T1 : BaseState, out T2: BaseState> (val data: T1, val context: T2)
        data class StateWrapper<out T1 : BaseState, out T2: BaseState>(val state: LTransactionState<T1, T2>)

        val factory1 = testDefaultFactoryNoEvolution()

        val state = LTransactionState(DState(1020304), EState(5060708, msg = "thigns"))
        val stateAndString = StateWrapper(state)

        val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)

        //val factory2 = testDefaultFactoryNoEvolution()
        val factory2 = testDefaultFactory()
        val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)

        assertEquals(state.data.a, des1.obj.state.data.a)
        assertEquals(state.context.a,  des1.obj.state.context.a)
    }

    @Test(timeout=300_000)
	fun nestedMultiGenericsNoBound() {
        open class BaseState(val a : Int)
        class DState(a: Int) : BaseState(a)
        class EState(a: Int, val msg: String) : BaseState(a)

        data class LTransactionState<out T1, out T2> (val data: T1, val context: T2)
        data class StateWrapper<out T1, out T2>(val state: LTransactionState<T1, T2>)

        val factory1 = testDefaultFactoryNoEvolution()

        val state = LTransactionState(DState(1020304), EState(5060708, msg = "things"))
        val stateAndString = StateWrapper(state)

        val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)

        //val factory2 = testDefaultFactoryNoEvolution()
        val factory2 = testDefaultFactory()
        val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)

        assertEquals(state.data.a, des1.obj.state.data.a)
        assertEquals(state.context.a, des1.obj.state.context.a)
        assertEquals(state.context.msg, des1.obj.state.context.msg)
    }

    @Test(timeout=300_000)
	fun baseClassInheritedButNotOverriden() {
        val factory1 = testDefaultFactoryNoEvolution()
        val factory2 = testDefaultFactory()

        open class BaseState<T1, T2>(open val a : T1, open val b: T2)
        class DState<T1, T2>(a: T1, b: T2) : BaseState<T1, T2>(a, b)

        val state = DState(100, "hello")
        val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state)
        val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)

        assertEquals(state.a, des1.obj.a)
        assertEquals(state.b, des1.obj.b)

        class DState2<T1, T2, T3>(a: T1, b: T2, val c: T3) : BaseState<T1, T2>(a, b)

        val state2 = DState2(100, "hello", 100L)
        val ser2 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state2)
        val des2 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser2.obj)

        assertEquals(state2.a, des2.obj.a)
        assertEquals(state2.b, des2.obj.b)
        assertEquals(state2.c, des2.obj.c)
    }

    @Test(timeout=300_000)
	fun baseClassInheritedButNotOverridenBounded() {
        val factory1 = testDefaultFactoryNoEvolution()
        val factory2 = testDefaultFactory()

        open class Bound(val a: Int)

        open class BaseState<out T1 : Bound>(open val a: T1)
        class DState<out T1: Bound>(a: T1) : BaseState<T1>(a)

        val state = DState(Bound(100))
        val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state)
        val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)

        assertEquals(state.a.a, des1.obj.a.a)
    }

    @Test(timeout=300_000)
	fun nestedMultiGenericsAtBottomWithBound() {
        open class BaseState<T1, T2>(val a : T1, val b: T2)
        class DState<T1, T2>(a: T1, b: T2) : BaseState<T1, T2>(a, b)
        class EState<T1, T2>(a: T1, b: T2, val c: Long) : BaseState<T1, T2>(a, b)

        data class LTransactionState<T1, T2, T3 : BaseState<T1, T2>, out T4: BaseState<T1, T2>> (val data: T3, val context: T4)
        data class StateWrapper<T1, T2, T3 : BaseState<T1, T2>, out T4: BaseState<T1, T2>>(val state: LTransactionState<T1, T2, T3, T4>)

        val factory1 = testDefaultFactoryNoEvolution()

        val state = LTransactionState(DState(1020304, "Hello"), EState(5060708, "thins", 100L))
        val stateAndString = StateWrapper(state)

        val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)

        //val factory2 = testDefaultFactoryNoEvolution()
        val factory2 = testDefaultFactory()
        val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)

        assertEquals(state.data.a, des1.obj.state.data.a)
        assertEquals(state.context.a,  des1.obj.state.context.a)
    }

    fun implemntsGeneric() {
        open class B<out T>(open val a: T)
        class D(override val a: String) : B<String>(a)

        val factory = testDefaultFactoryNoEvolution()

        val bytes = SerializationOutput(factory).serialize(D("Test"))

        DeserializationInput(factory).deserialize(bytes).apply { assertEquals("Test", this.a) }
    }

    interface implementsGenericInterfaceI<out T> {
        val a: T
    }

    @Test(timeout=300_000)
	fun implemntsGenericInterface() {
        class D(override val a: String) : implementsGenericInterfaceI<String>

        val factory = testDefaultFactoryNoEvolution()

        val bytes = SerializationOutput(factory).serialize(D("Test"))

        DeserializationInput(factory).deserialize(bytes).apply { assertEquals("Test", this.a) }
    }
}
