/**
 * Test number format validation.
 */
USING OpenEdge.Core.Assert.

ROUTINE-LEVEL ON ERROR UNDO, THROW.

CLASS unittests.NumberFormatValidationTest:

   /* Test valid span coverage examples. */
   @Test.
   METHOD PUBLIC VOID testValidSpanCoverage():
       STRING(0, "abc>>9.<xy"). /* 2 '>' >= 1 '<' */
       STRING(0, "abc>,9.<<xy"). /* 2 ('>' + ',') >= 2 '<' */
   END METHOD.

   /* Test invalid span coverage examples. */
   @Test.
   METHOD PUBLIC VOID testInvalidSpanExceeds():
       testError22("abc>9.<<xy", 8). /* 1 '>' < 2 '<' */
       testError22("abc>9.<,xy", 8). /* '>' plus commas not enough */
       testError22("abc9.<,,<xy", 6). /* 0 '>' vs 4 '<' */
       testError22("9.<", 3). /* fractional less-than requires greater-than */
       testError22(">9.<,", 5). /* commas not allowed in right balance span */
       testError22(">>9.<,<", 7). /* right balance exceeds spare '>' */
       testError22(">9.<<", 5). /* right balance exceeds spare '>' */
   END METHOD.

   /* Test right balance must not begin with a comma. */
   @Test.
   METHOD PUBLIC VOID testRightBalanceMustNotBeginWithComma():
       testError22(">9.,<", 4). /* right balance span must not begin with a comma */
   END METHOD.

   /* Test invalid suffix starting with dot. */
   @Test.
   METHOD PUBLIC VOID testInvalidSuffixStartsWithDot():
       testError22("9.99.", 5). /* suffix cannot start with '.' */
       testError22("9.99.abc", 5). /* suffix cannot start with '.' */
   END METHOD.

   /* Test invalid dot in suffix. */
   @Test.
   METHOD PUBLIC VOID testInvalidDotInSuffix():
       testError22("abc.,9", 5). /* dot not allowed in suffix */
   END METHOD.

   /* Test invalid comma in suffix. */
   @Test.
   METHOD PUBLIC VOID testInvalidDotThenCommaInSuffix():
       testError22(".abc,,9xy", 4). /* comma not allowed in suffix */
   END METHOD.

   /* Test valid extra dot inside suffix. */
   @Test.
   METHOD PUBLIC VOID testValidExtraDotInsideSuffix():
       STRING(0, "9.99a.b.c"). /* dots allowed inside non-empty suffix */
   END METHOD.

   /* Test invalid comma after suffix. */
   @Test.
   METHOD PUBLIC VOID testInvalidCommaAfterSuffix():
       testError22(">9.<a,", 5). /* commas not allowed after suffix */
   END METHOD.

   /* Test invalid multiple signs. */
   @Test.
   METHOD PUBLIC VOID testInvalidMultipleSigns():
       testError22("++9xy", 2). /* only one sign specification allowed */
       testError22("abc+9-xy", 6). /* only one sign specification allowed */
       testError22("ab(c+9)xy", 5). /* parenthesis sign cannot mix with '+' */
       testError22("+9-", 3). /* cannot have signs before and after number */
       testError22("a-9-", 4). /* second sign not allowed after number */
       testError22("a+9-", 4). /* second sign not allowed after number */
       testError22("a(9-", 4). /* cannot mix '(' and trailing '-' */
       testError22("(+", 2). /* only one sign specification allowed */
       testError22("()+", 3). /* sign after number requires digits before */
       testError22("()a+", 4). /* sign after number requires digits before */
       testError22("bc+9)", 5). /* right ')' requires '(' in prefix */
       testError22("b(c9+", 5). /* right '+' not allowed with '(' */
       testError22("+(b9", 2). /* only one sign specification allowed */
       testError22("(b9)+", 4). /* cannot place '+' after ')' */
       testError22("(9)+", 3). /* cannot place '+' after ')' */
       testError22("(9+", 3). /* sign after number not allowed with '(' */
       testError22("(9dr", 3). /* non-digit after number where sign expected */
       testError22("+(9)", 2). /* cannot mix '+' and parenthesis */
       testError22("b(c+9", 4). /* cannot mix '(' and '+' before number */
       testError22("(abc9-xy", 6). /* cannot mix '(' with '-' after number */
       testError22("--", 2). /* sign must not follow another sign */
       testError22("+-", 2). /* sign must not follow another sign */
   END METHOD.

   /* Test valid sign positions. */
   @Test.
   METHOD PUBLIC VOID testValidSignPositions():
       STRING(0, "abc+9xy"). /* sign before number */
       STRING(0, "abc9-xy"). /* sign after number */
   END METHOD.

   /* Test valid parenthesis sign cases. */
   @Test.
   METHOD PUBLIC VOID testValidParenthesisSign():
       STRING(0, "((abc9)xy"). /* suffix present with ')' */
       STRING(0, "((abc9"). /* no suffix, ')' optional */
   END METHOD.

   /* Test invalid parenthesis cases. */
   @Test.
   METHOD PUBLIC VOID testInvalidParenthesisCases():
       testError22("9)xy", 2). /* unmatched ')' sign spec */
       testError22("(9-", 3). /* cannot mix '(' with '-' after number */
       testError22("(9xy", 3). /* missing ')' before suffix */
   END METHOD.

   /* Test valid multiple parentheses in prefix. */
   @Test.
   METHOD PUBLIC VOID testValidMultipleParensInPrefix():
       STRING(0, "((abc9)xy"). /* multiple '(' with matching ')' */
   END METHOD.

   /* Test extra closing character. */
   @Test.
   METHOD PUBLIC VOID testExtraClosingCharacter():
       testError22("a(b9))", 6). /* extra ')' not allowed */
   END METHOD.

   /* Test invalid fraction start. */
   @Test.
   METHOD PUBLIC VOID testInvalidFractionStart():
       testError22("9.,", 3). /* fractional part cannot start with ',' */
       testError22("abc.Z", 5). /* fractional part cannot start with 'Z' */
   END METHOD.

   /* Test missing number part. */
   @Test.
   METHOD PUBLIC VOID testMissingNumberPart():
       testError22("+b", 2). /* missing number part */
   END METHOD.

   /* Test invalid no placeholders. */
   @Test.
   METHOD PUBLIC VOID testInvalidNoPlaceholders():
       testError148("abc.xy"). /* no digit placeholders */
       testError148("abcxy"). /* no digit placeholders */
       testError148("abc,,xy"). /* no digit placeholders */
   END METHOD.

   /* Test valid comma at end of fraction. */
   @Test.
   METHOD PUBLIC VOID testValidCommaAtEndOfFraction():
       STRING(0, "99.99,"). /* trailing comma in fraction */
   END METHOD.

   /* Test dot must be followed by nine or less than. */
   @Test.
   METHOD PUBLIC VOID testDotMustBeFollowedByNineOrLessThan():
       testError22("9.,", 3). /* fractional part must start with '9' or '<' */
       testError22("9.*", 3). /* fractional part must start with '9' or '<' */
       testError22("9.z", 3). /* fractional part must start with '9' or '<' */
       testError22("9.Z", 3). /* fractional part must start with '9' or '<' */
   END METHOD.

   /* Test illegal right user part immediate. */
   @Test.
   METHOD PUBLIC VOID testIllegalRightUserPartImmediate():
       testError22("9a>", 2). /* illegal character in suffix */
   END METHOD.

   /* Test illegal right user part common. */
   @Test.
   METHOD PUBLIC VOID testIllegalRightUserPartCommon():
       testError22("9a,", 2). /* comma not allowed in suffix */
       testError22("9a*", 2). /* asterisk not allowed in suffix */
       testError22("9az", 2). /* 'z' not allowed in suffix */
       testError22("9aZ", 2). /* 'Z' not allowed in suffix */
       testError22("9a+", 2). /* '+' not allowed in suffix */
       testError22("9a-", 2). /* '-' not allowed in suffix */
       testError22("9a>", 2). /* '>' not allowed in suffix */
       testError22("9a9", 2). /* digit not allowed in suffix */
   END METHOD.

   /* Test illegal right user part digits and parentheses. */
   @Test.
   METHOD PUBLIC VOID testIllegalRightUserPartDigitsAndParens():
       testError22("9a0", 3). /* digit not allowed in suffix */
       testError22("9a1", 3). /* digit not allowed in suffix */
       testError22("9a2", 3). /* digit not allowed in suffix */
       testError22("9a3", 3). /* digit not allowed in suffix */
       testError22("9a4", 3). /* digit not allowed in suffix */
       testError22("9a5", 3). /* digit not allowed in suffix */
       testError22("9a6", 3). /* digit not allowed in suffix */
       testError22("9a7", 3). /* digit not allowed in suffix */
       testError22("9a8", 3). /* digit not allowed in suffix */
       testError22("9a(", 3). /* '(' not allowed in suffix */
       testError22("9a)", 3). /* ')' not allowed in suffix */
   END METHOD.

   /* Test illegal right user part first and middle. */
   @Test.
   METHOD PUBLIC VOID testIllegalRightUserPartFirstAndMiddle():
       testError22("9-z", 3). /* hyphen not allowed in suffix */
       testError22("9abcdz0123456789)(", 5). /* first illegal character in suffix */
   END METHOD.

   /* Test illegal right user part near balance. */
   @Test.
   METHOD PUBLIC VOID testIllegalRightUserPartNearBalance():
       testError22(">>9.< +", 6). /* space not allowed inside balance/right user section */
   END METHOD.

   /* Test valid minimal and composite formats. */
   @Test.
   METHOD PUBLIC VOID testValidMinimalAndComposite():
       STRING(0, "abc9xy"). /* minimal pattern */
       STRING(0, ",,.99"). /* composite pattern */
       STRING(0, "abc>,>Z*,9.9<,xy"). /* complex pattern */
       STRING(0, "->>9,99."). /* valid pattern */
       STRING(0, " 9 "). /* minimal with spaces */
   END METHOD.

   /* Test valid common patterns. */
   @Test.
   METHOD PUBLIC VOID testValidCommonPatterns():
       STRING(0, "abc,9xy"). /* common pattern */
       STRING(0, "((>Z*9.<)xy"). /* complex pattern */
       STRING(0, "(9)"). /* simple parenthesis */
       STRING(0, "(9"). /* parenthesis without suffix */
       STRING(0, ".9"). /* decimal pattern */
       STRING(0, "9."). /* decimal pattern */
       STRING(0, ",Z9"). /* valid with comma */
   END METHOD.

   /* Test valid minus with Z placeholders. */
   @Test.
   METHOD PUBLIC VOID testValidMinusWithZPlaceholders():
       STRING(0, "-zzz"). /* OE-valid with Z placeholders */
   END METHOD.

   /**
    * Validate simple formats
    */
   @Test.
   METHOD PUBLIC VOID TestSimpleFormat():
      STRING(0, "->>,>>9.99<<").
   END METHOD.

   /**
    * Validate formats which are invalid
    */
   @Test.
   METHOD PUBLIC VOID TestInvalid():
      testError22("8", 1).
   END METHOD.

   /**
    * Validate valid OE formats which are not accepted by FWD
    */
   @Test.
   METHOD PUBLIC VOID TestComplexFormats():
      STRING(0, "+>>,>.9,,99999999").
      STRING(0, "+>>>.9,,99999999").
      STRING(0, "+>>>>.9,,99999999").
      STRING(0, "+>>>>>.9,,99999999").
      STRING(0, ">>>>.9,,99999999").
      STRING(0, ">>>>>.9,,99999999").
      STRING(0, "+9*Z9.9,,9999").
      STRING(0, "+>,,9,*Z9.9,,9999").
      STRING(0, "+>,9,*Z9.9,,9999").
      STRING(0, ",>9,*Z9.9,,9999").
      STRING(0, "A,,>*Z9.9,,9999").
      STRING(0, "A>*Z9.9,,9999B").
      STRING(0, "prefix>>,>*Z9.9,,9999+").
      STRING(0, "prefix+>>,>*Z9.9,,9999suffix").
      STRING(0, "prefix+>>>*Z9.9,,9999suffix").
      STRING(0, "prefix>>,>*Z9.9,,9999+suffix").
      STRING(0, ">*Z9").
      STRING(0, ">*Z9.").
      STRING(0, "+>*Z9.9,,9999").
      STRING(0, "+>>>*Z,9.9,,9999").
      STRING(0, "+>>>*Z9.9,,9999").
      STRING(0, ",,>*Z9.9,,9999").
      STRING(0, ",>*Z9.9,,9999").
      STRING(0, ",>,*Z9.9,,9999").
      STRING(0, "->*Z9.9,,9999").
      STRING(0, "->>>*Z9.9,,9999").
      STRING(0, ">*Z9.9,,99,,99").
      STRING(0, ">*Z9.9,,9999").
      STRING(0, ">Z9.9,,9999+").
      STRING(0, "+>>>*Z,9.9,,99999999999").
   END METHOD.

   METHOD PRIVATE STATIC VOID testError22(INPUT fmt AS CHARACTER, INPUT expectedPosition AS INTEGER):
      DEFINE VARIABLE dummy AS CHARACTER NO-UNDO.
      dummy = STRING(0, fmt) NO-ERROR.

      Assert:IsTrue(ERROR-STATUS:ERROR).
      Assert:Equals(22, ERROR-STATUS:GET-NUMBER(1)).
      Assert:Equals("** Character number " + STRING(expectedPosition) + " of format " + fmt + " is invalid. (22)", ERROR-STATUS:GET-MESSAGE(1)).
   END METHOD.

   METHOD PRIVATE STATIC VOID testError148(INPUT fmt AS CHARACTER):
      DEFINE VARIABLE dummy AS CHARACTER NO-UNDO.
      dummy = STRING(0, fmt) NO-ERROR.

      Assert:IsTrue(ERROR-STATUS:ERROR).
      Assert:Equals(148, ERROR-STATUS:GET-NUMBER(1)).
      Assert:Equals("** Numeric format " + fmt + " provides for no digits. (148)", ERROR-STATUS:GET-MESSAGE(1)).
   END METHOD.

END CLASS.