Kotlin basics

import org.junit.Assert
import org.junit.Test

class MyTest {

    // immutable data class
    data class User(
            val firstname: String,
            val lastname: String
    )

    // immutable data class with optional values and nullable value
    data class Customer(
            val name: String = "", // name cannot be null
            val address: String? = "" // address can also be null
    )

    // null checks
    // type inference
    @Test
    fun test_null_safety() {
        val customer = Customer(name = "robbert", address = null) // the type of customer is inferred
        //println(robbert.address.length)    // this does not compile because we did no null check
        //println(customer.address!!.length) // use double bang to force length call without null check (this will give
        // a KotlinNullPointerException)
        println(customer.address?.length)    // print "null" because address is null
        println(customer.address?.length ?: 0) // print 0 because address is null
    }

    // elvis operator in filter
    // lambda
    // classes in functions
    @Test
    fun test_elvis() {
        data class Adress(val place: String?, val street: String?, val number: String?)
        data class Contact(val name: String, val adress: Adress?)

        val contacten = listOf(Contact("Robbert", Adress("Alkmaar", null, null)))

        val hasContactsFromAlkmaar =
                contacten.filter { it.adress?.place?.equals("Alkmaar") ?: false }
                        .isNotEmpty()
        Assert.assertTrue(hasContactsFromAlkmaar)
    }

    // immutable en mutable collecties
    @Test
    fun test_collections() {
        val immutableList = listOf("Robbert", "Jack")
        val mutableList = immutableList.toMutableList() // create new mutable list
        mutableList.add("Karen")
        println(mutableList)   // print: [Robbert, Jack, Karen]
        println(immutableList) // print: [Robbert, Jack]
    }


    // builder
    // toString
    @Test
    fun test_builder() {
        val robbert = User("Robbert", "vd Zon")
        val jack = robbert.copy(firstname = "Jack") //  copy all fields of robbert but overwrite firstname
        println(robbert) // prints:  User(firstname=Robbert, lastname=vd Zon)
        println(jack)    // prints:  User(firstname=Jack, lastname=vd Zon)
    }

    // functional expression (an if statement returns a value)
    @Test
    fun test_functionele_expressie() {
        val value = if (true) 1 else 2;
        Assert.assertEquals(value, 1)
    }

    // String intepolation
    @Test
    fun test_stringinterpolation() {
        val name = "Robbert"
        println("hello $name!")
    }

    // ranges
    @Test
    fun test_ranges() {
        val nr = 5
        if (nr in 1..10) print(nr) // equivalent of 1 <= i && i  "developer!"
            is Person.Painter -> "painter"
        // this works because Kotlin knows that Developer and Painter are the only 2 possible subclasses of Person
        }

        val p: Person = Person.Developer(1, "robbert", "Kotlin")
        println(getType(p))
    }


    // tail recursion
    @Test
    fun test_tailrecursion() {
        fun factorial(n: Int): Int {
            tailrec fun factorial(accumulator: Int, n: Int): Int = if (n == 1) accumulator else factorial(accumulator * n, n - 1)
            return factorial(1, n)
        }

        println(factorial(10))
    }

    // create a DSL
    @Test
    fun test_dsl() {
        class div(p: () -> Unit)
        class html(div: (d: div) -> Unit)

        val htmlCode =
                html {
                    div {

                    }
                }

        println(htmlCode)
    }

    // static methods via companion objects
    class MyClass {
        companion object Factory {
            fun create(): MyClass = MyClass()
        }
    }

    @Test
    fun test_companion_objects() {
        val instance = MyClass.create()
    }


}