package de.kuschku.libquassel.util

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

class ExpressionMatchTest {
  @Test
  fun testEmptyPattern() {
    // Empty pattern
    val emptyMatch = ExpressionMatch("", ExpressionMatch.MatchMode.MatchPhrase, false)

    // Assert empty is valid
    assertTrue(emptyMatch.isValid())
    // Assert empty
    assertTrue(emptyMatch.isEmpty())
    // Assert default match fails (same as setting match empty to false)
    assertFalse(emptyMatch.match("something"))
    // Assert match empty succeeds
    assertTrue(emptyMatch.match("something", true))
  }

  @Test
  fun testMatchPhrase() {
    // Simple phrase, case-insensitive
    val simpleMatch = ExpressionMatch("test", ExpressionMatch.MatchMode.MatchPhrase, false)
    // Simple phrase, case-sensitive
    val simpleMatchCS = ExpressionMatch("test", ExpressionMatch.MatchMode.MatchPhrase, true)
    // Phrase with space, case-insensitive
    val simpleMatchSpace = ExpressionMatch(" space ", ExpressionMatch.MatchMode.MatchPhrase, true)
    // Complex phrase
    val complexMatchFull = """^(?:norm|norm\-space|\!norm\-escaped|\\\!slash\-invert|\\\\double|escape\;sep|slash\-end\-split\\|quad\\\\\!noninvert|newline\-split|newline\-split\-slash\\|slash\-at\-end\\)$"""
    val complexMatch = ExpressionMatch(complexMatchFull,
                                       ExpressionMatch.MatchMode.MatchPhrase,
                                       false)

    // Assert valid and not empty
    assertTrue(simpleMatch.isValid())
    assertFalse(simpleMatch.isEmpty())
    assertTrue(simpleMatchCS.isValid())
    assertFalse(simpleMatchCS.isEmpty())
    assertTrue(simpleMatchSpace.isValid())
    assertFalse(simpleMatchSpace.isEmpty())
    assertTrue(complexMatch.isValid())
    assertFalse(complexMatch.isEmpty())

    // Assert match succeeds
    assertTrue(simpleMatch.match("test"))
    assertTrue(simpleMatch.match("other test;"))
    assertTrue(simpleMatchSpace.match(" space "))
    // Assert partial match fails
    assertFalse(simpleMatch.match("testing"))
    assertFalse(simpleMatchSpace.match("space"))
    // Assert unrelated fails
    assertFalse(simpleMatch.match("not above"))

    // Assert case sensitivity followed
    assertFalse(simpleMatch.sourceCaseSensitive)
    assertTrue(simpleMatch.match("TeSt"))
    assertTrue(simpleMatchCS.sourceCaseSensitive)
    assertFalse(simpleMatchCS.match("TeSt"))

    // Assert complex phrases are escaped properly
    assertTrue(complexMatch.match(complexMatchFull))
    assertFalse(complexMatch.match("norm"))
  }

  @Test
  fun matchMultiPhrase() {
    // Simple phrases, case-insensitive
    val simpleMatch = ExpressionMatch("test\nOther ",
                                      ExpressionMatch.MatchMode.MatchMultiPhrase,
                                      false)
    // Simple phrases, case-sensitive
    val simpleMatchCS = ExpressionMatch("test\nOther ",
                                        ExpressionMatch.MatchMode.MatchMultiPhrase,
                                        true)
    // Complex phrases
    val complexMatchFullA = """^(?:norm|norm\-space|\!norm\-escaped|\\\!slash\-invert|\\\\double)|escape\;sep|slash\-end\-split\\|quad\\\\\!noninvert)|newline\-split|newline\-split\-slash\\|slash\-at\-end\\)$"""
    val complexMatchFullB = """^(?:invert|invert\-space)$)$"""
    val complexMatch = ExpressionMatch(complexMatchFullA + "\n" + complexMatchFullB,
                                       ExpressionMatch.MatchMode.MatchMultiPhrase,
                                       false)

    // Assert valid and not empty
    assertTrue(simpleMatch.isValid())
    assertFalse(simpleMatch.isEmpty())
    assertTrue(simpleMatchCS.isValid())
    assertFalse(simpleMatchCS.isEmpty())
    assertTrue(complexMatch.isValid())
    assertFalse(complexMatch.isEmpty())

    // Assert match succeeds
    assertTrue(simpleMatch.match("test"))
    assertTrue(simpleMatch.match("test[suffix]"))
    assertTrue(simpleMatch.match("other test;"))
    assertTrue(simpleMatch.match("Other "))
    assertTrue(simpleMatch.match(".Other !"))
    // Assert partial match fails
    assertFalse(simpleMatch.match("testing"))
    assertFalse(simpleMatch.match("Other!"))
    // Assert unrelated fails
    assertFalse(simpleMatch.match("not above"))

    // Assert case sensitivity followed
    assertFalse(simpleMatch.sourceCaseSensitive)
    assertTrue(simpleMatch.match("TeSt"))
    assertTrue(simpleMatchCS.sourceCaseSensitive)
    assertFalse(simpleMatchCS.match("TeSt"))

    // Assert complex phrases are escaped properly
    assertTrue(complexMatch.match(complexMatchFullA))
    assertTrue(complexMatch.match(complexMatchFullB))
    assertFalse(complexMatch.match("norm"))
    assertFalse(complexMatch.match("invert"))
  }

  @Test
  fun matchWildcard() {
    // Simple wildcard, case-insensitive
    val simpleMatch =
      ExpressionMatch("?test*", ExpressionMatch.MatchMode.MatchWildcard, false)
    // Simple wildcard, case-sensitive
    val simpleMatchCS =
      ExpressionMatch("?test*", ExpressionMatch.MatchMode.MatchWildcard, true)
    // Escaped wildcard, case-insensitive
    val simpleMatchEscape =
      ExpressionMatch("""\?test\*""", ExpressionMatch.MatchMode.MatchWildcard, false)
    // Inverted wildcard, case-insensitive
    val simpleMatchInvert =
      ExpressionMatch("!test*", ExpressionMatch.MatchMode.MatchWildcard, false)
    // Not inverted wildcard, case-insensitive
    val simpleMatchNoInvert =
      ExpressionMatch("""\!test*""", ExpressionMatch.MatchMode.MatchWildcard, false)
    // Not inverted wildcard literal slash, case-insensitive
    val simpleMatchNoInvertSlash =
      ExpressionMatch("""\\!test*""", ExpressionMatch.MatchMode.MatchWildcard, false)
    // Complex wildcard
    val complexMatch =
      ExpressionMatch("""never?gonna*give\*you\?up\\test|y\yeah\\1\\\\2\\\1inval""",
                      ExpressionMatch.MatchMode.MatchWildcard, false)

    // Assert valid and not empty
    assertTrue(simpleMatch.isValid())
    assertFalse(simpleMatch.isEmpty())
    assertTrue(simpleMatchCS.isValid())
    assertFalse(simpleMatchCS.isEmpty())
    assertTrue(simpleMatchEscape.isValid())
    assertFalse(simpleMatchEscape.isEmpty())
    assertTrue(simpleMatchInvert.isValid())
    assertFalse(simpleMatchInvert.isEmpty())
    assertTrue(simpleMatchNoInvert.isValid())
    assertFalse(simpleMatchNoInvert.isEmpty())
    assertTrue(simpleMatchNoInvertSlash.isValid())
    assertFalse(simpleMatchNoInvertSlash.isEmpty())
    assertTrue(complexMatch.isValid())
    assertFalse(complexMatch.isEmpty())

    // Assert match succeeds
    assertTrue(simpleMatch.match("@test"))
    assertTrue(simpleMatch.match("@testing"))
    assertTrue(simpleMatch.match("!test"))
    assertTrue(simpleMatchEscape.match("?test*"))
    assertTrue(simpleMatchInvert.match("atest"))
    assertTrue(simpleMatchNoInvert.match("!test"))
    assertTrue(simpleMatchNoInvertSlash.match("""\!test)"""))
    // Assert partial match fails
    assertFalse(simpleMatch.match("test"))
    // Assert unrelated fails
    assertFalse(simpleMatch.match("not above"))
    // Assert escaped wildcard fails
    assertFalse(simpleMatchEscape.match("@testing"))
    assertFalse(simpleMatchNoInvert.match("test"))
    assertFalse(simpleMatchNoInvert.match("anything"))
    assertFalse(simpleMatchNoInvertSlash.match("!test"))
    assertFalse(simpleMatchNoInvertSlash.match("test"))
    assertFalse(simpleMatchNoInvertSlash.match("anything"))
    // Assert non-inverted fails
    assertFalse(simpleMatchInvert.match("testing"))

    // Assert case sensitivity followed
    assertFalse(simpleMatch.sourceCaseSensitive)
    assertTrue(simpleMatch.match("@TeSt"))
    assertTrue(simpleMatchCS.sourceCaseSensitive)
    assertFalse(simpleMatchCS.match("@TeSt"))

    // Assert complex match
    assertTrue(complexMatch.match("""neverAgonnaBBBgive*you?up\test|yyeah\1\\2\1inval"""))
    // Assert complex not literal match
    assertFalse(complexMatch.match("""never?gonna*give\*you\?up\\test|y\yeah\\1\\\\2\\\1inval"""))
    // Assert complex unrelated not match
    assertFalse(complexMatch.match("other"))
  }

  @Test
  fun matchMultiWildcard() {
    // Simple wildcards, case-insensitive
    val simpleMatch =
      ExpressionMatch("?test*;another?",
                      ExpressionMatch.MatchMode.MatchMultiWildcard, false)
    // Simple wildcards, case-sensitive
    val simpleMatchCS =
      ExpressionMatch("?test*;another?",
                      ExpressionMatch.MatchMode.MatchMultiWildcard, true)
    // Escaped wildcards, case-insensitive
    val simpleMatchEscape =
      ExpressionMatch("""\?test\*\;*thing\*""",
                      ExpressionMatch.MatchMode.MatchMultiWildcard, false)
    // Inverted wildcards, case-insensitive
    val simpleMatchInvert =
      ExpressionMatch("""test*;!testing""",
                      ExpressionMatch.MatchMode.MatchMultiWildcard, false)
    // Implicit wildcards, case-insensitive
    val simpleMatchImplicit =
      ExpressionMatch("""!testing*""",
                      ExpressionMatch.MatchMode.MatchMultiWildcard, false)
    // Complex wildcard
    val complexMatchFull = """norm;!invert; norm-space ; !invert-space ;;!;\!norm-escaped;""" +
                           """\\!slash-invert;\\\\double; escape\;sep;slash-end-split\\;""" +
                           """quad\\\\!noninvert;newline-split""" + "\n" +
                           """newline-split-slash\\""" + "\n" +
                           """slash-at-end\\"""

    // Match normal components
    val complexMatchNormal = listOf(
      """norm""",
      """norm-space""",
      """!norm-escaped""",
      """\!slash-invert""",
      """\\double""",
      """escape;sep""",
      """slash-end-split\""",
      """quad\\!noninvert""",
      """newline-split""",
      """newline-split-slash\""",
      """slash-at-end\"""
    )
    // Match negating components
    val complexMatchInvert = listOf(
      """invert""",
      """invert-space"""
    )
    val complexMatch =
      ExpressionMatch(complexMatchFull, ExpressionMatch.MatchMode.MatchMultiWildcard,
                      false)

    // Assert valid and not empty
    assertTrue(simpleMatch.isValid())
    assertFalse(simpleMatch.isEmpty())
    assertTrue(simpleMatchCS.isValid())
    assertFalse(simpleMatchCS.isEmpty())
    assertTrue(simpleMatchEscape.isValid())
    assertFalse(simpleMatchEscape.isEmpty())
    assertTrue(simpleMatchInvert.isValid())
    assertFalse(simpleMatchInvert.isEmpty())
    assertTrue(simpleMatchImplicit.isValid())
    assertFalse(simpleMatchImplicit.isEmpty())
    assertTrue(complexMatch.isValid())
    assertFalse(complexMatch.isEmpty())

    // Assert match succeeds
    assertTrue(simpleMatch.match("@test"))
    assertTrue(simpleMatch.match("@testing"))
    assertTrue(simpleMatch.match("!test"))
    assertTrue(simpleMatch.match("anotherA"))
    assertTrue(simpleMatchEscape.match("?test*;thing*"))
    assertTrue(simpleMatchEscape.match("?test*;AAAAAthing*"))
    assertTrue(simpleMatchInvert.match("test"))
    assertTrue(simpleMatchInvert.match("testing things"))
    // Assert implicit wildcard succeeds
    assertTrue(simpleMatchImplicit.match("AAAAAA"))
    // Assert partial match fails
    assertFalse(simpleMatch.match("test"))
    assertFalse(simpleMatch.match("another"))
    assertFalse(simpleMatch.match("anotherBB"))
    // Assert unrelated fails
    assertFalse(simpleMatch.match("not above"))
    // Assert escaped wildcard fails
    assertFalse(simpleMatchEscape.match("@testing"))
    // Assert inverted match fails
    assertFalse(simpleMatchInvert.match("testing"))
    assertFalse(simpleMatchImplicit.match("testing"))

    // Assert case sensitivity followed
    assertFalse(simpleMatch.sourceCaseSensitive)
    assertTrue(simpleMatch.match("@TeSt"))
    assertTrue(simpleMatchCS.sourceCaseSensitive)
    assertFalse(simpleMatchCS.match("@TeSt"))

    // Assert complex match
    for (normMatch in complexMatchNormal) {
      // Each normal component should match
      assertTrue(complexMatch.match(normMatch))
    }

    for (invertMatch in complexMatchInvert) {
      // Each invert component should not match
      assertFalse(complexMatch.match(invertMatch))
    }

    // Assert complex not literal match
    assertFalse(complexMatch.match(complexMatchFull))
    // Assert complex unrelated not match
    assertFalse(complexMatch.match("other"))
  }

  @Test
  fun matchRegEx() {
    // Simple regex, case-insensitive
    val simpleMatch =
      ExpressionMatch("""simple.\*escape-match.*""",
                      ExpressionMatch.MatchMode.MatchRegEx, false)
    // Simple regex, case-sensitive
    val simpleMatchCS =
      ExpressionMatch("""simple.\*escape-match.*""",
                      ExpressionMatch.MatchMode.MatchRegEx, true)
    // Inverted regex, case-insensitive
    val simpleMatchInvert =
      ExpressionMatch("""!invert.\*escape-match.*""",
                      ExpressionMatch.MatchMode.MatchRegEx, false)
    // Non-inverted regex, case-insensitive
    val simpleMatchNoInvert =
      ExpressionMatch("""\!simple.\*escape-match.*""",
                      ExpressionMatch.MatchMode.MatchRegEx, false)
    // Non-inverted regex literal slash, case-insensitive
    val simpleMatchNoInvertSlash =
      ExpressionMatch("""\\!simple.\*escape-match.*""",
                      ExpressionMatch.MatchMode.MatchRegEx, false)

    // Assert valid and not empty
    assertTrue(simpleMatch.isValid())
    assertFalse(simpleMatch.isEmpty())
    assertTrue(simpleMatchCS.isValid())
    assertFalse(simpleMatchCS.isEmpty())
    assertTrue(simpleMatchInvert.isValid())
    assertFalse(simpleMatchInvert.isEmpty())
    assertTrue(simpleMatchNoInvert.isValid())
    assertFalse(simpleMatchNoInvert.isEmpty())
    assertTrue(simpleMatchNoInvertSlash.isValid())
    assertFalse(simpleMatchNoInvertSlash.isEmpty())

    // Assert match succeeds
    assertTrue(simpleMatch.match("simpleA*escape-match"))
    assertTrue(simpleMatch.match("simpleA*escape-matchBBBB"))
    assertTrue(simpleMatchInvert.match("not above"))
    assertTrue(simpleMatchNoInvert.match("!simpleA*escape-matchBBBB"))
    assertTrue(simpleMatchNoInvertSlash.match("""\!simpleA*escape-matchBBBB"""))
    // Assert partial match fails
    assertFalse(simpleMatch.match("simpleA*escape-mat"))
    assertFalse(simpleMatch.match("simple*escape-match"))
    // Assert unrelated fails
    assertFalse(simpleMatch.match("not above"))
    // Assert escaped wildcard fails
    assertFalse(simpleMatch.match("simpleABBBBescape-matchBBBB"))
    // Assert inverted fails
    assertFalse(simpleMatchInvert.match("invertA*escape-match"))
    assertFalse(simpleMatchInvert.match("invertA*escape-matchBBBB"))
    assertFalse(simpleMatchNoInvert.match("simpleA*escape-matchBBBB"))
    assertFalse(simpleMatchNoInvert.match("anything"))
    assertFalse(simpleMatchNoInvertSlash.match("!simpleA*escape-matchBBBB"))
    assertFalse(simpleMatchNoInvertSlash.match("anything"))

    // Assert case sensitivity followed
    assertFalse(simpleMatch.sourceCaseSensitive)
    assertTrue(simpleMatch.match("SiMpLEA*escape-MATCH"))
    assertTrue(simpleMatchCS.sourceCaseSensitive)
    assertFalse(simpleMatchCS.match("SiMpLEA*escape-MATCH"))
  }

  @Test
  fun trimMultiWildcardWhitespace() {
    // Patterns
    val patterns = listOf(
      // Literal
      Pair("literal",
           "literal"),
      // Simple semicolon cleanup
      Pair("simple1  ;simple2; simple3 ",
           "simple1; simple2; simple3"),
      // Simple newline cleanup
      Pair("simple1  \nsimple2\n simple3 ",
           "simple1\nsimple2\nsimple3"),
      // Complex cleanup
      Pair(
        """norm; norm-space ; newline-space """ + "\n" +
        """ ;escape \; sep ; slash-end-split\\; quad\\\\norm; newline-split-slash\\""" + "\n" +
        """slash-at-end\\""",
        """norm; norm-space; newline-space""" + "\n" +
        """escape \; sep; slash-end-split\\; quad\\\\norm; newline-split-slash\\""" + "\n" +
        """slash-at-end\\"""
      )
    )

    // Check every source string...
    for (patternPair in patterns) {
      // Run transformation
      val result = ExpressionMatch.trimMultiWildcardWhitespace(patternPair.first)
      // Assert that source trims into expected pattern
      assertEquals(patternPair.second, result)
      // Assert that re-trimming expected pattern gives the same result
      assertEquals(ExpressionMatch.trimMultiWildcardWhitespace(result), result)
    }
  }
}