// RUN: %target-run-simple-swift %t
// REQUIRES: executable_test
// UNSUPPORTED: freestanding

import StdlibUnittest

var UnicodeScalarPropertiesTests = TestSuite("UnicodeScalarPropertiesTests")

func us(_ scalar: Unicode.Scalar) -> Unicode.Scalar { return scalar }

UnicodeScalarPropertiesTests.test("properties.booleanProperties") {
  func expectBooleanProperty(
    _ keyPath: KeyPath<Unicode.Scalar.Properties, Bool>,
    trueFor trueScalar: Unicode.Scalar,
    falseFor falseScalar: Unicode.Scalar,
    _ message: String = "",
    file: String = #file,
    line: UInt = #line
  ) {
    expectTrue(
      trueScalar.properties[keyPath: keyPath], message, file: file, line: line)
    expectFalse(
      falseScalar.properties[keyPath: keyPath], message, file: file, line: line)
  }

  // Some soundness checks for basic properties. Not intended to be comprehensive.
  expectBooleanProperty(\.isAlphabetic, trueFor: "A", falseFor: "5")
  expectBooleanProperty(\.isASCIIHexDigit, trueFor: "F", falseFor: "G")
  // U+200E LEFT-TO-RIGHT MARK
  expectBooleanProperty(\.isBidiControl, trueFor: "\u{200E}", falseFor: "A")
  expectBooleanProperty(\.isBidiMirrored, trueFor: "(", falseFor: "A")
  expectBooleanProperty(\.isDash, trueFor: "-", falseFor: "A")
  // U+00AD SOFT HYPHEN
  expectBooleanProperty(\.isDefaultIgnorableCodePoint, trueFor: "\u{00AD}", falseFor: "A")
  // U+0149 LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
  expectBooleanProperty(\.isDeprecated, trueFor: "\u{0149}", falseFor: "A")
  expectBooleanProperty(\.isDiacritic, trueFor: "^", falseFor: "A")
  // U+00B7 MIDDLE DOT
  expectBooleanProperty(\.isExtender, trueFor: "\u{00B7}", falseFor: "A")
  // U+0340 COMBINING GRAVE TONE MARK
  expectBooleanProperty(\.isFullCompositionExclusion, trueFor: "\u{0340}", falseFor: "A")
  // U+0300 COMBINING GRAVE ACCENT
  expectBooleanProperty(\.isGraphemeBase, trueFor: "A", falseFor: "\u{0300}")
  expectBooleanProperty(\.isGraphemeExtend, trueFor: "\u{0300}", falseFor: "A")
  // U+FF21 FULLWIDTH LATIN CAPITAL LETTER A
  expectBooleanProperty(\.isHexDigit, trueFor: "\u{FF21}", falseFor: "G")
  expectBooleanProperty(\.isIDContinue, trueFor: "0", falseFor: "!")
  expectBooleanProperty(\.isIDStart, trueFor: "A", falseFor: "!")
  // U+3400 CJK UNIFIED IDEOGRAPH-3400
  expectBooleanProperty(\.isIdeographic, trueFor: "\u{3400}", falseFor: "A")
  // U+2FF0 IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT
  expectBooleanProperty(\.isIDSBinaryOperator, trueFor: "\u{2FF0}", falseFor: "A")
  // U+2FF2 IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO MIDDLE AND RIGHT
  expectBooleanProperty(\.isIDSTrinaryOperator, trueFor: "\u{2FF2}", falseFor: "A")
  // U+200C ZERO WIDTH NON-JOINER
  expectBooleanProperty(\.isJoinControl, trueFor: "\u{200C}", falseFor: "A")
  // U+0E40 THAI CHARACTER SARA E
  expectBooleanProperty(\.isLogicalOrderException, trueFor: "\u{0E40}", falseFor: "A")
  expectBooleanProperty(\.isLowercase, trueFor: "a", falseFor: "A")
  expectBooleanProperty(\.isMath, trueFor: "+", falseFor: "A")
  expectBooleanProperty(\.isNoncharacterCodePoint, trueFor: Unicode.Scalar(0xFDD0)!, falseFor: "A")
  expectBooleanProperty(\.isQuotationMark, trueFor: "'", falseFor: "A")
  // U+2E80 CJK RADICAL REPEAT
  expectBooleanProperty(\.isRadical, trueFor: "\u{2E80}", falseFor: "A")
  expectBooleanProperty(\.isSoftDotted, trueFor: "i", falseFor: "A")
  expectBooleanProperty(\.isTerminalPunctuation, trueFor: "!", falseFor: "A")
  expectBooleanProperty(\.isUnifiedIdeograph, trueFor: "\u{3400}", falseFor: "A")
  expectBooleanProperty(\.isUppercase, trueFor: "A", falseFor: "a")
  expectBooleanProperty(\.isWhitespace, trueFor: " ", falseFor: "A")
  expectBooleanProperty(\.isXIDContinue, trueFor: "0", falseFor: "!")
  expectBooleanProperty(\.isXIDStart, trueFor: "A", falseFor: "!")
  expectBooleanProperty(\.isSentenceTerminal, trueFor: "!", falseFor: "A")
  // U+FE00 VARIATION SELECTOR-1
  expectBooleanProperty(\.isVariationSelector, trueFor: "\u{FE00}", falseFor: "A")
  expectBooleanProperty(\.isPatternSyntax, trueFor: "!", falseFor: "A")
  expectBooleanProperty(\.isPatternWhitespace, trueFor: " ", falseFor: "A")
  expectBooleanProperty(\.isCased, trueFor: "A", falseFor: "!")
  expectBooleanProperty(\.isCaseIgnorable, trueFor: "'", falseFor: "A")
  expectBooleanProperty(\.changesWhenLowercased, trueFor: "A", falseFor: "a")
  expectBooleanProperty(\.changesWhenUppercased, trueFor: "a", falseFor: "A")
  expectBooleanProperty(\.changesWhenTitlecased, trueFor: "a", falseFor: "A")
  expectBooleanProperty(\.changesWhenCaseFolded, trueFor: "A", falseFor: "!")
  expectBooleanProperty(\.changesWhenCaseMapped, trueFor: "A", falseFor: "!")
  expectBooleanProperty(\.changesWhenNFKCCaseFolded, trueFor: "A", falseFor: "!")

#if canImport(Darwin)
  if #available(macOS 10.12.2, iOS 10.2, tvOS 10.1, watchOS 3.1.1, *) {
    // U+2708 AIRPLANE
    expectBooleanProperty(\.isEmoji, trueFor: "\u{2708}", falseFor: "A")
    // U+231A WATCH
    expectBooleanProperty(
      \.isEmojiPresentation, trueFor: "\u{231A}", falseFor: "A")
    // U+1F3FD EMOJI MODIFIER FITZPATRICK TYPE-4
    expectBooleanProperty(
      \.isEmojiModifier, trueFor: "\u{1F3FD}", falseFor: "A")
    // U+270B RAISED HAND
    expectBooleanProperty(
      \.isEmojiModifierBase, trueFor: "\u{270B}", falseFor: "A")
  }
#endif
}

UnicodeScalarPropertiesTests.test("properties.lowercaseMapping") {
  expectEqual("2", us("2").properties.lowercaseMapping)
  expectEqual("i", us("I").properties.lowercaseMapping)
  expectEqual("i\u{0307}", us("\u{0130}").properties.lowercaseMapping)
  // There are currently no lowercase mappings that produce multiple graphemes.
}

UnicodeScalarPropertiesTests.test("uppercaseMapping") {
  expectEqual("2", us("2").properties.uppercaseMapping)
  expectEqual("I", us("i").properties.uppercaseMapping)
  expectEqual("\u{02BC}N", us("\u{0149}").properties.uppercaseMapping)  // multiple scalars
  expectEqual("SS", us("ß").properties.uppercaseMapping)  // multiple graphemes
  expectEqual("FFL", us("\u{FB04}").properties.uppercaseMapping)  // multiple graphemes
}

UnicodeScalarPropertiesTests.test("titlecaseMapping") {
  expectEqual("2", us("2").properties.titlecaseMapping)
  expectEqual("I", us("i").properties.titlecaseMapping)
  expectEqual("\u{02BC}N", us("\u{0149}").properties.titlecaseMapping)  // multiple scalars
  expectEqual("Ff", us("\u{FB00}").properties.titlecaseMapping)  // multiple graphemes
  expectEqual("Ffl", us("\u{FB04}").properties.titlecaseMapping)  // multiple graphemes
}

UnicodeScalarPropertiesTests.test("properties.name") {
  // A scalar with no assigned name returns nil.
  expectNil(us("\u{0000}").properties.name)

  // Try some results that should fit in small strings.
  expectEqual("CARE OF", us("\u{2105}").properties.name)
  expectEqual("ACCOUNT OF", us("\u{2100}").properties.name)

  // Try some results that need heap-allocated strings.
  expectEqual("LATIN SMALL LETTER A", us("\u{0061}").properties.name)
  expectEqual(
    "COMBINING LEFTWARDS HARPOON WITH BARB DOWNWARDS",
    us("\u{20ED}").properties.name)
  expectEqual(
    "PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET",  // [sic]
    us("\u{FE18}").properties.name)

  // Try some boundary cases around the length limit of a SmallUTF8String.
  expectEqual("COMBINING HORN", us("\u{031B}").properties.name)  // 14
  expectEqual("COMBINING TILDE", us("\u{0303}").properties.name)  // 15
  expectEqual("COMBINING MACRON", us("\u{0304}").properties.name)  // 16
}

UnicodeScalarPropertiesTests.test("properties.nameAlias") {
  // A scalar with no assigned alias returns nil.
  expectNil(us("\u{0000}").properties.nameAlias)
  expectNil(us("\u{0040}").properties.nameAlias)

  // Try some aliases of varying lengths, getting some small and large string
  // coverage.
  expectEqual("LAO LETTER RO", us("\u{0EA3}").properties.nameAlias)
  expectEqual("MICR DASH SYMBOL", us("\u{2449}").properties.nameAlias)
  expectEqual(
    "PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRACKET",
    us("\u{FE18}").properties.nameAlias)
}

UnicodeScalarPropertiesTests.test("properties.age") {
  func expectAgeEqual(
    _ expected: (Int, Int),
    _ scalar: Unicode.Scalar,
    _ message: String = "",
    file: String = #file,
    line: UInt = #line
  ) {
    expectNotNil(scalar.properties.age, message, file: file, line: line)
    expectEqual(
      expected, scalar.properties.age!, message, file: file, line: line)
  }

  expectNil(us("\u{0378}").properties.age)
  expectAgeEqual((1, 1), "\u{0040}")
  expectAgeEqual((3, 0), "\u{3500}")
  expectAgeEqual((3, 2), "\u{FE00}")
  expectAgeEqual((6, 1), "\u{11180}")
}

UnicodeScalarPropertiesTests.test("properties.generalCategory") {
  expectEqual(.uppercaseLetter, us("A").properties.generalCategory)
  expectEqual(.lowercaseLetter, us("a").properties.generalCategory)
  // U+01C8 LATIN CAPITAL LETTER L WITH SMALL LETTER J
  expectEqual(.titlecaseLetter, us("\u{01C8}").properties.generalCategory)
  // U+02B0 MODIFIER LETTER SMALL H
  expectEqual(.modifierLetter, us("\u{02B0}").properties.generalCategory)
  // U+00AA FEMININE ORDINAL INDICATOR
  expectEqual(.otherLetter, us("\u{00AA}").properties.generalCategory)

  // U+0300 COMBINING GRAVE ACCENT
  expectEqual(.nonspacingMark, us("\u{0300}").properties.generalCategory)
  // U+20DD COMBINING ENCLOSING CIRCLE
  expectEqual(.enclosingMark, us("\u{20DD}").properties.generalCategory)

  expectEqual(.decimalNumber, us("0").properties.generalCategory)
  // U+2160 ROMAN NUMERAL ONE
  expectEqual(.letterNumber, us("\u{2160}").properties.generalCategory)
  // U+00B2 SUPERSCRIPT TWO
  expectEqual(.otherNumber, us("\u{00B2}").properties.generalCategory)

  expectEqual(.connectorPunctuation, us("_").properties.generalCategory)
  expectEqual(.dashPunctuation, us("-").properties.generalCategory)
  expectEqual(.openPunctuation, us("(").properties.generalCategory)
  expectEqual(.closePunctuation, us(")").properties.generalCategory)
  // U+201C LEFT DOUBLE QUOTATION MARK
  expectEqual(.initialPunctuation, us("\u{201C}").properties.generalCategory)
  // U+201D RIGHT DOUBLE QUOTATION MARK
  expectEqual(.finalPunctuation, us("\u{201D}").properties.generalCategory)
  expectEqual(.otherPunctuation, us("!").properties.generalCategory)

  expectEqual(.mathSymbol, us("+").properties.generalCategory)
  expectEqual(.currencySymbol, us("$").properties.generalCategory)
  expectEqual(.modifierSymbol, us("^").properties.generalCategory)
  // U+00AE REGISTERED SIGN
  expectEqual(.otherSymbol, us("\u{00AE}").properties.generalCategory)

  expectEqual(.spaceSeparator, us(" ").properties.generalCategory)
  // U+2028 LINE SEPARATOR
  expectEqual(.lineSeparator, us("\u{2028}").properties.generalCategory)
  // U+2029 PARAGRAPH SEPARATOR
  expectEqual(.paragraphSeparator, us("\u{2029}").properties.generalCategory)

  expectEqual(.control, us("\u{0000}").properties.generalCategory)
  // U+200B ZERO WIDTH SPACE
  expectEqual(.format, us("\u{200B}").properties.generalCategory)
  expectEqual(.privateUse, us("\u{E000}").properties.generalCategory)
  expectEqual(.unassigned, us("\u{FFFE}").properties.generalCategory)
  // .surrogate cannot be tested because Swift does not permit creating
  // Unicode.Scalars for code points U+D800-DFFF.
}

UnicodeScalarPropertiesTests.test("properties.canonicalCombiningClass") {
  expectEqual(.notReordered, us("A").properties.canonicalCombiningClass)
  expectEqual(.above, us("\u{0301}").properties.canonicalCombiningClass)
  expectEqual(.below, us("\u{0316}").properties.canonicalCombiningClass)
}

UnicodeScalarPropertiesTests.test("properties.numericTypeAndValue") {
  expectNil(us("X").properties.numericType)
  expectNil(us("X").properties.numericValue)

  expectEqual(.decimal, us("3").properties.numericType)
  expectEqual(3, us("3").properties.numericValue)

  // U+00BC VULGAR FRACTION ONE QUARTER
  expectEqual(.numeric, us("\u{00BC}").properties.numericType)
  expectEqual(0.25, us("\u{00BC}").properties.numericValue)

  // U+2463 CIRCLED DIGIT FOUR
  expectEqual(.digit, us("\u{2463}").properties.numericType)
  expectEqual(4, us("\u{2463}").properties.numericValue)
}

runAllTests()
