package io.gitlab.arturbosch.detekt.rules.style

import io.gitlab.arturbosch.detekt.rules.KotlinCoreEnvironmentTest
import io.gitlab.arturbosch.detekt.test.assertThat
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test

@KotlinCoreEnvironmentTest
class UseIsNullOrEmptySpec(val env: KotlinCoreEnvironment) {
    val subject = UseIsNullOrEmpty()

    @Nested
    inner class `report UseIsNullOrEmpty rule` {
        @Nested
        inner class `List` {
            @Test
            fun `null or isEmpty()`() {
                val code = """
                    fun test(x: List<Int>?) {
                        if (x == null || x.isEmpty()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
                assertThat(findings).hasSourceLocation(2, 9)
                assertThat(findings[0]).hasMessage(
                    "This 'x == null || x.isEmpty()' can be replaced with 'isNullOrEmpty()' call"
                )
            }

            @Test
            fun `null or count() == 0`() {
                val code = """
                    fun test(x: List<Int>?) {
                        if (x == null || x.count() == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `null or size == 0`() {
                val code = """
                    fun test(x: List<Int>?) {
                        if (x == null || x.size == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `flipped null check`() {
                val code = """
                    fun test(x: List<Int>?) {
                        if (null == x || x.isEmpty()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `flipped count check`() {
                val code = """
                    fun test(x: List<Int>?) {
                        if (x == null || 0 == x.count()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `flipped size check`() {
                val code = """
                    fun test(x: List<Int>?) {
                        if (x == null || 0 == x.size) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }
        }

        @Nested
        inner class `Set` {
            @Test
            fun `null or isEmpty()`() {
                val code = """
                    fun test(x: Set<Int>?) {
                        if (x == null || x.isEmpty()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `null or count() == 0`() {
                val code = """
                    fun test(x: Set<Int>?) {
                        if (x == null || x.count() == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `null or size == 0`() {
                val code = """
                    fun test(x: Set<Int>?) {
                        if (x == null || x.size == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }
        }

        @Nested
        inner class `Collection` {
            @Test
            fun `null or isEmpty()`() {
                val code = """
                    fun test(x: Collection<Int>?) {
                        if (x == null || x.isEmpty()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `null or count() == 0`() {
                val code = """
                    fun test(x: Collection<Int>?) {
                        if (x == null || x.count() == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `null or size == 0`() {
                val code = """
                    fun test(x: Collection<Int>?) {
                        if (x == null || x.size == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }
        }

        @Nested
        inner class `Map` {
            @Test
            fun `null or isEmpty()`() {
                val code = """
                    fun test(x: Map<Int, String>?) {
                        if (x == null || x.isEmpty()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `null or count() == 0`() {
                val code = """
                    fun test(x: Map<Int, String>?) {
                        if (x == null || x.count() == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `null or size == 0`() {
                val code = """
                    fun test(x: Map<Int, String>?) {
                        if (x == null || x.size == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }
        }

        @Nested
        inner class `Array` {
            @Test
            fun `null or isEmpty()`() {
                val code = """
                    fun test(x: Array<Int>?) {
                        if (x == null || x.isEmpty()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `null or count() == 0`() {
                val code = """
                    fun test(x: Array<Int>?) {
                        if (x == null || x.count() == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `null or size == 0`() {
                val code = """
                    fun test(x: Array<Int>?) {
                        if (x == null || x.size == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }
        }

        @Nested
        inner class `String` {
            @Test
            fun `null or isEmpty()`() {
                val code = """
                    fun test(x: String?) {
                        if (x == null || x.isEmpty()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `null or count() == 0`() {
                val code = """
                    fun test(x: String?) {
                        if (x == null || x.count() == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `null or length == 0`() {
                val code = """
                    fun test(x: String?) {
                        if (x == null || x.length == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }

            @Test
            fun `null or equal empty string`() {
                val code = """
                    fun test(x: String?) {
                        if (x == null || x == "") return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }
        }

        @Nested
        inner class `MutableList` {
            @Test
            fun `null or isEmpty()`() {
                val code = """
                    fun test(x: MutableList<Int>?) {
                        if (x == null || x.isEmpty()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }
        }

        @Nested
        inner class `MutableSet` {
            @Test
            fun `null or isEmpty()`() {
                val code = """
                    fun test(x: MutableSet<Int>?) {
                        if (x == null || x.isEmpty()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }
        }

        @Nested
        inner class `MutableCollection` {
            @Test
            fun `null or isEmpty()`() {
                val code = """
                    fun test(x: MutableCollection<Int>?) {
                        if (x == null || x.isEmpty()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }
        }

        @Nested
        inner class `MutableMap` {
            @Test
            fun `null or isEmpty()`() {
                val code = """
                    fun test(x: MutableMap<Int, String>?) {
                        if (x == null || x.isEmpty()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).hasSize(1)
            }
        }
    }

    @Nested
    inner class `does not report UseIsNullOrEmpty rule` {
        @Nested
        inner class `IntArray` {
            @Test
            fun `null or isEmpty()`() {
                val code = """
                    fun test(x: IntArray?) {
                        if (x == null || x.isEmpty()) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).isEmpty()
            }

            @Test
            fun `null or count() == 0`() {
                val code = """
                    fun test(x: IntArray?) {
                        if (x == null || x.count() == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).isEmpty()
            }

            @Test
            fun `null or size == 0`() {
                val code = """
                    fun test(x: IntArray?) {
                        if (x == null || x.size == 0) return
                    }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).isEmpty()
            }
        }

        @Nested
        inner class `Sequence` {
            @Test
            fun `null or count() == 0`() {
                val code = """
                fun test(x: Sequence<Int>?) {
                    if (x == null || x.count() == 0) return
                }
                """
                val findings = subject.compileAndLintWithContext(env, code)
                assertThat(findings).isEmpty()
            }
        }

        @Test
        fun `different variables`() {
            val code = """
                fun test(x: List<Int>?, y: List<Int>) {
                    if (x == null || y.isEmpty()) return
                }
            """
            val findings = subject.compileAndLintWithContext(env, code)
            assertThat(findings).isEmpty()
        }

        @Test
        fun `not null check`() {
            val code = """
                fun test(x: List<Int>?) {
                    if (x != null && x.isEmpty()) return
                }
            """
            val findings = subject.compileAndLintWithContext(env, code)
            assertThat(findings).isEmpty()
        }

        @Test
        fun `not size zero check`() {
            val code = """
                fun test(x: List<Int>?) {
                    if (x == null || x.count() == 1) return
                }
            """
            val findings = subject.compileAndLintWithContext(env, code)
            assertThat(findings).isEmpty()
        }

        @Test
        fun `not null`() {
            val code = """
                fun test(x: List<Int>) {
                    if (x == null || x.isEmpty()) return
                }
            """
            val findings = subject.compileAndLintWithContext(env, code)
            assertThat(findings).isEmpty()
        }

        @Test
        fun `var class member`() {
            val code = """
                class Test {
                    var x: List<Int>? = null
                
                    fun test() {
                        if (x == null || x?.count() == 0) return
                    }
                }
            """
            val findings = subject.compileAndLintWithContext(env, code)
            assertThat(findings).isEmpty()
        }
    }
}
