cbiditst.c   [plain text]


/********************************************************************
 * COPYRIGHT:
 * Copyright (c) 1997-2006, International Business Machines Corporation and
 * others. All Rights Reserved.
 ********************************************************************/
/*   file name:  cbiditst.cpp
*   encoding:   US-ASCII
*   tab size:   8 (not used)
*   indentation:4
*
*   created on: 1999sep27
*   created by: Markus W. Scherer
*/

#include "cintltst.h"
#include "unicode/utypes.h"
#include "unicode/uchar.h"
#include "unicode/ustring.h"
#include "unicode/ubidi.h"
#include "unicode/ushape.h"
#include "cbiditst.h"
#include "cstring.h"
/* the following include is needed for sprintf */
#include <stdio.h>

#define MAXLEN      MAX_STRING_LENGTH
#define LENGTHOF(array) (sizeof(array)/sizeof((array)[0]))

/* prototypes ---------------------------------------------------------------*/

static void
charFromDirPropTest(void);

static void
doBiDiTest(void);

static void
doTests(UBiDi *pBiDi, UBiDi *pLine, UBool countRunsFirst);

static void
doTest(UBiDi *pBiDi, int testNumber, BiDiTestData *test, int32_t lineStart, UBool countRunsFirst);

static void
testReordering(UBiDi *pBiDi, int testNumber);

static void
doInverseBiDiTest(void);

static void
testManyInverseBiDi(UBiDi *pBiDi, UBiDiLevel direction);

static void
testInverseBiDi(UBiDi *pBiDi, const UChar *src, int32_t srcLength, UBiDiLevel direction, UErrorCode *pErrorCode);

static void
testWriteReverse(void);

static void
doArabicShapingTest(void);

static void
doLamAlefSpecialVLTRArabicShapingTest(void);

static void
doTashkeelSpecialVLTRArabicShapingTest(void);

static void
doLOGICALArabicDeShapingTest(void);

static void TestReorder(void);

static void TestFailureRecovery(void);

static void TestMultipleParagraphs(void);

/* new BIDI API */
static void doReorderingModeBidiTest(void);
static void doReorderRunsTest(void);
static void doBidiStreamingTest(void);
static void doBidiClassOverrideTest(void);
static const char* inverseBasic(UBiDi *pBiDi, const UChar *src, int32_t srcLen,
                                uint32_t option, UBiDiLevel level, char *result);
static UBool assertRoundTrip(UBiDi *pBiDi, int32_t tc, int32_t outIndex,
                             const char *srcChars, const char *destChars,
                             const UChar *dest, int32_t destLen, int mode,
                             int option, UBiDiLevel level);
static UBool checkResultLength(UBiDi *pBiDi, const char *srcChars,
                               const char *destChars, const UChar *dest,
                               int32_t destLen, const char *mode,
                               const char *option, UBiDiLevel level);
static UBool testMaps(UBiDi *pBiDi, int32_t stringIndex, const char *src,
                      const char *dest, const char *mode, const char* option,
                      UBiDiLevel level, UBool forward);

/* helpers ------------------------------------------------------------------ */

static const char *levelString="...............................................................";

static void
initCharFromDirProps(void);

static UChar *
getStringFromDirProps(const uint8_t *dirProps, int32_t length, UChar *buffer);

static void
printUnicode(const UChar *s, int32_t length, const UBiDiLevel *levels);

/* regression tests ---------------------------------------------------------*/

void addComplexTest(TestNode** root);

void
addComplexTest(TestNode** root) {
    addTest(root, charFromDirPropTest, "complex/bidi/charFromDirPropTest");
    addTest(root, doBiDiTest, "complex/bidi/BiDiTest");
    addTest(root, doInverseBiDiTest, "complex/bidi/inverse");
    addTest(root, TestReorder,"complex/bidi/TestReorder");
    addTest(root, TestFailureRecovery,"complex/bidi/TestFailureRecovery");
    addTest(root, TestMultipleParagraphs,"complex/bidi/multipleParagraphs");
    addTest(root, doReorderingModeBidiTest, "complex/bidi/TestReorderingMode");
    addTest(root, doReorderRunsTest, "complex/bidi/TestReorderRunsOnly");
    addTest(root, doBidiStreamingTest, "complex/bidi/TestStreamingMode");
    addTest(root, doBidiClassOverrideTest, "complex/bidi/TestClassOverride");

    addTest(root, doArabicShapingTest, "complex/arabic-shaping/ArabicShapingTest");
    addTest(root, doLamAlefSpecialVLTRArabicShapingTest, "complex/arabic-shaping/lamalef");
    addTest(root, doTashkeelSpecialVLTRArabicShapingTest, "complex/arabic-shaping/tashkeel");
    addTest(root, doLOGICALArabicDeShapingTest, "complex/arabic-shaping/unshaping");
}

/* verify that the exemplar characters have the expected bidi classes */
static void
charFromDirPropTest(void) {
    int32_t i;

    initCharFromDirProps();

    for(i=0; i<U_CHAR_DIRECTION_COUNT; ++i) {
        if(u_charDirection(charFromDirProp[i])!=(UCharDirection)i) {
            log_err("\nu_charDirection(charFromDirProp[%d]=U+%04x)==%d!=%d\n",
                    i, charFromDirProp[i], u_charDirection(charFromDirProp[i]), i);
        }
    }
}

static void
doBiDiTest() {
    UBiDi *pBiDi, *pLine=NULL;
    UErrorCode errorCode=U_ZERO_ERROR;

    log_verbose("\n*** bidi regression test ***\n");

    pBiDi=ubidi_openSized(MAXLEN, 0, &errorCode);
    if(pBiDi!=NULL) {
        pLine=ubidi_open();
        if(pLine!=NULL) {
            doTests(pBiDi, pLine, FALSE);
            doTests(pBiDi, pLine, TRUE);
        } else {
            log_err("ubidi_open() returned NULL, out of memory\n");
        }
    } else {
        log_err("ubidi_openSized() returned NULL, errorCode %s\n", myErrorName(errorCode));
    }

    if(pLine!=NULL) {
        ubidi_close(pLine);
    }
    if(pBiDi!=NULL) {
        ubidi_close(pBiDi);
    }

    log_verbose("\n*** bidi regression test finished ***\n");
}

static void
doTests(UBiDi *pBiDi, UBiDi *pLine, UBool countRunsFirst) {
    int i;
    UChar string[MAXLEN];
    UErrorCode errorCode;
    int32_t lineStart;
    UBiDiLevel paraLevel;

    for(i=0; i<bidiTestCount; ++i) {
        errorCode=U_ZERO_ERROR;
        getStringFromDirProps(tests[i].text, tests[i].length, string);
        paraLevel=tests[i].paraLevel;
        ubidi_setPara(pBiDi, string, -1, paraLevel, NULL, &errorCode);
        if(U_SUCCESS(errorCode)) {
            log_verbose("ubidi_setPara(tests[%d], paraLevel %d) ok, direction %d paraLevel=%d\n",
                    i, paraLevel, ubidi_getDirection(pBiDi), ubidi_getParaLevel(pBiDi));
            lineStart=tests[i].lineStart;
            if(lineStart==-1) {
                doTest(pBiDi, i, tests+i, 0, countRunsFirst);
            } else {
                ubidi_setLine(pBiDi, lineStart, tests[i].lineLimit, pLine, &errorCode);
                if(U_SUCCESS(errorCode)) {
                    log_verbose("ubidi_setLine(%d, %d) ok, direction %d paraLevel=%d\n",
                            lineStart, tests[i].lineLimit, ubidi_getDirection(pLine), ubidi_getParaLevel(pLine));
                    doTest(pLine, i, tests+i, lineStart, countRunsFirst);
                } else {
                    log_err("ubidi_setLine(tests[%d], %d, %d) failed with errorCode %s\n",
                            i, lineStart, tests[i].lineLimit, myErrorName(errorCode));
                }
            }
        } else {
            log_err("ubidi_setPara(tests[%d], paraLevel %d) failed with errorCode %s\n",
                    i, paraLevel, myErrorName(errorCode));
        }
    }
}

static const char columns[62] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

#define TABLE_SIZE  256
static UBool   tablesInitialized = FALSE;
static UChar   pseudoToUChar[TABLE_SIZE];
static uint8_t UCharToPseudo[TABLE_SIZE];    /* used for Unicode chars < 0x0100 */
static uint8_t UCharToPseud2[TABLE_SIZE];    /* used for Unicode chars >=0x0100 */

static void buildPseudoTables(void)
/*
    The rules for pseudo-Bidi are as follows:
    - [ == LRE
    - ] == RLE
    - { == LRO
    - } == RLO
    - ^ == PDF
    - @ == LRM
    - & == RLM
    - A-F == Arabic Letters 0631-0636
    - G-Z == Hebrew letters 05d7-05ea
    - 0-5 == western digits 0030-0035
    - 6-9 == Arabic-Indic digits 0666-0669
    - ` == Combining Grave Accent 0300 (NSM)
    - ~ == Delete 007f (BN)
    - | == Paragraph Separator 2029 (B)
    - _ == Info Separator 1 001f (S)
    All other characters represent themselves as Latin-1, with the corresponding
    Bidi properties.
*/
{
    int             i;
    UChar           uchar;
    uint8_t         c;
    /* initialize all tables to unknown */
    for (i=0; i < TABLE_SIZE; i++) {
        pseudoToUChar[i] = 0xFFFD;
        UCharToPseudo[i] = '?';
        UCharToPseud2[i] = '?';
    }
    /* initialize non letters or digits */
    pseudoToUChar[(uint8_t) 0 ] = 0x0000;    UCharToPseudo[0x00] = (uint8_t) 0 ;
    pseudoToUChar[(uint8_t)' '] = 0x0020;    UCharToPseudo[0x20] = (uint8_t)' ';
    pseudoToUChar[(uint8_t)'!'] = 0x0021;    UCharToPseudo[0x21] = (uint8_t)'!';
    pseudoToUChar[(uint8_t)'"'] = 0x0022;    UCharToPseudo[0x22] = (uint8_t)'"';
    pseudoToUChar[(uint8_t)'#'] = 0x0023;    UCharToPseudo[0x23] = (uint8_t)'#';
    pseudoToUChar[(uint8_t)'$'] = 0x0024;    UCharToPseudo[0x24] = (uint8_t)'$';
    pseudoToUChar[(uint8_t)'%'] = 0x0025;    UCharToPseudo[0x25] = (uint8_t)'%';
    pseudoToUChar[(uint8_t)'\'']= 0x0027;    UCharToPseudo[0x27] = (uint8_t)'\'';
    pseudoToUChar[(uint8_t)'('] = 0x0028;    UCharToPseudo[0x28] = (uint8_t)'(';
    pseudoToUChar[(uint8_t)')'] = 0x0029;    UCharToPseudo[0x29] = (uint8_t)')';
    pseudoToUChar[(uint8_t)'*'] = 0x002A;    UCharToPseudo[0x2A] = (uint8_t)'*';
    pseudoToUChar[(uint8_t)'+'] = 0x002B;    UCharToPseudo[0x2B] = (uint8_t)'+';
    pseudoToUChar[(uint8_t)','] = 0x002C;    UCharToPseudo[0x2C] = (uint8_t)',';
    pseudoToUChar[(uint8_t)'-'] = 0x002D;    UCharToPseudo[0x2D] = (uint8_t)'-';
    pseudoToUChar[(uint8_t)'.'] = 0x002E;    UCharToPseudo[0x2E] = (uint8_t)'.';
    pseudoToUChar[(uint8_t)'/'] = 0x002F;    UCharToPseudo[0x2F] = (uint8_t)'/';
    pseudoToUChar[(uint8_t)':'] = 0x003A;    UCharToPseudo[0x3A] = (uint8_t)':';
    pseudoToUChar[(uint8_t)';'] = 0x003B;    UCharToPseudo[0x3B] = (uint8_t)';';
    pseudoToUChar[(uint8_t)'<'] = 0x003C;    UCharToPseudo[0x3C] = (uint8_t)'<';
    pseudoToUChar[(uint8_t)'='] = 0x003D;    UCharToPseudo[0x3D] = (uint8_t)'=';
    pseudoToUChar[(uint8_t)'>'] = 0x003E;    UCharToPseudo[0x3E] = (uint8_t)'>';
    pseudoToUChar[(uint8_t)'?'] = 0x003F;    UCharToPseudo[0x3F] = (uint8_t)'?';
    pseudoToUChar[(uint8_t)'\\']= 0x005C;    UCharToPseudo[0x5C] = (uint8_t)'\\';
    /* initialize specially used characters */
    pseudoToUChar[(uint8_t)'`'] = 0x0300;    UCharToPseud2[0x00] = (uint8_t)'`';  /* NSM */
    pseudoToUChar[(uint8_t)'@'] = 0x200E;    UCharToPseud2[0x0E] = (uint8_t)'@';  /* LRM */
    pseudoToUChar[(uint8_t)'&'] = 0x200F;    UCharToPseud2[0x0F] = (uint8_t)'&';  /* RLM */
    pseudoToUChar[(uint8_t)'_'] = 0x001F;    UCharToPseudo[0x1F] = (uint8_t)'_';  /* S   */
    pseudoToUChar[(uint8_t)'|'] = 0x2029;    UCharToPseud2[0x29] = (uint8_t)'|';  /* B   */
    pseudoToUChar[(uint8_t)'['] = 0x202A;    UCharToPseud2[0x2A] = (uint8_t)'[';  /* LRE */
    pseudoToUChar[(uint8_t)']'] = 0x202B;    UCharToPseud2[0x2B] = (uint8_t)']';  /* RLE */
    pseudoToUChar[(uint8_t)'^'] = 0x202C;    UCharToPseud2[0x2C] = (uint8_t)'^';  /* PDF */
    pseudoToUChar[(uint8_t)'{'] = 0x202D;    UCharToPseud2[0x2D] = (uint8_t)'{';  /* LRO */
    pseudoToUChar[(uint8_t)'}'] = 0x202E;    UCharToPseud2[0x2E] = (uint8_t)'}';  /* RLO */
    pseudoToUChar[(uint8_t)'~'] = 0x007F;    UCharToPseudo[0x7F] = (uint8_t)'~';  /* BN  */
    /* initialize western digits */
    for (i = 0, uchar = 0x0030; i < 6; i++, uchar++) {
        c = (uint8_t)columns[i];
        pseudoToUChar[c] = uchar;
        UCharToPseudo[uchar & 0x00ff] = c;
    }
    /* initialize Hindi digits */
    for (i = 6, uchar = 0x0666; i < 10; i++, uchar++) {
        c = (uint8_t)columns[i];
        pseudoToUChar[c] = uchar;
        UCharToPseud2[uchar & 0x00ff] = c;
    }
    /* initialize Arabic letters */
    for (i = 10, uchar = 0x0631; i < 16; i++, uchar++) {
        c = (uint8_t)columns[i];
        pseudoToUChar[c] = uchar;
        UCharToPseud2[uchar & 0x00ff] = c;
    }
    /* initialize Hebrew letters */
    for (i = 16, uchar = 0x05D7; i < 36; i++, uchar++) {
        c = (uint8_t)columns[i];
        pseudoToUChar[c] = uchar;
        UCharToPseud2[uchar & 0x00ff] = c;
    }
    /* initialize Latin lower case letters */
    for (i = 36, uchar = 0x0061; i < 62; i++, uchar++) {
        c = (uint8_t)columns[i];
        pseudoToUChar[c] = uchar;
        UCharToPseudo[uchar & 0x00ff] = c;
    }
    tablesInitialized = TRUE;
}

/*----------------------------------------------------------------------*/

static int pseudoToU16( const int length, const char * input, UChar * output )
/*  This function converts a pseudo-Bidi string into a UChar string.
    It returns the length of the UChar string.
*/
{
    int             i;
    if (!tablesInitialized) {
        buildPseudoTables();
    }
    for (i = 0; i < length; i++)
        output[i] = pseudoToUChar[(uint8_t)input[i]];
    return length;
}

/*----------------------------------------------------------------------*/

static int u16ToPseudo( const int length, const UChar * input, char * output )
/*  This function converts a UChar string into a pseudo-Bidi string.
    It returns the length of the pseudo-Bidi string.
*/
{
    int             i;
    UChar           uchar;
    if (!tablesInitialized) {
        buildPseudoTables();
    }
    for (i = 0; i < length; i++)
    {
        uchar = input[i];
        output[i] = uchar < 0x0100 ? UCharToPseudo[uchar] :
                                        UCharToPseud2[uchar & 0x00ff];
    }
    output[length] = '\0';
    return length;
}

static char * formatLevels(UBiDi *bidi, char *buffer) {
    UErrorCode ec = U_ZERO_ERROR;
    const UBiDiLevel* gotLevels = ubidi_getLevels(bidi, &ec);
    int len = ubidi_getLength(bidi);
    char c;
    int i, k;

    if(U_FAILURE(ec)) {
        strcpy(buffer, "BAD LEVELS");
        return buffer;
    }
    for (i=0; i<len; i++) {
        k = gotLevels[i];
        if (k >= sizeof columns)
            c = '+';
        else
            c = columns[k];
        buffer[i] = c;
    }
    buffer[len] = '\0';
    return buffer;
}

static void TestReorder(){
    static const char* const logicalOrder[] ={
            "del(KC)add(K.C.&)",
            "del(QDVT) add(BVDL)",
            "del(PQ)add(R.S.)T)U.&",
            "del(LV)add(L.V.) L.V.&",
            "day  0  R  DPDHRVR dayabbr",
            "day  1  H  DPHPDHDA dayabbr",
            "day  2   L  DPBLENDA dayabbr",
            "day  3  J  DPJQVM  dayabbr",
            "day  4   I  DPIQNF    dayabbr",
            "day  5  M  DPMEG  dayabbr",
            "helloDPMEG",
    };
    static const char* const visualOrder[]={
            "del(CK)add(&.C.K)",
            "del(TVDQ) add(LDVB)",
            "del(QP)add(&.U(T(.S.R",
            "del(VL)add(&.V.L (.V.L",
            "day  0  RVRHDPD  R dayabbr",
            "day  1  ADHDPHPD  H dayabbr",
            "day  2   ADNELBPD  L dayabbr",
            "day  3  MVQJPD  J  dayabbr",
            "day  4   FNQIPD  I    dayabbr",
            "day  5  GEMPD  M  dayabbr",
            "helloGEMPD",
    };
    static const char* const visualOrder1[]={
            ")K.C.&(dda)KC(led",
            ")BVDL(dda )QDVT(led",
            "R.S.(T(U.&(dda)PQ(led",
            "L.V.( L.V.&(dda)LV(led",
            "rbbayad R  DPDHRVR  0  yad",
            "rbbayad H  DPHPDHDA  1  yad",
            "rbbayad L  DPBLENDA   2  yad",
            "rbbayad  J  DPJQVM  3  yad",
            "rbbayad    I  DPIQNF   4  yad",
            "rbbayad  M  DPMEG  5  yad",
            "DPMEGolleh",
    };

    static const char* const visualOrder2[]={
            "@)@K.C.&@(dda)@KC@(led",
            "@)@BVDL@(dda )@QDVT@(led",
            "R.S.)T)U.&@(dda)@PQ@(led",
            "L.V.) L.V.&@(dda)@LV@(led",
            "rbbayad @R  DPDHRVR@  0  yad",
            "rbbayad @H  DPHPDHDA@  1  yad",
            "rbbayad @L  DPBLENDA@   2  yad",
            "rbbayad  @J  DPJQVM@  3  yad",
            "rbbayad    @I  DPIQNF@   4  yad",
            "rbbayad  @M  DPMEG@  5  yad",
            "DPMEGolleh",
    };
    static const char* const visualOrder3[]={
            ")K.C.&(KC)dda(led",
            ")BVDL(ddaQDVT) (led",
            "R.S.)T)U.&(PQ)dda(led",
            "L.V.) L.V.&(LV)dda(led",
            "rbbayad DPDHRVR   R  0 yad",
            "rbbayad DPHPDHDA   H  1 yad",
            "rbbayad DPBLENDA     L 2 yad",
            "rbbayad  DPJQVM   J  3 yad",
            "rbbayad    DPIQNF     I 4 yad",
            "rbbayad  DPMEG   M  5 yad",
            "DPMEGolleh"
    };
    static const char* const visualOrder4[]={
            "del(add(CK(.C.K)",
            "del( (TVDQadd(LDVB)",
            "del(add(QP(.U(T(.S.R",
            "del(add(VL(.V.L (.V.L",
            "day 0  R   RVRHDPD dayabbr",
            "day 1  H   ADHDPHPD dayabbr",
            "day 2 L     ADNELBPD dayabbr",
            "day 3  J   MVQJPD  dayabbr",
            "day 4 I     FNQIPD    dayabbr",
            "day 5  M   GEMPD  dayabbr",
            "helloGEMPD"
    };
    char formatChars[MAXLEN];
    UErrorCode ec = U_ZERO_ERROR;
    UBiDi* bidi = ubidi_open();
    int i;
    for(i=0;i<LENGTHOF(logicalOrder);i++){
        int32_t srcSize = (int32_t)strlen(logicalOrder[i]);
        int32_t destSize = srcSize*2;
        UChar src[MAXLEN];
        UChar dest[MAXLEN];
        char chars[MAXLEN];
        pseudoToU16(srcSize,logicalOrder[i],src);
        ec = U_ZERO_ERROR;
        ubidi_setPara(bidi,src,srcSize,UBIDI_DEFAULT_LTR ,NULL,&ec);
        if(U_FAILURE(ec)){
            log_err("ubidi_setPara(tests[%d], paraLevel %d) failed with errorCode %s\n",
                    i, UBIDI_DEFAULT_LTR, u_errorName(ec));
        }
        /* try pre-flighting */
        destSize = ubidi_writeReordered(bidi,dest,0,UBIDI_DO_MIRRORING,&ec);
        if(ec!=U_BUFFER_OVERFLOW_ERROR){
            log_err("Pre-flighting did not give expected error: Expected: U_BUFFER_OVERFLOW_ERROR. Got: %s \n",u_errorName(ec));
        }else if(destSize!=srcSize){
            log_err("Pre-flighting did not give expected size: Expected: %d. Got: %d \n",srcSize,destSize);
        }else{
            ec= U_ZERO_ERROR;
        }
        destSize=ubidi_writeReordered(bidi,dest,destSize+1,UBIDI_DO_MIRRORING,&ec);
        u16ToPseudo(destSize,dest,chars);
        if(destSize!=srcSize){
            log_err("ubidi_writeReordered() destSize and srcSize do not match\n");
        }else if(strcmp(visualOrder[i],chars)!=0){
            log_err("ubidi_writeReordered() did not give expected results for UBIDI_DO_MIRRORING.\n"
                    "Input   : %s\nExpected: %s\nGot     : %s\nLevels  : %s\nAt Index: %d\n",
                    logicalOrder[i],visualOrder[i],chars,formatLevels(bidi, formatChars),i);
        }
    }

    for(i=0;i<LENGTHOF(logicalOrder);i++){
        int32_t srcSize = (int32_t)strlen(logicalOrder[i]);
        int32_t destSize = srcSize*2;
        UChar src[MAXLEN];
        UChar dest[MAXLEN];
        char chars[MAXLEN];
        pseudoToU16(srcSize,logicalOrder[i],src);
        ec = U_ZERO_ERROR;
        ubidi_setPara(bidi,src,srcSize,UBIDI_DEFAULT_LTR ,NULL,&ec);
        if(U_FAILURE(ec)){
            log_err("ubidi_setPara(tests[%d], paraLevel %d) failed with errorCode %s\n",
                    i, UBIDI_DEFAULT_LTR, u_errorName(ec));
        }
        /* try pre-flighting */
        destSize = ubidi_writeReordered(bidi,dest,0,UBIDI_DO_MIRRORING+UBIDI_OUTPUT_REVERSE,&ec);
        if(ec!=U_BUFFER_OVERFLOW_ERROR){
            log_err("Pre-flighting did not give expected error: Expected: U_BUFFER_OVERFLOW_ERROR. Got: %s \n",u_errorName(ec));
        }else if(destSize!=srcSize){
            log_err("Pre-flighting did not give expected size: Expected: %d. Got: %d \n",srcSize,destSize);
        }else{
            ec= U_ZERO_ERROR;
        }
        destSize=ubidi_writeReordered(bidi,dest,destSize+1,UBIDI_DO_MIRRORING+UBIDI_OUTPUT_REVERSE,&ec);
        u16ToPseudo(destSize,dest,chars);
        if(destSize!=srcSize){
            log_err("ubidi_writeReordered() destSize and srcSize do not match\n");
        }else if(strcmp(visualOrder1[i],chars)!=0){
            log_err("ubidi_writeReordered() did not give expected results for UBIDI_DO_MIRRORING+UBIDI_OUTPUT_REVERSE.\n"
                    "Input   : %s\nExpected: %s\nGot     : %s\nLevels  : %s\nAt Index: %d\n",
                    logicalOrder[i],visualOrder1[i],chars,formatLevels(bidi, formatChars),i);
        }
    }

    for(i=0;i<LENGTHOF(logicalOrder);i++){
        int32_t srcSize = (int32_t)strlen(logicalOrder[i]);
        int32_t destSize = srcSize*2;
        UChar src[MAXLEN];
        UChar dest[MAXLEN];
        char chars[MAXLEN];
        pseudoToU16(srcSize,logicalOrder[i],src);
        ec = U_ZERO_ERROR;
        ubidi_setInverse(bidi,TRUE);
        ubidi_setPara(bidi,src,srcSize,UBIDI_DEFAULT_LTR ,NULL,&ec);
        if(U_FAILURE(ec)){
            log_err("ubidi_setPara(tests[%d], paraLevel %d) failed with errorCode %s\n",
                    i, UBIDI_DEFAULT_LTR, u_errorName(ec));
        }
                /* try pre-flighting */
        destSize = ubidi_writeReordered(bidi,dest,0,UBIDI_INSERT_LRM_FOR_NUMERIC+UBIDI_OUTPUT_REVERSE,&ec);
        if(ec!=U_BUFFER_OVERFLOW_ERROR){
            log_err("Pre-flighting did not give expected error: Expected: U_BUFFER_OVERFLOW_ERROR. Got: %s \n",u_errorName(ec));
        }else{
            ec= U_ZERO_ERROR;
        }
        destSize=ubidi_writeReordered(bidi,dest,destSize+1,UBIDI_INSERT_LRM_FOR_NUMERIC+UBIDI_OUTPUT_REVERSE,&ec);
        u16ToPseudo(destSize,dest,chars);
        if(strcmp(visualOrder2[i],chars)!=0){
            log_err("ubidi_writeReordered() did not give expected results for UBIDI_INSERT_LRM_FOR_NUMERIC+UBIDI_OUTPUT_REVERSE.\n"
                    "Input   : %s\nExpected: %s\nGot     : %s\nLevels  : %s\nAt Index: %d\n",
                    logicalOrder[i],visualOrder2[i],chars,formatLevels(bidi, formatChars),i);
        }
    }
        /* Max Explicit level */
    for(i=0;i<LENGTHOF(logicalOrder);i++){
        int32_t srcSize = (int32_t)strlen(logicalOrder[i]);
        int32_t destSize = srcSize*2;
        UChar src[MAXLEN];
        UChar dest[MAXLEN];
        char chars[MAXLEN];
        UBiDiLevel levels[UBIDI_MAX_EXPLICIT_LEVEL]={1,2,3,4,5,6,7,8,9,10};
        pseudoToU16(srcSize,logicalOrder[i],src);
        ec = U_ZERO_ERROR;
        ubidi_setPara(bidi,src,srcSize,UBIDI_DEFAULT_LTR,levels,&ec);
        if(U_FAILURE(ec)){
            log_err("ubidi_setPara(tests[%d], paraLevel %d) failed with errorCode %s\n",
                    i, UBIDI_MAX_EXPLICIT_LEVEL, u_errorName(ec));
        }
                /* try pre-flighting */
        destSize = ubidi_writeReordered(bidi,dest,0,UBIDI_OUTPUT_REVERSE,&ec);
        if(ec!=U_BUFFER_OVERFLOW_ERROR){
            log_err("Pre-flighting did not give expected error: Expected: U_BUFFER_OVERFLOW_ERROR. Got: %s \n",u_errorName(ec));
        }else if(destSize!=srcSize){
            log_err("Pre-flighting did not give expected size: Expected: %d. Got: %d \n",srcSize,destSize);
        }else{
            ec = U_ZERO_ERROR;
        }
        destSize=ubidi_writeReordered(bidi,dest,destSize+1,UBIDI_OUTPUT_REVERSE,&ec);
        u16ToPseudo(destSize,dest,chars);
        if(destSize!=srcSize){
            log_err("ubidi_writeReordered() destSize and srcSize do not match. Dest Size = %d Source Size = %d\n",destSize,srcSize );
        }else if(strcmp(visualOrder3[i],chars)!=0){
            log_err("ubidi_writeReordered() did not give expected results for UBIDI_OUTPUT_REVERSE.\n"
                    "Input   : %s\nExpected: %s\nGot     : %s\nLevels  : %s\nAt Index: %d\n",
                    logicalOrder[i],visualOrder3[i],chars,formatLevels(bidi, formatChars),i);
        }
    }
    for(i=0;i<LENGTHOF(logicalOrder);i++){
        int32_t srcSize = (int32_t)strlen(logicalOrder[i]);
        int32_t destSize = srcSize*2;
        UChar src[MAXLEN];
        UChar dest[MAXLEN];
        char chars[MAXLEN];
        UBiDiLevel levels[UBIDI_MAX_EXPLICIT_LEVEL]={1,2,3,4,5,6,7,8,9,10};
        pseudoToU16(srcSize,logicalOrder[i],src);
        ec = U_ZERO_ERROR;
        ubidi_setPara(bidi,src,srcSize,UBIDI_DEFAULT_LTR,levels,&ec);
        if(U_FAILURE(ec)){
            log_err("ubidi_setPara(tests[%d], paraLevel %d) failed with errorCode %s\n",
                    i, UBIDI_MAX_EXPLICIT_LEVEL, u_errorName(ec));
        }
        /* try pre-flighting */
        destSize = ubidi_writeReordered(bidi,dest,0,UBIDI_DO_MIRRORING+UBIDI_REMOVE_BIDI_CONTROLS,&ec);
        if(ec!=U_BUFFER_OVERFLOW_ERROR){
            log_err("Pre-flighting did not give expected error: Expected: U_BUFFER_OVERFLOW_ERROR. Got: %s \n",u_errorName(ec));
        }else{
            ec= U_ZERO_ERROR;
        }
        destSize=ubidi_writeReordered(bidi,dest,destSize+1,UBIDI_DO_MIRRORING+UBIDI_REMOVE_BIDI_CONTROLS,&ec);
        u16ToPseudo(destSize,dest,chars);
        if(strcmp(visualOrder4[i],chars)!=0){
            log_err("ubidi_writeReordered() did not give expected results for UBIDI_DO_MIRRORING+UBIDI_REMOVE_BIDI_CONTROLS.\n"
                    "Input   : %s\nExpected: %s\nGot     : %s\nLevels  : %s\nAt Index: %d\n",
                    logicalOrder[i],visualOrder4[i],chars,formatLevels(bidi, formatChars),i);
        }
    }
    ubidi_close(bidi);
}

static void
doTest(UBiDi *pBiDi, int testNumber, BiDiTestData *test, int32_t lineStart, UBool countRunsFirst) {
    const uint8_t *dirProps=test->text+lineStart;
    const UBiDiLevel *levels=test->levels;
    const uint8_t *visualMap=test->visualMap;
    int32_t i, len=ubidi_getLength(pBiDi), logicalIndex, runCount = 0;
    UErrorCode errorCode=U_ZERO_ERROR;
    UBiDiLevel level, level2;

    if (countRunsFirst) {
        log_verbose("Calling ubidi_countRuns() first.\n");

        runCount = ubidi_countRuns(pBiDi, &errorCode);

        if(U_FAILURE(errorCode)) {
            log_err("ubidi_countRuns(tests[%d]): error %s\n", testNumber, myErrorName(errorCode));
            return;
        }
    } else {
        log_verbose("Calling ubidi_getLogicalMap() first.\n");
    }

    testReordering(pBiDi, testNumber);

    for(i=0; i<len; ++i) {
        log_verbose("%3d %3d %.*s%-3s @%d\n",
                i, ubidi_getLevelAt(pBiDi, i), ubidi_getLevelAt(pBiDi, i), levelString,
                dirPropNames[dirProps[i]],
                ubidi_getVisualIndex(pBiDi, i, &errorCode));
    }

    log_verbose("\n-----levels:");
    for(i=0; i<len; ++i) {
        if(i>0) {
            log_verbose(",");
        }
        log_verbose(" %d", ubidi_getLevelAt(pBiDi, i));
    }

    log_verbose("\n--reordered:");
    for(i=0; i<len; ++i) {
        if(i>0) {
            log_verbose(",");
        }
        log_verbose(" %d", ubidi_getVisualIndex(pBiDi, i, &errorCode));
    }
    log_verbose("\n");

    if(test->direction!=ubidi_getDirection(pBiDi)) {
        log_err("ubidi_getDirection(tests[%d]): wrong direction %d\n", testNumber, ubidi_getDirection(pBiDi));
    }

    if(test->resultLevel!=ubidi_getParaLevel(pBiDi)) {
        log_err("ubidi_getParaLevel(tests[%d]): wrong paragraph level %d\n", testNumber, ubidi_getParaLevel(pBiDi));
    }

    for(i=0; i<len; ++i) {
        if(levels[i]!=ubidi_getLevelAt(pBiDi, i)) {
            log_err("ubidi_getLevelAt(tests[%d], %d): wrong level %d, expected %d\n", testNumber, i, ubidi_getLevelAt(pBiDi, i), levels[i]);
            return;
        }
    }

    for(i=0; i<len; ++i) {
        logicalIndex=ubidi_getVisualIndex(pBiDi, i, &errorCode);
        if(U_FAILURE(errorCode)) {
            log_err("ubidi_getVisualIndex(tests[%d], %d): error %s\n", testNumber, i, myErrorName(errorCode));
            return;
        }
        if(visualMap[i]!=logicalIndex) {
            log_err("ubidi_getVisualIndex(tests[%d], %d): wrong index %d\n", testNumber, i, logicalIndex);
            return;
        }
    }

    if (! countRunsFirst) {
        runCount=ubidi_countRuns(pBiDi, &errorCode);
        if(U_FAILURE(errorCode)) {
            log_err("ubidi_countRuns(tests[%d]): error %s\n", testNumber, myErrorName(errorCode));
            return;
        }
    }

    for(logicalIndex=0; logicalIndex<len;) {
        level=ubidi_getLevelAt(pBiDi, logicalIndex);
        ubidi_getLogicalRun(pBiDi, logicalIndex, &logicalIndex, &level2);
        if(level!=level2) {
            log_err("ubidi_getLogicalRun(tests[%d], run ending at index %d): wrong level %d\n", testNumber, logicalIndex, level2);
        }
        if(--runCount<0) {
            log_err("\nubidi_getLogicalRun(tests[%d]): wrong number of runs compared to %d=ubidi_getRunCount()\n", testNumber, ubidi_countRuns(pBiDi, &errorCode));
            return;
        }
    }
    if(runCount!=0) {
        log_err("\nubidi_getLogicalRun(tests[%d]): wrong number of runs compared to %d=ubidi_getRunCount()\n", testNumber, ubidi_countRuns(pBiDi, &errorCode));
        return;
    }

    log_verbose("\n\n");
}

static void
testReordering(UBiDi *pBiDi, int testNumber) {
    int32_t
        logicalMap1[MAXLEN], logicalMap2[MAXLEN], logicalMap3[MAXLEN],
        visualMap1[MAXLEN], visualMap2[MAXLEN], visualMap3[MAXLEN], visualMap4[MAXLEN];
    UErrorCode errorCode=U_ZERO_ERROR;
    const UBiDiLevel *levels;
    int32_t i, length=ubidi_getLength(pBiDi),
               destLength=ubidi_getResultLength(pBiDi);
    int32_t runCount, visualIndex, logicalStart, runLength;
    UBool odd;

    if(length<=0) {
        return;
    }

    /* get the logical and visual maps from the object */
    ubidi_getLogicalMap(pBiDi, logicalMap1, &errorCode);
    if(U_FAILURE(errorCode)) {
        log_err("ubidi_getLogicalMap(tests[%d]): error %s\n", testNumber, myErrorName(errorCode));
        return;
    }

    ubidi_getVisualMap(pBiDi, visualMap1, &errorCode);
    if(U_FAILURE(errorCode)) {
        log_err("ubidi_getVisualMap(tests[%d]): error %s\n", testNumber, myErrorName(errorCode));
        return;
    }

    /* invert them both */
    ubidi_invertMap(logicalMap1, visualMap2, length);
    ubidi_invertMap(visualMap1, logicalMap2, destLength);

    /* get them from the levels array, too */
    levels=ubidi_getLevels(pBiDi, &errorCode);

    if(U_FAILURE(errorCode)) {
        log_err("ubidi_getLevels(tests[%d]): error %s\n", testNumber, myErrorName(errorCode));
        return;
    }

    ubidi_reorderLogical(levels, length, logicalMap3);
    ubidi_reorderVisual(levels, length, visualMap3);

    /* get the visual map from the runs, too */
    runCount=ubidi_countRuns(pBiDi, &errorCode);
    if(U_FAILURE(errorCode)) {
        log_err("ubidi_countRuns(tests[%d]): error %s\n", testNumber, myErrorName(errorCode));
        return;
    }
    log_verbose("\n----%2d runs:", runCount);
    visualIndex=0;
    for(i=0; i<runCount; ++i) {
        odd=(UBool)ubidi_getVisualRun(pBiDi, i, &logicalStart, &runLength);
        log_verbose(" (%c @%d[%d])", odd ? 'R' : 'L', logicalStart, runLength);
        if(UBIDI_LTR==odd) {
            do { /* LTR */
                visualMap4[visualIndex++]=logicalStart++;
            } while(--runLength>0);
        } else {
            logicalStart+=runLength;   /* logicalLimit */
            do { /* RTL */
                visualMap4[visualIndex++]=--logicalStart;
            } while(--runLength>0);
        }
    }
    log_verbose("\n");

    /* print all the maps */
    log_verbose("logical maps:\n");
    for(i=0; i<length; ++i) {
        log_verbose("%4d", logicalMap1[i]);
    }
    log_verbose("\n");
    for(i=0; i<length; ++i) {
        log_verbose("%4d", logicalMap2[i]);
    }
    log_verbose("\n");
    for(i=0; i<length; ++i) {
        log_verbose("%4d", logicalMap3[i]);
    }

    log_verbose("\nvisual maps:\n");
    for(i=0; i<destLength; ++i) {
        log_verbose("%4d", visualMap1[i]);
    }
    log_verbose("\n");
    for(i=0; i<destLength; ++i) {
        log_verbose("%4d", visualMap2[i]);
    }
    log_verbose("\n");
    for(i=0; i<length; ++i) {
        log_verbose("%4d", visualMap3[i]);
    }
    log_verbose("\n");
    for(i=0; i<length; ++i) {
        log_verbose("%4d", visualMap4[i]);
    }
    log_verbose("\n");

    /* check that the indexes are the same between these and ubidi_getLogical/VisualIndex() */
    for(i=0; i<length; ++i) {
        if(logicalMap1[i]!=logicalMap2[i]) {
            log_err("bidi reordering error in tests[%d]: logicalMap1[i]!=logicalMap2[i] at i=%d\n", testNumber, i);
            break;
        }
        if(logicalMap1[i]!=logicalMap3[i]) {
            log_err("bidi reordering error in tests[%d]: logicalMap1[i]!=logicalMap3[i] at i=%d\n", testNumber, i);
            break;
        }

        if(visualMap1[i]!=visualMap2[i]) {
            log_err("bidi reordering error in tests[%d]: visualMap1[i]!=visualMap2[i] at i=%d\n", testNumber, i);
            break;
        }
        if(visualMap1[i]!=visualMap3[i]) {
            log_err("bidi reordering error in tests[%d]: visualMap1[i]!=visualMap3[i] at i=%d\n", testNumber, i);
            break;
        }
        if(visualMap1[i]!=visualMap4[i]) {
            log_err("bidi reordering error in tests[%d]: visualMap1[i]!=visualMap4[i] at i=%d\n", testNumber, i);
            break;
        }

        if(logicalMap1[i]!=ubidi_getVisualIndex(pBiDi, i, &errorCode)) {
            log_err("bidi reordering error in tests[%d]: logicalMap1[i]!=ubidi_getVisualIndex(i) at i=%d\n", testNumber, i);
            break;
        }
        if(U_FAILURE(errorCode)) {
            log_err("ubidi_getVisualIndex(tests[%d], %d): error %s\n", testNumber, i, myErrorName(errorCode));
            break;
        }
        if(visualMap1[i]!=ubidi_getLogicalIndex(pBiDi, i, &errorCode)) {
            log_err("bidi reordering error in tests[%d]: visualMap1[i]!=ubidi_getLogicalIndex(i) at i=%d\n", testNumber, i);
            break;
        }
        if(U_FAILURE(errorCode)) {
            log_err("ubidi_getLogicalIndex(tests[%d], %d): error %s\n", testNumber, i, myErrorName(errorCode));
            break;
        }
    }
}

static void TestFailureRecovery(void) {
    UErrorCode status;

    status = U_FILE_ACCESS_ERROR;
    if (ubidi_writeReordered(NULL, NULL, 0, 0, &status) != 0) {
        log_err("ubidi_writeReordered did not return 0 when passed a failing UErrorCode\n");
    }
    if (ubidi_writeReverse(NULL, 0, NULL, 0, 0, &status) != 0) {
        log_err("ubidi_writeReverse did not return 0 when passed a failing UErrorCode\n");
    }
    status = U_ZERO_ERROR;
    if (ubidi_writeReordered(NULL, NULL, 0, 0, &status) != 0 || status != U_ILLEGAL_ARGUMENT_ERROR) {
        log_err("ubidi_writeReordered did not fail as expected\n");
    }
    status = U_ZERO_ERROR;
    if (ubidi_writeReverse(NULL, 0, NULL, 0, 0, &status) != 0 || status != U_ILLEGAL_ARGUMENT_ERROR) {
        log_err("ubidi_writeReverse did not fail as expected\n");
    }
}

static void TestMultipleParagraphs(void) {
    static const char* const text = "__ABC\\u001c"          /* Para #0 offset 0 */
                                    "__\\u05d0DE\\u001c"    /*       1        6 */
                                    "__123\\u001c"          /*       2       12 */
                                    "\\u000d\\u000a"        /*       3       18 */
                                    "FG\\u000d"             /*       4       20 */
                                    "\\u000d"               /*       5       23 */
                                    "HI\\u000d\\u000a"      /*       6       24 */
                                    "\\u000d\\u000a"        /*       7       28 */
                                    "\\u000a"               /*       8       30 */
                                    "\\u000a"               /*       9       31 */
                                    "JK\\u001c";            /*      10       32 */
    static const int32_t paraCount=11;
    static const int32_t paraBounds[]={0, 6, 12, 18, 20, 23, 24, 28, 30, 31, 32, 35};
    static const UBiDiLevel paraLevels[]={UBIDI_LTR, UBIDI_RTL, UBIDI_DEFAULT_LTR, UBIDI_DEFAULT_RTL, 22, 23};
    static const UBiDiLevel multiLevels[6][11] = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                                                  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                                                  {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                                                  {0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0},
                                                  {22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22},
                                                  {23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23}};
    static const char* const text2 = "\\u05d0 1-2\\u001c\\u0630 1-2\\u001c1-2";
    static const UBiDiLevel levels2[] = {1,1,2,2,2,0, 1,1,2,1,2,0, 2,2,2};
    UBiDiLevel gotLevel;
    const UBiDiLevel* gotLevels;
    UBool orderParagraphsLTR;
    UChar src[MAXLEN];
    UErrorCode errorCode=U_ZERO_ERROR;
    UBiDi* pBidi=ubidi_open();
    UBiDi* pLine;
    int32_t srcSize, count, paraStart, paraLimit, paraIndex, length;
    int i, j, k;

    u_unescape(text, src, MAXLEN);
    srcSize=u_strlen(src);
    ubidi_setPara(pBidi, src, srcSize, UBIDI_LTR, NULL, &errorCode);
    if(U_FAILURE(errorCode)){
        log_err("ubidi_setPara failed, paraLevel=%d, errorCode %s\n",
                UBIDI_LTR, u_errorName(errorCode));
        ubidi_close(pBidi);
        return;
    }
    /* check paragraph count and boundaries */
    if (paraCount!=(count=ubidi_countParagraphs(pBidi))) {
        log_err("ubidi_countParagraphs returned %d, should be %d\n",
                count, paraCount);
    }
    for (i=0; i<paraCount; i++) {
        ubidi_getParagraphByIndex(pBidi, i, &paraStart, &paraLimit, NULL, &errorCode);
        if ((paraStart!=paraBounds[i]) || (paraLimit!=paraBounds[i+1])) {
            log_err("Found boundaries of paragraph %d: %d-%d; expected: %d-%d\n",
                    i, paraStart, paraLimit, paraBounds[i], paraBounds[i+1]);
        }
    }
    errorCode=U_ZERO_ERROR;
    /* check with last paragraph not terminated by B */
    src[srcSize-1]='L';
    ubidi_setPara(pBidi, src, srcSize, UBIDI_LTR, NULL, &errorCode);
    if(U_FAILURE(errorCode)){
        log_err("2nd ubidi_setPara failed, paraLevel=%d, errorCode %s\n",
                UBIDI_LTR, u_errorName(errorCode));
        ubidi_close(pBidi);
        return;
    }
    if (paraCount!=(count=ubidi_countParagraphs(pBidi))) {
        log_err("2nd ubidi_countParagraphs returned %d, should be %d\n",
                count, paraCount);
    }
    i=paraCount-1;
    ubidi_getParagraphByIndex(pBidi, i, &paraStart, &paraLimit, NULL, &errorCode);
    if ((paraStart!=paraBounds[i]) || (paraLimit!=paraBounds[i+1])) {
        log_err("2nd Found boundaries of paragraph %d: %d-%d; expected: %d-%d\n",
                i, paraStart, paraLimit, paraBounds[i], paraBounds[i+1]);
    }
    errorCode=U_ZERO_ERROR;
    /* check paraLevel for all paragraphs under various paraLevel specs */
    for (k=0; k<6; k++) {
        ubidi_setPara(pBidi, src, srcSize, paraLevels[k], NULL, &errorCode);
        for (i=0; i<paraCount; i++) {
            paraIndex=ubidi_getParagraph(pBidi, paraBounds[i], NULL, NULL, &gotLevel, &errorCode);
            if (paraIndex!=i) {
                log_err("For paraLevel=%d paragraph=%d, found paragraph index=%d expected=%d\n",
                        paraLevels[k], i, paraIndex, i);
            }
            if (gotLevel!=multiLevels[k][i]) {
                log_err("For paraLevel=%d paragraph=%d, found level=%d expected %d\n",
                        paraLevels[k], i, gotLevel, multiLevels[k][i]);
            }
        }
        gotLevel=ubidi_getParaLevel(pBidi);
        if (gotLevel!=multiLevels[k][0]) {
            log_err("For paraLevel=%d getParaLevel=%d, expected %d\n",
                    paraLevels[k], gotLevel, multiLevels[k][0]);
        }
    }
    errorCode=U_ZERO_ERROR;
    /* check that the result of ubidi_getParaLevel changes if the first
     * paragraph has a different level
     */
    src[0]=0x05d2;                      /* Hebrew letter Gimel */
    ubidi_setPara(pBidi, src, srcSize, UBIDI_DEFAULT_LTR, NULL, &errorCode);
    gotLevel=ubidi_getParaLevel(pBidi);
    if (gotLevel!=UBIDI_RTL) {
        log_err("For paraLevel=UBIDI_DEFAULT_LTR getParaLevel=%d, expected=%d\n",
                        gotLevel, UBIDI_RTL);
    }
    errorCode=U_ZERO_ERROR;
    /* check that line cannot overlap paragraph boundaries */
    pLine=ubidi_open();
    i=paraBounds[1];
    k=paraBounds[2]+1;
    ubidi_setLine(pBidi, i, k, pLine, &errorCode);
    if (U_SUCCESS(errorCode)) {
        log_err("For line limits %d-%d got success %s\n",
                i, k, u_errorName(errorCode));
    }
    errorCode=U_ZERO_ERROR;
    i=paraBounds[1];
    k=paraBounds[2];
    ubidi_setLine(pBidi, i, k, pLine, &errorCode);
    if (U_FAILURE(errorCode)) {
        log_err("For line limits %d-%d got error %s\n",
                i, k, u_errorName(errorCode));
        errorCode=U_ZERO_ERROR;
    }
    /* check level of block separator at end of paragraph when orderParagraphsLTR==FALSE */
    ubidi_setPara(pBidi, src, srcSize, UBIDI_RTL, NULL, &errorCode);
    /* get levels through para Bidi block */
    gotLevels=ubidi_getLevels(pBidi, &errorCode);
    if (U_FAILURE(errorCode)) {
        log_err("Error on Para getLevels %s\n", u_errorName(errorCode));
        ubidi_close(pLine);
        ubidi_close(pBidi);
        return;
    }
    for (i=26; i<32; i++) {
        if (gotLevels[i]!=UBIDI_RTL) {
            log_err("For char %d(%04x), level=%d, expected=%d\n",
                    i, src[i], gotLevels[i], UBIDI_RTL);
        }
    }
    /* get levels through para Line block */
    i=paraBounds[1];
    k=paraBounds[2];
    ubidi_setLine(pBidi, i, k, pLine, &errorCode);
    if (U_FAILURE(errorCode)) {
        log_err("For line limits %d-%d got error %s\n",
                i, k, u_errorName(errorCode));
        ubidi_close(pLine);
        ubidi_close(pBidi);
        return;
    }
    paraIndex=ubidi_getParagraph(pLine, i, &paraStart, &paraLimit, &gotLevel, &errorCode);
    gotLevels=ubidi_getLevels(pLine, &errorCode);
    if (U_FAILURE(errorCode)) {
        log_err("Error on Line getLevels %s\n", u_errorName(errorCode));
        ubidi_close(pLine);
        ubidi_close(pBidi);
        return;
    }
    length=ubidi_getLength(pLine);
    if ((gotLevel!=UBIDI_RTL) || (gotLevels[length-1]!=UBIDI_RTL)) {
        log_err("For paragraph %d with limits %d-%d, paraLevel=%d expected=%d, "
                "level of separator=%d expected=%d\n",
                paraIndex, paraStart, paraLimit, gotLevel, UBIDI_RTL, gotLevels[length-1], UBIDI_RTL);
    }
    orderParagraphsLTR=ubidi_isOrderParagraphsLTR(pBidi);
    if (orderParagraphsLTR) {
        log_err("Found orderParagraphsLTR=%d expected=%d\n", orderParagraphsLTR, FALSE);
    }
    ubidi_orderParagraphsLTR(pBidi, TRUE);
    orderParagraphsLTR=ubidi_isOrderParagraphsLTR(pBidi);
    if (!orderParagraphsLTR) {
        log_err("Found orderParagraphsLTR=%d expected=%d\n", orderParagraphsLTR, TRUE);
    }
    /* check level of block separator at end of paragraph when orderParagraphsLTR==TRUE */
    ubidi_setPara(pBidi, src, srcSize, UBIDI_RTL, NULL, &errorCode);
    /* get levels through para Bidi block */
    gotLevels=ubidi_getLevels(pBidi, &errorCode);
    for (i=26; i<32; i++) {
        if (gotLevels[i]!=0) {
            log_err("For char %d(%04x), level=%d, expected=%d\n",
                    i, src[i], gotLevels[i], 0);
        }
    }
    errorCode=U_ZERO_ERROR;
    /* get levels through para Line block */
    i=paraBounds[1];
    k=paraBounds[2];
    ubidi_setLine(pBidi, paraStart, paraLimit, pLine, &errorCode);
    paraIndex=ubidi_getParagraph(pLine, i, &paraStart, &paraLimit, &gotLevel, &errorCode);
    gotLevels=ubidi_getLevels(pLine, &errorCode);
    length=ubidi_getLength(pLine);
    if ((gotLevel!=UBIDI_RTL) || (gotLevels[length-1]!=0)) {
        log_err("For paragraph %d with limits %d-%d, paraLevel=%d expected=%d, "
                "level of separator=%d expected=%d\n",
                paraIndex, paraStart, paraLimit, gotLevel, UBIDI_RTL, gotLevels[length-1], 0);
        log_verbose("levels=");
        for (count=0; count<length; count++) {
            log_verbose(" %d", gotLevels[count]);
        }
        log_verbose("\n");
    }

    /* test that the concatenation of separate invocations of the bidi code
     * on each individual paragraph in order matches the levels array that
     * results from invoking bidi once over the entire multiparagraph tests
     * (with orderParagraphsLTR false, of course)
     */
    u_unescape(text, src, MAXLEN);      /* restore original content */
    srcSize=u_strlen(src);
    ubidi_orderParagraphsLTR(pBidi, FALSE);
    ubidi_setPara(pBidi, src, srcSize, UBIDI_DEFAULT_RTL, NULL, &errorCode);
    gotLevels=ubidi_getLevels(pBidi, &errorCode);
    for (i=0; i<paraCount; i++) {
        /* use pLine for individual paragraphs */
        paraStart = paraBounds[i];
        length = paraBounds[i+1] - paraStart;
        ubidi_setPara(pLine, src+paraStart, length, UBIDI_DEFAULT_RTL, NULL, &errorCode);
        for (j=0; j<length; j++) {
            if ((k=ubidi_getLevelAt(pLine, j)) != (gotLevel=gotLevels[paraStart+j])) {
                log_err("Checking paragraph concatenation: for paragraph=%d, "
                        "char=%d(%04x), level=%d, expected=%d\n",
                        i, j, src[paraStart+j], k, gotLevel);
            }
        }
    }

    /* ensure that leading numerics in a paragraph are not treated as arabic
       numerals because of arabic text in a preceding paragraph
     */
    u_unescape(text2, src, MAXLEN);
    srcSize=u_strlen(src);
    ubidi_orderParagraphsLTR(pBidi, TRUE);
    ubidi_setPara(pBidi, src, srcSize, UBIDI_RTL, NULL, &errorCode);
    gotLevels=ubidi_getLevels(pBidi, &errorCode);
    for (i=0; i<srcSize; i++) {
        if (gotLevels[i]!=levels2[i]) {
            log_err("Checking leading numerics: for char %d(%04x), level=%d, expected=%d\n",
                    i, src[i], gotLevels[i], levels2[i]);
        }
    }

    /* check handling of whitespace before end of paragraph separator when
     * orderParagraphsLTR==TRUE, when last paragraph has, and lacks, a terminating B
     */
    u_memset(src, 0x0020, MAXLEN);
    srcSize = 5;
    ubidi_orderParagraphsLTR(pBidi, TRUE);
    for (i=0x001c; i<=0x0020; i+=(0x0020-0x001c)) {
        src[4]=i;                       /* with and without terminating B */
        for (j=0x0041; j<=0x05d0; j+=(0x05d0-0x0041)) {
            src[0]=j;                   /* leading 'A' or Alef */
            for (gotLevel=4; gotLevel<=5; gotLevel++) {
                /* test even and odd paraLevel */
                ubidi_setPara(pBidi, src, srcSize, gotLevel, NULL, &errorCode);
                gotLevels=ubidi_getLevels(pBidi, &errorCode);
                for (k=1; k<=3; k++) {
                    if (gotLevels[k]!=gotLevel) {
                        log_err("Checking trailing spaces: for leading_char=%04x, "
                                "last_char=%04x, index=%d, level=%d, expected=%d\n",
                                src[0], src[4], k, gotLevels[k], gotLevel);
                    }
                }
            }
        }
    }

    ubidi_close(pLine);
    ubidi_close(pBidi);
}


/* inverse BiDi ------------------------------------------------------------- */

static int countRoundtrips=0, countNonRoundtrips=0;

#define STRING_TEST_CASE(s) { (s), LENGTHOF(s) }

static void
doInverseBiDiTest() {
    static const UChar
        string0[]={ 0x6c, 0x61, 0x28, 0x74, 0x69, 0x6e, 0x20, 0x5d0, 0x5d1, 0x29, 0x5d2, 0x5d3 },
        string1[]={ 0x6c, 0x61, 0x74, 0x20, 0x5d0, 0x5d1, 0x5d2, 0x20, 0x31, 0x32, 0x33 },
        string2[]={ 0x6c, 0x61, 0x74, 0x20, 0x5d0, 0x28, 0x5d1, 0x5d2, 0x20, 0x31, 0x29, 0x32, 0x33 },
        string3[]={ 0x31, 0x32, 0x33, 0x20, 0x5d0, 0x5d1, 0x5d2, 0x20, 0x34, 0x35, 0x36 },
        string4[]={ 0x61, 0x62, 0x20, 0x61, 0x62, 0x20, 0x661, 0x662 };

    static const struct {
        const UChar *s;
        int32_t length;
    } testCases[]={
        STRING_TEST_CASE(string0),
        STRING_TEST_CASE(string1),
        STRING_TEST_CASE(string2),
        STRING_TEST_CASE(string3),
        STRING_TEST_CASE(string4)
    };

    UBiDi *pBiDi;
    UErrorCode errorCode;
    int i;

    pBiDi=ubidi_open();
    if(pBiDi==NULL) {
        log_err("unable to open a UBiDi object (out of memory)\n");
        return;
    }

    log_verbose("inverse BiDi: testInverseBiDi(L) with %u test cases ---\n", LENGTHOF(testCases));
     for(i=0; i<LENGTHOF(testCases); ++i) {
        errorCode=U_ZERO_ERROR;
        testInverseBiDi(pBiDi, testCases[i].s, testCases[i].length, 0, &errorCode);
    }

    log_verbose("inverse BiDi: testInverseBiDi(R) with %u test cases ---\n", LENGTHOF(testCases));
    for(i=0; i<LENGTHOF(testCases); ++i) {
        errorCode=U_ZERO_ERROR;
        testInverseBiDi(pBiDi, testCases[i].s, testCases[i].length, 1, &errorCode);
    }

    testManyInverseBiDi(pBiDi, 0);
    testManyInverseBiDi(pBiDi, 1);

    ubidi_close(pBiDi);

    log_verbose("inverse BiDi: rountrips: %5u\nnon-roundtrips: %5u\n", countRoundtrips, countNonRoundtrips);

    testWriteReverse();
}

#define COUNT_REPEAT_SEGMENTS 6

static const UChar repeatSegments[COUNT_REPEAT_SEGMENTS][2]={
    { 0x61, 0x62 },     /* L */
    { 0x5d0, 0x5d1 },   /* R */
    { 0x627, 0x628 },   /* AL */
    { 0x31, 0x32 },     /* EN */
    { 0x661, 0x662 },   /* AN */
    { 0x20, 0x20 }      /* WS (N) */
};

static void
testManyInverseBiDi(UBiDi *pBiDi, UBiDiLevel direction) {
    UChar text[8]={ 0, 0, 0x20, 0, 0, 0x20, 0, 0 };
    int i, j, k;
    UErrorCode errorCode;

    log_verbose("inverse BiDi: testManyInverseBiDi(%c) - test permutations of text snippets ---\n", direction==0 ? 'L' : 'R');
    for(i=0; i<COUNT_REPEAT_SEGMENTS; ++i) {
        text[0]=repeatSegments[i][0];
        text[1]=repeatSegments[i][1];
        for(j=0; j<COUNT_REPEAT_SEGMENTS; ++j) {
            text[3]=repeatSegments[j][0];
            text[4]=repeatSegments[j][1];
            for(k=0; k<COUNT_REPEAT_SEGMENTS; ++k) {
                text[6]=repeatSegments[k][0];
                text[7]=repeatSegments[k][1];

                errorCode=U_ZERO_ERROR;
                log_verbose("inverse BiDi: testManyInverseBiDi()[%u %u %u]\n", i, j, k);
                testInverseBiDi(pBiDi, text, 8, direction, &errorCode);
            }
        }
    }
}

static void
testInverseBiDi(UBiDi *pBiDi, const UChar *src, int32_t srcLength,
                UBiDiLevel direction, UErrorCode *pErrorCode) {
    UChar visualLTR[MAXLEN], logicalDest[MAXLEN], visualDest[MAXLEN];
    int32_t ltrLength, logicalLength, visualLength;

    if(direction==0) {
        log_verbose("inverse BiDi: testInverseBiDi(L)\n");

        /* convert visual to logical */
        ubidi_setInverse(pBiDi, TRUE);
        if (!ubidi_isInverse(pBiDi)) {
            log_err("Error while doing ubidi_setInverse(TRUE)\n");
        }
        ubidi_setPara(pBiDi, src, srcLength, 0, NULL, pErrorCode);
        if (src != ubidi_getText(pBiDi)) {
            log_err("Wrong value returned by ubidi_getText\n");
        }
        logicalLength=ubidi_writeReordered(pBiDi, logicalDest, LENGTHOF(logicalDest),
                                           UBIDI_DO_MIRRORING|UBIDI_INSERT_LRM_FOR_NUMERIC, pErrorCode);
        log_verbose("  v ");
        printUnicode(src, srcLength, ubidi_getLevels(pBiDi, pErrorCode));
        log_verbose("\n");

        /* convert back to visual LTR */
        ubidi_setInverse(pBiDi, FALSE);
        if (ubidi_isInverse(pBiDi)) {
            log_err("Error while doing ubidi_setInverse(FALSE)\n");
        }
        ubidi_setPara(pBiDi, logicalDest, logicalLength, 0, NULL, pErrorCode);
        visualLength=ubidi_writeReordered(pBiDi, visualDest, LENGTHOF(visualDest),
                                          UBIDI_DO_MIRRORING|UBIDI_REMOVE_BIDI_CONTROLS, pErrorCode);
    } else {
        log_verbose("inverse BiDi: testInverseBiDi(R)\n");

        /* reverse visual from RTL to LTR */
        ltrLength=ubidi_writeReverse(src, srcLength, visualLTR, LENGTHOF(visualLTR), 0, pErrorCode);
        log_verbose("  vr");
        printUnicode(src, srcLength, NULL);
        log_verbose("\n");

        /* convert visual RTL to logical */
        ubidi_setInverse(pBiDi, TRUE);
        ubidi_setPara(pBiDi, visualLTR, ltrLength, 0, NULL, pErrorCode);
        logicalLength=ubidi_writeReordered(pBiDi, logicalDest, LENGTHOF(logicalDest),
                                           UBIDI_DO_MIRRORING|UBIDI_INSERT_LRM_FOR_NUMERIC, pErrorCode);
        log_verbose("  vl");
        printUnicode(visualLTR, ltrLength, ubidi_getLevels(pBiDi, pErrorCode));
        log_verbose("\n");

        /* convert back to visual RTL */
        ubidi_setInverse(pBiDi, FALSE);
        ubidi_setPara(pBiDi, logicalDest, logicalLength, 0, NULL, pErrorCode);
        visualLength=ubidi_writeReordered(pBiDi, visualDest, LENGTHOF(visualDest),
                                          UBIDI_DO_MIRRORING|UBIDI_REMOVE_BIDI_CONTROLS|UBIDI_OUTPUT_REVERSE, pErrorCode);
    }
    log_verbose("  l ");
    printUnicode(logicalDest, logicalLength, ubidi_getLevels(pBiDi, pErrorCode));
    log_verbose("\n");
    log_verbose("  v ");
    printUnicode(visualDest, visualLength, NULL);
    log_verbose("\n");

    /* check and print results */
    if(U_FAILURE(*pErrorCode)) {
        log_err("inverse BiDi: *** error %s\n"
                "                 turn on verbose mode to see details\n", u_errorName(*pErrorCode));
    } else if(srcLength==visualLength && memcmp(src, visualDest, srcLength*U_SIZEOF_UCHAR)==0) {
        ++countRoundtrips;
        log_verbose(" + roundtripped\n");
    } else {
        ++countNonRoundtrips;
        log_verbose(" * did not roundtrip\n");
        log_err("inverse BiDi: transformation visual->logical->visual did not roundtrip the text;\n"
                "                 turn on verbose mode to see details\n");
    }
}

static void
testWriteReverse() {
    /* U+064e and U+0650 are combining marks (Mn) */
    static const UChar forward[]={
        0x200f, 0x627, 0x64e, 0x650, 0x20, 0x28, 0x31, 0x29
    }, reverseKeepCombining[]={
        0x29, 0x31, 0x28, 0x20, 0x627, 0x64e, 0x650, 0x200f
    }, reverseRemoveControlsKeepCombiningDoMirror[]={
        0x28, 0x31, 0x29, 0x20, 0x627, 0x64e, 0x650
    };
    UChar reverse[10];
    UErrorCode errorCode;
    int32_t length;

    /* test ubidi_writeReverse() with "interesting" options */
    errorCode=U_ZERO_ERROR;
    length=ubidi_writeReverse(forward, LENGTHOF(forward),
                              reverse, LENGTHOF(reverse),
                              UBIDI_KEEP_BASE_COMBINING,
                              &errorCode);
    if(U_FAILURE(errorCode) || length!=LENGTHOF(reverseKeepCombining) || memcmp(reverse, reverseKeepCombining, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in ubidi_writeReverse(UBIDI_KEEP_BASE_COMBINING): length=%d (should be %d), error code %s\n",
                length, LENGTHOF(reverseKeepCombining), u_errorName(errorCode));
    }

    memset(reverse, 0xa5, LENGTHOF(reverse)*U_SIZEOF_UCHAR);
    errorCode=U_ZERO_ERROR;
    length=ubidi_writeReverse(forward, LENGTHOF(forward),
                              reverse, LENGTHOF(reverse),
                              UBIDI_REMOVE_BIDI_CONTROLS|UBIDI_DO_MIRRORING|UBIDI_KEEP_BASE_COMBINING,
                              &errorCode);
    if(U_FAILURE(errorCode) || length!=LENGTHOF(reverseRemoveControlsKeepCombiningDoMirror) || memcmp(reverse, reverseRemoveControlsKeepCombiningDoMirror, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in ubidi_writeReverse(UBIDI_REMOVE_BIDI_CONTROLS|UBIDI_DO_MIRRORING|UBIDI_KEEP_BASE_COMBINING):\n"
                "    length=%d (should be %d), error code %s\n",
                length, LENGTHOF(reverseRemoveControlsKeepCombiningDoMirror), u_errorName(errorCode));
    }
}

/* arabic shaping ----------------------------------------------------------- */

static void
doArabicShapingTest() {
    static const UChar
    source[]={
        0x31,   /* en:1 */
        0x627,  /* arabic:alef */
        0x32,   /* en:2 */
        0x6f3,  /* an:3 */
        0x61,   /* latin:a */
        0x34,   /* en:4 */
        0
    }, en2an[]={
        0x661, 0x627, 0x662, 0x6f3, 0x61, 0x664, 0
    }, an2en[]={
        0x31, 0x627, 0x32, 0x33, 0x61, 0x34, 0
    }, logical_alen2an_init_lr[]={
        0x31, 0x627, 0x662, 0x6f3, 0x61, 0x34, 0
    }, logical_alen2an_init_al[]={
        0x6f1, 0x627, 0x6f2, 0x6f3, 0x61, 0x34, 0
    }, reverse_alen2an_init_lr[]={
        0x661, 0x627, 0x32, 0x6f3, 0x61, 0x34, 0
    }, reverse_alen2an_init_al[]={
        0x6f1, 0x627, 0x32, 0x6f3, 0x61, 0x6f4, 0
    };
    UChar dest[8];
    UErrorCode errorCode;
    int32_t length;

    /* test number shaping */

    /* european->arabic */
    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_DIGITS_EN2AN|U_SHAPE_DIGIT_TYPE_AN,
                         &errorCode);
    if(U_FAILURE(errorCode) || length!=LENGTHOF(source) || memcmp(dest, en2an, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(en2an)\n");
    }

    /* arabic->european */
    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, -1,
                         dest, LENGTHOF(dest),
                         U_SHAPE_DIGITS_AN2EN|U_SHAPE_DIGIT_TYPE_AN_EXTENDED,
                         &errorCode);
    if(U_FAILURE(errorCode) || length!=u_strlen(source) || memcmp(dest, an2en, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(an2en)\n");
    }

    /* european->arabic with context, logical order, initial state not AL */
    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_DIGITS_ALEN2AN_INIT_LR|U_SHAPE_DIGIT_TYPE_AN,
                         &errorCode);
    if(U_FAILURE(errorCode) || length!=LENGTHOF(source) || memcmp(dest, logical_alen2an_init_lr, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(logical_alen2an_init_lr)\n");
    }

    /* european->arabic with context, logical order, initial state AL */
    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_DIGITS_ALEN2AN_INIT_AL|U_SHAPE_DIGIT_TYPE_AN_EXTENDED,
                         &errorCode);
    if(U_FAILURE(errorCode) || length!=LENGTHOF(source) || memcmp(dest, logical_alen2an_init_al, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(logical_alen2an_init_al)\n");
    }

    /* european->arabic with context, reverse order, initial state not AL */
    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_DIGITS_ALEN2AN_INIT_LR|U_SHAPE_DIGIT_TYPE_AN|U_SHAPE_TEXT_DIRECTION_VISUAL_LTR,
                         &errorCode);
    if(U_FAILURE(errorCode) || length!=LENGTHOF(source) || memcmp(dest, reverse_alen2an_init_lr, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(reverse_alen2an_init_lr)\n");
    }

    /* european->arabic with context, reverse order, initial state AL */
    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_DIGITS_ALEN2AN_INIT_AL|U_SHAPE_DIGIT_TYPE_AN_EXTENDED|U_SHAPE_TEXT_DIRECTION_VISUAL_LTR,
                         &errorCode);
    if(U_FAILURE(errorCode) || length!=LENGTHOF(source) || memcmp(dest, reverse_alen2an_init_al, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(reverse_alen2an_init_al)\n");
    }

    /* test noop */
    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         0,
                         &errorCode);
    if(U_FAILURE(errorCode) || length!=LENGTHOF(source) || memcmp(dest, source, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(noop)\n");
    }

    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, 0,
                         dest, LENGTHOF(dest),
                         U_SHAPE_DIGITS_EN2AN|U_SHAPE_DIGIT_TYPE_AN,
                         &errorCode);
    if(U_FAILURE(errorCode) || length!=0) {
        log_err("failure in u_shapeArabic(en2an, sourceLength=0), returned %d/%s\n", u_errorName(errorCode), LENGTHOF(source));
    }

    /* preflight digit shaping */
    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, LENGTHOF(source),
                         NULL, 0,
                         U_SHAPE_DIGITS_EN2AN|U_SHAPE_DIGIT_TYPE_AN,
                         &errorCode);
    if(errorCode!=U_BUFFER_OVERFLOW_ERROR || length!=LENGTHOF(source)) {
        log_err("failure in u_shapeArabic(en2an preflighting), returned %d/%s instead of %d/U_BUFFER_OVERFLOW_ERROR\n",
                length, u_errorName(errorCode), LENGTHOF(source));
    }

    /* test illegal arguments */
    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(NULL, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_DIGITS_EN2AN|U_SHAPE_DIGIT_TYPE_AN,
                         &errorCode);
    if(errorCode!=U_ILLEGAL_ARGUMENT_ERROR) {
        log_err("failure in u_shapeArabic(source=NULL), returned %s instead of U_ILLEGAL_ARGUMENT_ERROR\n", u_errorName(errorCode));
    }

    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, -2,
                         dest, LENGTHOF(dest),
                         U_SHAPE_DIGITS_EN2AN|U_SHAPE_DIGIT_TYPE_AN,
                         &errorCode);
    if(errorCode!=U_ILLEGAL_ARGUMENT_ERROR) {
        log_err("failure in u_shapeArabic(sourceLength=-2), returned %s instead of U_ILLEGAL_ARGUMENT_ERROR\n", u_errorName(errorCode));
    }

    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, LENGTHOF(source),
                         NULL, LENGTHOF(dest),
                         U_SHAPE_DIGITS_EN2AN|U_SHAPE_DIGIT_TYPE_AN,
                         &errorCode);
    if(errorCode!=U_ILLEGAL_ARGUMENT_ERROR) {
        log_err("failure in u_shapeArabic(dest=NULL), returned %s instead of U_ILLEGAL_ARGUMENT_ERROR\n", u_errorName(errorCode));
    }

    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, -1,
                         U_SHAPE_DIGITS_EN2AN|U_SHAPE_DIGIT_TYPE_AN,
                         &errorCode);
    if(errorCode!=U_ILLEGAL_ARGUMENT_ERROR) {
        log_err("failure in u_shapeArabic(destSize=-1), returned %s instead of U_ILLEGAL_ARGUMENT_ERROR\n", u_errorName(errorCode));
    }

    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_DIGITS_RESERVED|U_SHAPE_DIGIT_TYPE_AN,
                         &errorCode);
    if(errorCode!=U_ILLEGAL_ARGUMENT_ERROR) {
        log_err("failure in u_shapeArabic(U_SHAPE_DIGITS_RESERVED), returned %s instead of U_ILLEGAL_ARGUMENT_ERROR\n", u_errorName(errorCode));
    }

    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_DIGITS_EN2AN|U_SHAPE_DIGIT_TYPE_RESERVED,
                         &errorCode);
    if(errorCode!=U_ILLEGAL_ARGUMENT_ERROR) {
        log_err("failure in u_shapeArabic(U_SHAPE_DIGIT_TYPE_RESERVED), returned %s instead of U_ILLEGAL_ARGUMENT_ERROR\n", u_errorName(errorCode));
    }

    errorCode=U_ZERO_ERROR;
    length=u_shapeArabic(source, LENGTHOF(source),
                         (UChar *)(source+2), LENGTHOF(dest), /* overlap source and destination */
                         U_SHAPE_DIGITS_EN2AN|U_SHAPE_DIGIT_TYPE_AN,
                         &errorCode);
    if(errorCode!=U_ILLEGAL_ARGUMENT_ERROR) {
        log_err("failure in u_shapeArabic(U_SHAPE_DIGIT_TYPE_RESERVED), returned %s instead of U_ILLEGAL_ARGUMENT_ERROR\n", u_errorName(errorCode));
    }
}

static void
doLamAlefSpecialVLTRArabicShapingTest() {
    static const UChar
    source[]={
/*a*/   0x20 ,0x646,0x622,0x644,0x627,0x20,
/*b*/   0x646,0x623,0x64E,0x644,0x627,0x20,
/*c*/   0x646,0x627,0x670,0x644,0x627,0x20,
/*d*/   0x646,0x622,0x653,0x644,0x627,0x20,
/*e*/   0x646,0x625,0x655,0x644,0x627,0x20,
/*f*/   0x646,0x622,0x654,0x644,0x627,0x20,
/*g*/   0xFEFC,0x639
    }, shape_near[]={
        0x20,0xfee5,0x20,0xfef5,0xfe8d,0x20,0xfee5,0x20,0xfe76,0xfef7,0xfe8d,0x20,
        0xfee5,0x20,0x670,0xfefb,0xfe8d,0x20,0xfee5,0x20,0x653,0xfef5,0xfe8d,0x20,
        0xfee5,0x20,0x655,0xfef9,0xfe8d,0x20,0xfee5,0x20,0x654,0xfef5,0xfe8d,0x20,
        0xfefc,0xfecb
    }, shape_at_end[]={
        0x20,0xfee5,0xfef5,0xfe8d,0x20,0xfee5,0xfe76,0xfef7,0xfe8d,0x20,0xfee5,0x670,
        0xfefb,0xfe8d,0x20,0xfee5,0x653,0xfef5,0xfe8d,0x20,0xfee5,0x655,0xfef9,0xfe8d,
        0x20,0xfee5,0x654,0xfef5,0xfe8d,0x20,0xfefc,0xfecb,0x20,0x20,0x20,0x20,0x20,0x20
    }, shape_at_begin[]={
        0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xfee5,0xfef5,0xfe8d,0x20,0xfee5,0xfe76,
        0xfef7,0xfe8d,0x20,0xfee5,0x670,0xfefb,0xfe8d,0x20,0xfee5,0x653,0xfef5,0xfe8d,
        0x20,0xfee5,0x655,0xfef9,0xfe8d,0x20,0xfee5,0x654,0xfef5,0xfe8d,0x20,0xfefc,0xfecb
    }, shape_grow_shrink[]={
        0x20,0xfee5,0xfef5,0xfe8d,0x20,0xfee5,0xfe76,0xfef7,0xfe8d,0x20,0xfee5,
        0x670,0xfefb,0xfe8d,0x20,0xfee5,0x653,0xfef5,0xfe8d,0x20,0xfee5,0x655,0xfef9,
        0xfe8d,0x20,0xfee5,0x654,0xfef5,0xfe8d,0x20,0xfefc,0xfecb
    }, shape_excepttashkeel_near[]={
        0x20,0xfee5,0x20,0xfef5,0xfe8d,0x20,0xfee5,0x20,0xfe76,0xfef7,0xfe8d,0x20,
        0xfee5,0x20,0x670,0xfefb,0xfe8d,0x20,0xfee5,0x20,0x653,0xfef5,0xfe8d,0x20,
        0xfee5,0x20,0x655,0xfef9,0xfe8d,0x20,0xfee5,0x20,0x654,0xfef5,0xfe8d,0x20,
        0xfefc,0xfecb
    }, shape_excepttashkeel_at_end[]={
        0x20,0xfee5,0xfef5,0xfe8d,0x20,0xfee5,0xfe76,0xfef7,0xfe8d,0x20,0xfee5,
        0x670,0xfefb,0xfe8d,0x20,0xfee5,0x653,0xfef5,0xfe8d,0x20,0xfee5,0x655,0xfef9,
        0xfe8d,0x20,0xfee5,0x654,0xfef5,0xfe8d,0x20,0xfefc,0xfecb,0x20,0x20,0x20,
        0x20,0x20,0x20
    }, shape_excepttashkeel_at_begin[]={
        0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xfee5,0xfef5,0xfe8d,0x20,0xfee5,0xfe76,
        0xfef7,0xfe8d,0x20,0xfee5,0x670,0xfefb,0xfe8d,0x20,0xfee5,0x653,0xfef5,0xfe8d,
        0x20,0xfee5,0x655,0xfef9,0xfe8d,0x20,0xfee5,0x654,0xfef5,0xfe8d,0x20,0xfefc,0xfecb
    }, shape_excepttashkeel_grow_shrink[]={
        0x20,0xfee5,0xfef5,0xfe8d,0x20,0xfee5,0xfe76,0xfef7,0xfe8d,0x20,0xfee5,0x670,
        0xfefb,0xfe8d,0x20,0xfee5,0x653,0xfef5,0xfe8d,0x20,0xfee5,0x655,0xfef9,0xfe8d,
        0x20,0xfee5,0x654,0xfef5,0xfe8d,0x20,0xfefc,0xfecb
    };

    UChar dest[38];
    UErrorCode errorCode;
    int32_t length;

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_SHAPE|U_SHAPE_LENGTH_FIXED_SPACES_NEAR|
                         U_SHAPE_TEXT_DIRECTION_VISUAL_LTR,
                         &errorCode);

    if(U_FAILURE(errorCode) || length!=LENGTHOF(shape_near) || memcmp(dest, shape_near, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(LAMALEF shape_near)\n");
    }

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_SHAPE|U_SHAPE_LENGTH_FIXED_SPACES_AT_END|
                         U_SHAPE_TEXT_DIRECTION_VISUAL_LTR,
                         &errorCode);

    if(U_FAILURE(errorCode) || length!=LENGTHOF(shape_at_end) || memcmp(dest, shape_at_end, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(LAMALEF shape_at_end)\n");
    }

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_SHAPE|U_SHAPE_LENGTH_FIXED_SPACES_AT_BEGINNING|
                         U_SHAPE_TEXT_DIRECTION_VISUAL_LTR,
                         &errorCode);

    if(U_FAILURE(errorCode) || length!=LENGTHOF(shape_at_begin) || memcmp(dest, shape_at_begin, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(LAMALEF shape_at_begin)\n");
    }

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_SHAPE|U_SHAPE_LENGTH_GROW_SHRINK|
                         U_SHAPE_TEXT_DIRECTION_VISUAL_LTR,
                         &errorCode);

    if(U_FAILURE(errorCode) || memcmp(dest, shape_grow_shrink, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(LAMALEF shape_grow_shrink)\n");
    }

    /* ==================== U_SHAPE_LETTERS_SHAPE_TASHKEEL_ISOLATED ==================== */

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_SHAPE_TASHKEEL_ISOLATED|U_SHAPE_LENGTH_FIXED_SPACES_NEAR|
                         U_SHAPE_TEXT_DIRECTION_VISUAL_LTR,
                         &errorCode);

    if(U_FAILURE(errorCode) || length!=LENGTHOF(shape_excepttashkeel_near) || memcmp(dest, shape_excepttashkeel_near, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(LAMALEF shape_excepttashkeel_near)\n");
    }

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_SHAPE_TASHKEEL_ISOLATED|U_SHAPE_LENGTH_FIXED_SPACES_AT_END|
                         U_SHAPE_TEXT_DIRECTION_VISUAL_LTR,
                         &errorCode);

    if(U_FAILURE(errorCode) || length!=LENGTHOF(shape_excepttashkeel_at_end) || memcmp(dest,shape_excepttashkeel_at_end , length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(LAMALEF shape_excepttashkeel_at_end)\n");
    }

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_SHAPE_TASHKEEL_ISOLATED|U_SHAPE_LENGTH_FIXED_SPACES_AT_BEGINNING|
                         U_SHAPE_TEXT_DIRECTION_VISUAL_LTR,
                         &errorCode);

    if(U_FAILURE(errorCode) || length!=LENGTHOF(shape_excepttashkeel_at_begin) || memcmp(dest, shape_excepttashkeel_at_begin, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(LAMALEF shape_excepttashkeel_at_begin)\n");
    }

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_SHAPE_TASHKEEL_ISOLATED|U_SHAPE_LENGTH_GROW_SHRINK|
                         U_SHAPE_TEXT_DIRECTION_VISUAL_LTR,
                         &errorCode);

    if(U_FAILURE(errorCode) || memcmp(dest, shape_excepttashkeel_grow_shrink, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(LAMALEF shape_excepttashkeel_grow_shrink)\n");
    }
}

static void
doTashkeelSpecialVLTRArabicShapingTest() {
    static const UChar
    source[]={
        0x64A,0x628,0x631,0x639,0x20,
        0x64A,0x628,0x651,0x631,0x64E,0x639,0x20,
        0x64C,0x64A,0x628,0x631,0x64F,0x639,0x20,
        0x628,0x670,0x631,0x670,0x639,0x20,
        0x628,0x653,0x631,0x653,0x639,0x20,
        0x628,0x654,0x631,0x654,0x639,0x20,
        0x628,0x655,0x631,0x655,0x639,0x20,
    }, shape_near[]={
        0xfef2,0xfe91,0xfeae,0xfecb,0x20,0xfef2,0xfe91,0xfe7c,0xfeae,0xfe77,0xfecb,
        0x20,0xfe72,0xfef2,0xfe91,0xfeae,0xfe79,0xfecb,0x20,0xfe8f,0x670,0xfeae,0x670,
        0xfecb,0x20,0xfe8f,0x653,0xfeae,0x653,0xfecb,0x20,0xfe8f,0x654,0xfeae,0x654,
        0xfecb,0x20,0xfe8f,0x655,0xfeae,0x655,0xfecb,0x20
    }, shape_excepttashkeel_near[]={
        0xfef2,0xfe91,0xfeae,0xfecb,0x20,0xfef2,0xfe91,0xfe7c,0xfeae,0xfe76,0xfecb,0x20,
        0xfe72,0xfef2,0xfe91,0xfeae,0xfe78,0xfecb,0x20,0xfe8f,0x670,0xfeae,0x670,0xfecb,
        0x20,0xfe8f,0x653,0xfeae,0x653,0xfecb,0x20,0xfe8f,0x654,0xfeae,0x654,0xfecb,0x20,
        0xfe8f,0x655,0xfeae,0x655,0xfecb,0x20
    };

    UChar dest[43];
    UErrorCode errorCode;
    int32_t length;

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_SHAPE|U_SHAPE_LENGTH_FIXED_SPACES_NEAR|
                         U_SHAPE_TEXT_DIRECTION_VISUAL_LTR,
                         &errorCode);

    if(U_FAILURE(errorCode) || length!=LENGTHOF(shape_near) || memcmp(dest, shape_near, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(TASHKEEL shape_near)\n");
    }

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_SHAPE_TASHKEEL_ISOLATED|U_SHAPE_LENGTH_FIXED_SPACES_NEAR|
                         U_SHAPE_TEXT_DIRECTION_VISUAL_LTR,
                         &errorCode);

    if(U_FAILURE(errorCode) || length!=LENGTHOF(shape_excepttashkeel_near) || memcmp(dest, shape_excepttashkeel_near, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(TASHKEEL shape_excepttashkeel_near)\n");
    }
}

static void
doLOGICALArabicDeShapingTest() {
    static const UChar
    source[]={
        0x0020,0x0020,0x0020,0xFE8D,0xFEF5,0x0020,0xFEE5,0x0020,0xFE8D,0xFEF7,0x0020,
        0xFED7,0xFEFC,0x0020,0xFEE1,0x0020,0xFE8D,0xFEDF,0xFECC,0xFEAE,0xFE91,0xFEF4,
        0xFE94,0x0020,0xFE8D,0xFEDF,0xFEA4,0xFEAE,0xFE93,0x0020,0x0020,0x0020,0x0020
    }, unshape_near[]={
        0x20,0x20,0x20,0x627,0x644,0x622,0x646,0x20,0x627,0x644,0x623,0x642,0x644,0x627,
        0x645,0x20,0x627,0x644,0x639,0x631,0x628,0x64a,0x629,0x20,0x627,0x644,0x62d,0x631,
        0x629,0x20,0x20,0x20,0x20
    }, unshape_at_end[]={
        0x20,0x20,0x20,0x627,0x644,0x622,0x20,0x646,0x20,0x627,0x644,0x623,0x20,0x642,
        0x644,0x627,0x20,0x645,0x20,0x627,0x644,0x639,0x631,0x628,0x64a,0x629,0x20,0x627,
        0x644,0x62d,0x631,0x629,0x20
    }, unshape_at_begin[]={
        0x627,0x644,0x622,0x20,0x646,0x20,0x627,0x644,0x623,0x20,0x642,0x644,0x627,0x20,
        0x645,0x20,0x627,0x644,0x639,0x631,0x628,0x64a,0x629,0x20,0x627,0x644,0x62d,0x631,
        0x629,0x20,0x20,0x20,0x20
    }, unshape_grow_shrink[]={
        0x20,0x20,0x20,0x627,0x644,0x622,0x20,0x646,0x20,0x627,0x644,0x623,0x20,0x642,
        0x644,0x627,0x20,0x645,0x20,0x627,0x644,0x639,0x631,0x628,0x64a,0x629,0x20,0x627,
        0x644,0x62d,0x631,0x629,0x20,0x20,0x20,0x20
    };

    UChar dest[36];
    UErrorCode errorCode;
    int32_t length;

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_UNSHAPE|U_SHAPE_LENGTH_FIXED_SPACES_NEAR|
                         U_SHAPE_TEXT_DIRECTION_LOGICAL,
                         &errorCode);

    if(U_FAILURE(errorCode) || length!=LENGTHOF(unshape_near) || memcmp(dest, unshape_near, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(unshape_near)\n");
    }

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_UNSHAPE|U_SHAPE_LENGTH_FIXED_SPACES_AT_END|
                         U_SHAPE_TEXT_DIRECTION_LOGICAL,
                         &errorCode);

    if(U_FAILURE(errorCode) || length!=LENGTHOF(unshape_at_end) || memcmp(dest, unshape_at_end, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(unshape_at_end)\n");
    }

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_UNSHAPE|U_SHAPE_LENGTH_FIXED_SPACES_AT_BEGINNING|
                         U_SHAPE_TEXT_DIRECTION_LOGICAL,
                         &errorCode);

    if(U_FAILURE(errorCode) || length!=LENGTHOF(unshape_at_begin) || memcmp(dest, unshape_at_begin, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(unshape_at_begin)\n");
    }

    errorCode=U_ZERO_ERROR;

    length=u_shapeArabic(source, LENGTHOF(source),
                         dest, LENGTHOF(dest),
                         U_SHAPE_LETTERS_UNSHAPE|U_SHAPE_LENGTH_GROW_SHRINK|
                         U_SHAPE_TEXT_DIRECTION_LOGICAL,
                         &errorCode);

    if(U_FAILURE(errorCode) || memcmp(dest, unshape_grow_shrink, length*U_SIZEOF_UCHAR)!=0) {
        log_err("failure in u_shapeArabic(unshape_grow_shrink)\n");
    }

}

/* helpers ------------------------------------------------------------------ */

static void
initCharFromDirProps() {
    static const UVersionInfo ucd401={ 4, 0, 1, 0 };
    static UVersionInfo ucdVersion={ 0, 0, 0, 0 };

    /* lazy initialization */
    if(ucdVersion[0]>0) {
        return;
    }

    u_getUnicodeVersion(ucdVersion);
    if(memcmp(ucdVersion, ucd401, sizeof(UVersionInfo))>=0) {
        /* Unicode 4.0.1 changes bidi classes for +-/ */
        charFromDirProp[U_EUROPEAN_NUMBER_SEPARATOR]=0x2b; /* change ES character from / to + */
    }
}

/* return a string with characters according to the desired directional properties */
static UChar *
getStringFromDirProps(const uint8_t *dirProps, int32_t length, UChar *buffer) {
    int32_t i;

    initCharFromDirProps();

    /* this part would have to be modified for UTF-x */
    for(i=0; i<length; ++i) {
        buffer[i]=charFromDirProp[dirProps[i]];
    }
    buffer[length]=0;
    return buffer;
}

static void
printUnicode(const UChar *s, int32_t length, const UBiDiLevel *levels) {
    int32_t i;

    log_verbose("{ ");
    for(i=0; i<length; ++i) {
        if(levels!=NULL) {
            log_verbose("%4x.%u  ", s[i], levels[i]);
        } else {
            log_verbose("%4x    ", s[i]);
        }
    }
    log_verbose(" }");
}

/* new BIDI API */

/* Reordering Mode BiDi --------------------------------------------------------- */

static const UBiDiLevel paraLevels[] = { UBIDI_LTR, UBIDI_RTL };

static UBool
assertSuccessful(const char* message, UErrorCode* rc) {
    if (rc != NULL && U_FAILURE(*rc)) {
        log_err("%s() failed with error %s.\n", message, myErrorName(*rc));
        return FALSE;
    }
    return TRUE;
}

static UBool
assertStringsEqual(const char* expected, const char* actual, const char* src,
                   const char* mode, const char* option, UBiDi* pBiDi) {
    if (uprv_strcmp(expected, actual)) {
        char formatChars[MAXLEN];
        log_err("\nActual and expected output mismatch.\n"
            "%20s %s\n%20s %s\n%20s %s\n%20s %s\n%20s %d %s\n%20s %u\n%20s %d %s\n",
            "Input:", src,
            "Actual output:", actual,
            "Expected output:", expected,
            "Levels:", formatLevels(pBiDi, formatChars),
            "Reordering mode:", ubidi_getReorderingMode(pBiDi), mode,
            "Paragraph level:", ubidi_getParaLevel(pBiDi),
            "Reordering option:", ubidi_getReorderingOptions(pBiDi), option);
        return FALSE;
    }
    return TRUE;
}

static UBiDi*
getBiDiObject(void) {
    UBiDi* pBiDi = ubidi_open();
    if (pBiDi == NULL) {
        log_err("Unable to allocate a UBiDi object. Tests are skipped.\n");
    }
    return pBiDi;
}

#define MAKE_ITEMS(val) val, #val

static const struct {
    uint32_t value;
    const char* description;
}
modes[] = {
    { MAKE_ITEMS(UBIDI_REORDER_GROUP_NUMBERS_WITH_R) },
    { MAKE_ITEMS(UBIDI_REORDER_INVERSE_LIKE_DIRECT) },
    { MAKE_ITEMS(UBIDI_REORDER_NUMBERS_SPECIAL) },
    { MAKE_ITEMS(UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL) },
    { MAKE_ITEMS(UBIDI_REORDER_INVERSE_NUMBERS_AS_L) }
},
options[] = {
    { MAKE_ITEMS(UBIDI_OPTION_INSERT_MARKS) },
    { MAKE_ITEMS(0) }
};

#define TC_COUNT                LENGTHOF(textIn)
#define MODES_COUNT             LENGTHOF(modes)
#define OPTIONS_COUNT           LENGTHOF(options)
#define LEVELS_COUNT            LENGTHOF(paraLevels)

static const char* const textIn[] = {
/* (0) 123 */
    "123",
/* (1) .123->4.5 */
    ".123->4.5",
/* (2) 678 */
    "678",
/* (3) .678->8.9 */
    ".678->8.9",
/* (4) JIH1.2,3MLK */
    "JIH1.2,3MLK",
/* (5) FE.>12-> */
    "FE.>12->",
/* (6) JIH.>12->a */
    "JIH.>12->a",
/* (7) CBA.>67->89=a */
    "CBA.>67->89=a",
/* (8) CBA.123->xyz */
    "CBA.123->xyz",
/* (9) .>12->xyz */
    ".>12->xyz",
/* (10) a.>67->xyz */
    "a.>67->xyz",
/* (11) 123JIH */
    "123JIH",
/* (12) 123 JIH */
    "123 JIH"
};

static const char* const textOut[] = {
/* TC 0: 123 */
    "123",                                                              /* (0) */
/* TC 1: .123->4.5 */
    ".123->4.5",                                                        /* (1) */
    "4.5<-123.",                                                        /* (2) */
/* TC 2: 678 */
    "678",                                                              /* (3) */
/* TC 3: .678->8.9 */
    ".8.9<-678",                                                        /* (4) */
    "8.9<-678.",                                                        /* (5) */
    ".678->8.9",                                                        /* (6) */
/* TC 4: MLK1.2,3JIH */
    "KLM1.2,3HIJ",                                                      /* (7) */
/* TC 5: FE.>12-> */
    "12<.EF->",                                                         /* (8) */
    "<-12<.EF",                                                         /* (9) */
    "EF.>@12->",                                                        /* (10) */
/* TC 6: JIH.>12->a */
    "12<.HIJ->a",                                                       /* (11) */
    "a<-12<.HIJ",                                                       /* (12) */
    "HIJ.>@12->a",                                                      /* (13) */
    "a&<-12<.HIJ",                                                      /* (14) */
/* TC 7: CBA.>67->89=a */
    "ABC.>@67->89=a",                                                   /* (15) */
    "a=89<-67<.ABC",                                                    /* (16) */
    "a&=89<-67<.ABC",                                                   /* (17) */
    "89<-67<.ABC=a",                                                    /* (18) */
/* TC 8: CBA.123->xyz */
    "123.ABC->xyz",                                                     /* (19) */
    "xyz<-123.ABC",                                                     /* (20) */
    "ABC.@123->xyz",                                                    /* (21) */
    "xyz&<-123.ABC",                                                    /* (22) */
/* TC 9: .>12->xyz */
    ".>12->xyz",                                                        /* (23) */
    "xyz<-12<.",                                                        /* (24) */
    "xyz&<-12<.",                                                       /* (25) */
/* TC 10: a.>67->xyz */
    "a.>67->xyz",                                                       /* (26) */
    "a.>@67@->xyz",                                                     /* (27) */
    "xyz<-67<.a",                                                       /* (28) */
/* TC 11: 123JIH */
    "123HIJ",                                                           /* (29) */
    "HIJ123",                                                           /* (30) */
/* TC 12: 123 JIH */
    "123 HIJ",                                                          /* (31) */
    "HIJ 123",                                                          /* (32) */
};

#define NO                  UBIDI_MAP_NOWHERE
#define MAX_MAP_LENGTH      20

static const int32_t forwardMap[][MAX_MAP_LENGTH] = {
/* TC 0: 123 */
    { 0, 1, 2 },                                                        /* (0) */
/* TC 1: .123->4.5 */
    { 0, 1, 2, 3, 4, 5, 6, 7, 8 },                                      /* (1) */
    { 8, 5, 6, 7, 4, 3, 0, 1, 2 },                                      /* (2) */
/* TC 2: 678 */
    { 0, 1, 2 },                                                        /* (3) */
/* TC 3: .678->8.9 */
    { 0, 6, 7, 8, 5, 4, 1, 2, 3 },                                      /* (4) */
    { 8, 5, 6, 7, 4, 3, 0, 1, 2 },                                      /* (5) */
    { 0, 1, 2, 3, 4, 5, 6, 7, 8 },                                      /* (6) */
/* TC 4: MLK1.2,3JIH */
    { 10, 9, 8, 3, 4, 5, 6, 7, 2, 1, 0 },                               /* (7) */
/* TC 5: FE.>12-> */
    { 5, 4, 3, 2, 0, 1, 6, 7 },                                         /* (8) */
    { 7, 6, 5, 4, 2, 3, 1, 0 },                                         /* (9) */
    { 1, 0, 2, 3, 5, 6, 7, 8 },                                         /* (10) */
/* TC 6: JIH.>12->a */
    { 6, 5, 4, 3, 2, 0, 1, 7, 8, 9 },                                   /* (11) */
    { 9, 8, 7, 6, 5, 3, 4, 2, 1, 0 },                                   /* (12) */
    { 2, 1, 0, 3, 4, 6, 7, 8, 9, 10 },                                  /* (13) */
    { 10, 9, 8, 7, 6, 4, 5, 3, 2, 0 },                                  /* (14) */
/* TC 7: CBA.>67->89=a */
    { 2, 1, 0, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 },                      /* (15) */
    { 12, 11, 10, 9, 8, 6, 7, 5, 4, 2, 3, 1, 0 },                       /* (16) */
    { 13, 12, 11, 10, 9, 7, 8, 6, 5, 3, 4, 2, 0 },                      /* (17) */
    { 10, 9, 8, 7, 6, 4, 5, 3, 2, 0, 1, 11, 12 },                       /* (18) */
/* TC 8: CBA.123->xyz */
    { 6, 5, 4, 3, 0, 1, 2, 7, 8, 9, 10, 11 },                           /* (19) */
    { 11, 10, 9, 8, 5, 6, 7, 4, 3, 0, 1, 2 },                           /* (20) */
    { 2, 1, 0, 3, 5, 6, 7, 8, 9, 10, 11, 12 },                          /* (21) */
    { 12, 11, 10, 9, 6, 7, 8, 5, 4, 0, 1, 2 },                          /* (22) */
/* TC 9: .>12->xyz */
    { 0, 1, 2, 3, 4, 5, 6, 7, 8 },                                      /* (23) */
    { 8, 7, 5, 6, 4, 3, 0, 1, 2 },                                      /* (24) */
    { 9, 8, 6, 7, 5, 4, 0, 1, 2 },                                      /* (25) */
/* TC 10: a.>67->xyz */
    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },                                   /* (26) */
    { 0, 1, 2, 4, 5, 7, 8, 9, 10, 11 },                                 /* (27) */
    { 9, 8, 7, 5, 6, 4, 3, 0, 1, 2 },                                   /* (28) */
/* TC 11: 123JIH */
    { 0, 1, 2, 5, 4, 3 },                                               /* (29) */
    { 3, 4, 5, 2, 1, 0 },                                               /* (30) */
/* TC 12: 123 JIH */
    { 0, 1, 2, 3, 6, 5, 4 },                                            /* (31) */
    { 4, 5, 6, 3, 2, 1, 0 },                                            /* (32) */
};

static const int32_t inverseMap[][MAX_MAP_LENGTH] = {
/* TC 0: 123 */
    { 0, 1, 2 },                                                        /* (0) */
/* TC 1: .123->4.5 */
    { 0, 1, 2, 3, 4, 5, 6, 7, 8 },                                      /* (1) */
    { 6, 7, 8, 5, 4, 1, 2, 3, 0 },                                      /* (2) */
/* TC 2: 678 */
    { 0, 1, 2 },                                                        /* (3) */
/* TC 3: .678->8.9 */
    { 0, 6, 7, 8, 5, 4, 1, 2, 3 },                                      /* (4) */
    { 6, 7, 8, 5, 4, 1, 2, 3, 0 },                                      /* (5) */
    { 0, 1, 2, 3, 4, 5, 6, 7, 8 },                                      /* (6) */
/* TC 4: MLK1.2,3JIH */
    { 10, 9, 8, 3, 4, 5, 6, 7, 2, 1, 0 },                               /* (7) */
/* TC 5: FE.>12-> */
    { 4, 5, 3, 2, 1, 0, 6, 7 },                                         /* (8) */
    { 7, 6, 4, 5, 3, 2, 1, 0 },                                         /* (9) */
    { 1, 0, 2, 3, NO, 4, 5, 6, 7 },                                     /* (10) */
/* TC 6: JIH.>12->a */
    { 5, 6, 4, 3, 2, 1, 0, 7, 8, 9 },                                   /* (11) */
    { 9, 8, 7, 5, 6, 4, 3, 2, 1, 0 },                                   /* (12) */
    { 2, 1, 0, 3, 4, NO, 5, 6, 7, 8, 9 },                               /* (13) */
    { 9, NO, 8, 7, 5, 6, 4, 3, 2, 1, 0 },                               /* (14) */
/* TC 7: CBA.>67->89=a */
    { 2, 1, 0, 3, 4, NO, 5, 6, 7, 8, 9, 10, 11, 12 },                   /* (15) */
    { 12, 11, 9, 10, 8, 7, 5, 6, 4, 3, 2, 1, 0 },                       /* (16) */
    { 12, NO, 11, 9, 10, 8, 7, 5, 6, 4, 3, 2, 1, 0 },                   /* (17) */
    { 9, 10, 8, 7, 5, 6, 4, 3, 2, 1, 0, 11, 12 },                       /* (18) */
/* TC 8: CBA.123->xyz */
    { 4, 5, 6, 3, 2, 1, 0, 7, 8, 9, 10, 11 },                           /* (19) */
    { 9, 10, 11, 8, 7, 4, 5, 6, 3, 2, 1, 0 },                           /* (20) */
    { 2, 1, 0, 3, NO, 4, 5, 6, 7, 8, 9, 10, 11 },                       /* (21) */
    { 9, 10, 11, NO, 8, 7, 4, 5, 6, 3, 2, 1, 0 },                       /* (22) */
/* TC 9: .>12->xyz */
    { 0, 1, 2, 3, 4, 5, 6, 7, 8 },                                      /* (23) */
    { 6, 7, 8, 5, 4, 2, 3, 1, 0 },                                      /* (24) */
    { 6, 7, 8, NO, 5, 4, 2, 3, 1, 0 },                                  /* (25) */
/* TC 10: a.>67->xyz */
    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },                                   /* (26) */
    { 0, 1, 2, NO, 3, 4, NO, 5, 6, 7, 8, 9 },                           /* (27) */
    { 7, 8, 9, 6, 5, 3, 4, 2, 1, 0 },                                   /* (28) */
/* TC 11: 123JIH */
    { 0, 1, 2, 5, 4, 3 },                                               /* (29) */
    { 5, 4, 3, 0, 1, 2 },                                               /* (30) */
/* TC 12: 123 JIH */
    { 0, 1, 2, 3, 6, 5, 4 },                                            /* (31) */
    { 6, 5, 4, 3, 0, 1, 2 },                                            /* (32) */
};

static const char outIndices[TC_COUNT][MODES_COUNT - 1][OPTIONS_COUNT]
            [LEVELS_COUNT] = {
    { /* TC 0: 123 */
        {{ 0,  0}, { 0,  0}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{ 0,  0}, { 0,  0}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{ 0,  0}, { 0,  0}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{ 0,  0}, { 0,  0}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    },
    { /* TC 1: .123->4.5 */
        {{ 1,  2}, { 1,  2}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{ 1,  2}, { 1,  2}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{ 1,  2}, { 1,  2}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{ 1,  2}, { 1,  2}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    },
    { /* TC 2: 678 */
        {{ 3,  3}, { 3,  3}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{ 3,  3}, { 3,  3}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{ 3,  3}, { 3,  3}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{ 3,  3}, { 3,  3}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    },
    { /* TC 3: .678->8.9 */
        {{ 6,  5}, { 6,  5}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{ 4,  5}, { 4,  5}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{ 6,  5}, { 6,  5}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{ 6,  5}, { 6,  5}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    },
    { /* TC 4: MLK1.2,3JIH */
        {{ 7,  7}, { 7,  7}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{ 7,  7}, { 7,  7}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{ 7,  7}, { 7,  7}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{ 7,  7}, { 7,  7}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    },
    { /* TC 5: FE.>12-> */
        {{ 8,  9}, { 8,  9}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{10,  9}, { 8,  9}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{ 8,  9}, { 8,  9}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{10,  9}, { 8,  9}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    },
    { /* TC 6: JIH.>12->a */
        {{11, 12}, {11, 12}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{13, 14}, {11, 12}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{11, 12}, {11, 12}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{13, 14}, {11, 12}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    },
    { /* TC 7: CBA.>67->89=a */
        {{18, 16}, {18, 16}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{18, 17}, {18, 16}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{18, 16}, {18, 16}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{15, 17}, {18, 16}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    },
    { /* TC 8: CBA.>124->xyz */
        {{19, 20}, {19, 20}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{21, 22}, {19, 20}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{19, 20}, {19, 20}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{21, 22}, {19, 20}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    },
    { /* TC 9: .>12->xyz */
        {{23, 24}, {23, 24}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{23, 25}, {23, 24}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{23, 24}, {23, 24}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{23, 25}, {23, 24}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    },
    { /* TC 10: a.>67->xyz */
        {{26, 26}, {26, 26}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{26, 27}, {26, 28}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{26, 28}, {26, 28}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{26, 27}, {26, 28}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    },
    { /* TC 11: 124JIH */
        {{30, 30}, {30, 30}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{29, 30}, {29, 30}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{30, 30}, {30, 30}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{30, 30}, {30, 30}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    },
    { /* TC 12: 124 JIH */
        {{32, 32}, {32, 32}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
        {{31, 32}, {31, 32}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
        {{31, 32}, {31, 32}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
        {{31, 32}, {31, 32}}  /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
    }
};

static UBool
assertRoundTrip(UBiDi *pBiDi, int32_t tc, int32_t outIndex, const char *srcChars,
                const char *destChars, const UChar *dest, int32_t destLen,
                int mode, int option, UBiDiLevel level) {

    static const char roundtrip[TC_COUNT][MODES_COUNT][OPTIONS_COUNT]
                [LEVELS_COUNT] = {
        { /* TC 0: 123 */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        },
        { /* TC 1: .123->4.5 */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        },
        { /* TC 2: 678 */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        },
        { /* TC 3: .678->8.9 */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 0,  0}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        },
        { /* TC 4: MLK1.2,3JIH */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        },
        { /* TC 5: FE.>12-> */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 0,  1}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        },
        { /* TC 6: JIH.>12->a */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 0,  0}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        },
        { /* TC 7: CBA.>67->89=a */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 0,  1}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 0,  0}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        },
        { /* TC 8: CBA.>123->xyz */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 0,  0}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        },
        { /* TC 9: .>12->xyz */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 1,  0}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        },
        { /* TC 10: a.>67->xyz */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 1,  0}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        },
        { /* TC 11: 123JIH */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        },
        { /* TC 12: 123 JIH */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_GROUP_NUMBERS_WITH_R */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_LIKE_DIRECT */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}, /* UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL */
            {{ 1,  1}, { 1,  1}}  /* UBIDI_REORDER_INVERSE_NUMBERS_AS_L */
        }
    };

    #define SET_ROUND_TRIP_MODE(mode) \
        ubidi_setReorderingMode(pBiDi, mode); \
        desc = #mode; \
        break;

    UErrorCode rc = U_ZERO_ERROR;
    UChar dest2[MAXLEN];
    int32_t destLen2;
    const char* desc;
    char destChars2[MAXLEN];
    char destChars3[MAXLEN];

    switch (modes[mode].value) {
        case UBIDI_REORDER_NUMBERS_SPECIAL:
            SET_ROUND_TRIP_MODE(UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL)
        case UBIDI_REORDER_GROUP_NUMBERS_WITH_R:
            SET_ROUND_TRIP_MODE(UBIDI_REORDER_GROUP_NUMBERS_WITH_R)
        case UBIDI_REORDER_RUNS_ONLY:
            SET_ROUND_TRIP_MODE(UBIDI_REORDER_RUNS_ONLY)
        case UBIDI_REORDER_INVERSE_NUMBERS_AS_L:
            SET_ROUND_TRIP_MODE(UBIDI_REORDER_DEFAULT)
        case UBIDI_REORDER_INVERSE_LIKE_DIRECT:
            SET_ROUND_TRIP_MODE(UBIDI_REORDER_DEFAULT)
        case UBIDI_REORDER_INVERSE_FOR_NUMBERS_SPECIAL:
            SET_ROUND_TRIP_MODE(UBIDI_REORDER_NUMBERS_SPECIAL)
        default:
            SET_ROUND_TRIP_MODE(UBIDI_REORDER_INVERSE_LIKE_DIRECT)
    }
    ubidi_setReorderingOptions(pBiDi, UBIDI_OPTION_REMOVE_CONTROLS);

    ubidi_setPara(pBiDi, dest, destLen, level, NULL, &rc);
    assertSuccessful("ubidi_setPara", &rc);
    *dest2 = 0;
    destLen2 = ubidi_writeReordered(pBiDi, dest2, MAXLEN, UBIDI_DO_MIRRORING,
                                    &rc);
    assertSuccessful("ubidi_writeReordered", &rc);

    u16ToPseudo(destLen, dest, destChars3);
    u16ToPseudo(destLen2, dest2, destChars2);
    if (strcmp(srcChars, destChars2)) {
        if (roundtrip[tc][mode][option][level]) {
            log_err("\nRound trip failed for case=%d mode=%d option=%d.\n"
                    "%20s %s\n%20s %s\n%20s %s\n%20s %s\n%20s %s"
                    "\n%20s %u\n", tc, mode, option,
                    "Original text:", srcChars,
                    "Round-tripped text:", destChars2,
                    "Intermediate  text:", destChars3,
                    "Reordering mode:", modes[mode].description,
                    "Reordering option:", options[option].description,
                    "Paragraph level:", level);
        }
        else {
            log_verbose("\nExpected round trip failure for case=%d mode=%d option=%d.\n"
                    "%20s %s\n%20s %s\n%20s %s\n%20s %s\n%20s %s"
                    "\n%20s %u\n", tc, mode, option,
                    "Original text:", srcChars,
                    "Round-tripped text:", destChars2,
                    "Intermediate  text:", destChars3,
                    "Reordering mode:", modes[mode].description,
                    "Reordering option:", options[option].description,
                    "Paragraph level:", level);
        }
        return FALSE;
    }
    if (!checkResultLength(pBiDi, destChars, destChars2, dest2, destLen2,
                           desc, "UBIDI_OPTION_REMOVE_CONTROLS", level)) {
        return FALSE;
    }
    if (outIndex > -1 && !testMaps(pBiDi, outIndex, srcChars, destChars,
                                      desc, "UBIDI_OPTION_REMOVE_CONTROLS",
                                      level, FALSE)) {
        return FALSE;
    }
    return TRUE;
}

static UBool
checkResultLength(UBiDi *pBiDi, const char *srcChars, const char *destChars,
                  const UChar *dest, int32_t destLen, const char* mode,
                  const char* option, UBiDiLevel level) {
    int32_t actualLen;
    if (strcmp(mode, "UBIDI_REORDER_INVERSE_NUMBERS_AS_L") == 0)
        actualLen = strlen(destChars);
    else
        actualLen = ubidi_getResultLength(pBiDi);
    if (actualLen != destLen) {
        log_err("\nubidi_getResultLength failed.\n%20s %7d\n%20s %7d\n"
                "%20s %s\n%20s %s\n%20s %s\n%20s %s\n%20s %u\n",
                "Expected:", destLen, "Actual:", actualLen,
                "Input:", srcChars, "Output:", destChars,
                "Reordering mode:", mode, "Reordering option:", option,
                "Paragraph level:", level);
        return FALSE;
    }
    return TRUE;
}

static void
doReorderRunsTest(void) {
    static const struct {
        const char* textIn;
        const char* textOut[2][2];
        const char noroundtrip[2];
    } testCases[] = {
        {"ab 234 896 de", {{"de 896 ab 234", "de 896 ab 234"},
                           {"ab 234 @896@ de", "de 896 ab 234"}}, {0, 0}},
        {"abcGHI", {{"GHIabc", "GHIabc"}, {"GHIabc", "GHIabc"}}, {0, 0}},
        {"a.>67->", {{"<-67<.a", "<-67<.a"}, {"<-67<.a", "<-67<.a"}}, {0, 0}},
        {"-=%$123/ *", {{"* /%$123=-", "* /%$123=-"},
                        {"* /%$123=-", "* /%$123=-"}}, {0, 0}},
        {"abc->12..>JKL", {{"JKL<..12<-abc", "JKL<..abc->12"},
                           {"JKL<..12<-abc", "JKL<..abc->12"}}, {0, 0}},
        {"JKL->12..>abc", {{"abc<..JKL->12", "abc<..12<-JKL"},
                           {"abc<..JKL->12", "abc<..12<-JKL"}}, {0, 0}},
        {"123->abc", {{"abc<-123", "abc<-123"},
                      {"abc&<-123", "abc<-123"}}, {1, 0}},
        {"123->JKL", {{"JKL<-123", "123->JKL"},
                      {"JKL<-123", "JKL<-@123"}}, {0, 1}},
        {"*>12.>34->JKL", {{"JKL<-34<.12<*", "12.>34->JKL<*"},
                           {"JKL<-34<.12<*", "JKL<-@34<.12<*"}}, {0, 1}},
        {"*>67.>89->JKL", {{"67.>89->JKL<*", "67.>89->JKL<*"},
                           {"67.>89->JKL<*", "67.>89->JKL<*"}}, {0, 0}},
        {"* /abc-=$%123", {{"$%123=-abc/ *", "abc-=$%123/ *"},
                           {"$%123=-abc/ *", "abc-=$%123/ *"}}, {0, 0}},
        {"* /$%def-=123", {{"123=-def%$/ *", "def-=123%$/ *"},
                           {"123=-def%$/ *", "def-=123%$/ *"}}, {0, 0}},
        {"-=GHI* /123%$", {{"GHI* /123%$=-", "123%$/ *GHI=-"},
                           {"GHI* /123%$=-", "123%$/ *GHI=-"}}, {0, 0}},
        {"-=%$JKL* /123", {{"JKL* /%$123=-", "123/ *JKL$%=-"},
                           {"JKL* /%$123=-", "123/ *JKL$%=-"}}, {0, 0}},
        {"ab =#CD *?450", {{"CD *?450#= ab", "450?* CD#= ab"},
                           {"CD *?450#= ab", "450?* CD#= ab"}}, {0, 0}},
        {"ab 234 896 de", {{"de 896 ab 234", "de 896 ab 234"},
                           {"ab 234 @896@ de", "de 896 ab 234"}}, {0, 0}},
        {"abc-=%$LMN* /123", {{"LMN* /%$123=-abc", "123/ *LMN$%=-abc"},
                              {"LMN* /%$123=-abc", "123/ *LMN$%=-abc"}}, {0, 0}},
        {"123->JKL&MN&P", {{"JKLMNP<-123", "123->JKLMNP"},
                           {"JKLMNP<-123", "JKLMNP<-@123"}}, {0, 1}}
    };
    UBiDi *pBiDi = getBiDiObject();
    UBiDi *pL2VBiDi = getBiDiObject();
    UChar src[MAXLEN], dest[MAXLEN], visual1[MAXLEN], visual2[MAXLEN];
    char destChars[MAXLEN], vis1Chars[MAXLEN], vis2Chars[MAXLEN];
    int32_t srcLen, destLen, vis1Len, vis2Len, option, i, j, nCases;
    UErrorCode rc = U_ZERO_ERROR;
    UBiDiLevel level;
    if(!pL2VBiDi) {
        ubidi_close(pBiDi);             /* in case this one was allocated */
        return;
    }
    ubidi_setReorderingMode(pBiDi, UBIDI_REORDER_RUNS_ONLY);
    ubidi_setReorderingOptions(pL2VBiDi, UBIDI_OPTION_REMOVE_CONTROLS);

    for (option = 0; option < 2; option++) {
        ubidi_setReorderingOptions(pBiDi, option==0 ? UBIDI_OPTION_REMOVE_CONTROLS
                                                    : UBIDI_OPTION_INSERT_MARKS);
        for (i = 0, nCases = LENGTHOF(testCases); i < nCases; i++) {
            srcLen = strlen(testCases[i].textIn);
            pseudoToU16(srcLen, testCases[i].textIn, src);
            for(j = 0; j < 2; j++) {
                level = paraLevels[j];
                ubidi_setPara(pBiDi, src, srcLen, level, NULL, &rc);
                assertSuccessful("ubidi_setPara", &rc);
                *dest = 0;
                destLen = ubidi_writeReordered(pBiDi, dest, MAXLEN, UBIDI_DO_MIRRORING, &rc);
                assertSuccessful("ubidi_writeReordered", &rc);
                u16ToPseudo(destLen, dest, destChars);
                assertStringsEqual(testCases[i].textOut[option][level], destChars,
                        testCases[i].textIn, "UBIDI_REORDER_RUNS_ONLY",
                        option==0 ? "0" : "UBIDI_OPTION_INSERT_MARKS",
                        pBiDi);

                if((option==0) && testCases[i].noroundtrip[level]) {
                    continue;
                }
                ubidi_setPara(pL2VBiDi, src, srcLen, level, NULL, &rc);
                assertSuccessful("ubidi_setPara1", &rc);
                *visual1 = 0;
                vis1Len = ubidi_writeReordered(pL2VBiDi, visual1, MAXLEN, UBIDI_DO_MIRRORING, &rc);
                assertSuccessful("ubidi_writeReordered1", &rc);
                u16ToPseudo(vis1Len, visual1, vis1Chars);
                ubidi_setPara(pL2VBiDi, dest, destLen, level^1, NULL, &rc);
                assertSuccessful("ubidi_setPara2", &rc);
                *visual2 = 0;
                vis2Len = ubidi_writeReordered(pL2VBiDi, visual2, MAXLEN, UBIDI_DO_MIRRORING, &rc);
                assertSuccessful("ubidi_writeReordered2", &rc);
                u16ToPseudo(vis2Len, visual2, vis2Chars);
                assertStringsEqual(vis1Chars, vis2Chars,
                        testCases[i].textIn, "UBIDI_REORDER_RUNS_ONLY (2)",
                        option==0 ? "0" : "UBIDI_OPTION_INSERT_MARKS",
                        pBiDi);
            }
        }
    }
    ubidi_close(pBiDi);
    ubidi_close(pL2VBiDi);
}

static void
doReorderingModeBidiTest() {

    UChar src[MAXLEN], dest[MAXLEN];
    char destChars[MAXLEN];
    UBiDi *pBiDi = NULL, *pBiDi2 = NULL, *pBiDi3 = NULL;
    UErrorCode rc;
    int tc, mode, option, level;
    uint32_t modeValue, modeBack, optionValue, optionBack;
    int32_t srcLen, destLen, index;
    const char *expectedChars;
    UBool testOK = TRUE;

    log_verbose("\n*** Bidi reordering mode test ***\n");

    pBiDi = getBiDiObject();
    pBiDi2 = getBiDiObject();
    pBiDi3 = getBiDiObject();
    if(!pBiDi3) {
        ubidi_close(pBiDi);             /* in case this one was allocated */
        ubidi_close(pBiDi2);            /* in case this one was allocated */
        return;
    }

    ubidi_setInverse(pBiDi2, TRUE);

    for (tc = 0; tc < TC_COUNT; tc++) {
        const char* srcChars = textIn[tc];
        srcLen = strlen(srcChars);
        pseudoToU16(srcLen, srcChars, src);

        for (mode = 0; mode < MODES_COUNT; mode++) {
            modeValue = modes[mode].value;
            ubidi_setReorderingMode(pBiDi, modeValue);
            modeBack = ubidi_getReorderingMode(pBiDi);
            if (modeValue != modeBack) {
                log_err("Error while setting reordering mode to %d, returned %d\n",
                        modeValue, modeBack);
            }

            for (option = 0; option < OPTIONS_COUNT; option++) {
                optionValue = options[option].value;
                ubidi_setReorderingOptions(pBiDi, optionValue);
                optionBack = ubidi_getReorderingOptions(pBiDi);
                if (optionValue != optionBack) {
                    log_err("Error while setting reordering option to %d, returned %d\n",
                            optionValue, optionBack);
                }

                for (level = 0; level < LEVELS_COUNT; level++) {
                    log_verbose("starting test %d mode=%d option=%d level=%d\n",
                                tc, modes[mode].value, options[option].value, level);
                    rc = U_ZERO_ERROR;
                    ubidi_setPara(pBiDi, src, srcLen, paraLevels[level], NULL, &rc);
                    assertSuccessful("ubidi_setPara", &rc);

                    *dest = 0;
                    destLen = ubidi_writeReordered(pBiDi, dest, MAXLEN,
                                                   UBIDI_DO_MIRRORING, &rc);
                    assertSuccessful("ubidi_writeReordered", &rc);
                    u16ToPseudo(destLen, dest, destChars);

                    if (modes[mode].value == UBIDI_REORDER_INVERSE_NUMBERS_AS_L) {
                        index = -1;
                        expectedChars = inverseBasic(pBiDi2, src, srcLen,
                                options[option].value, paraLevels[level], destChars);
                    }
                    else {
                        index = outIndices[tc][mode][option][level];
                        expectedChars = textOut[index];
                    }
                    if (!assertStringsEqual(expectedChars, destChars, srcChars,
                                modes[mode].description,
                                options[option].description,
                                pBiDi)) {
                        testOK = FALSE;
                    }
                    else if (options[option].value == UBIDI_OPTION_INSERT_MARKS &&
                             !assertRoundTrip(pBiDi3, tc, index, srcChars,
                                              destChars, dest, destLen,
                                              mode, option, paraLevels[level])) {
                        testOK = FALSE;
                    }
                    else if (!checkResultLength(pBiDi, srcChars, destChars,
                                dest, destLen, modes[mode].description,
                                options[option].description,
                                paraLevels[level])) {
                        testOK = FALSE;
                    }
                    else if (index > -1 && !testMaps(pBiDi, index, srcChars,
                            destChars, modes[mode].description,
                            options[option].description, paraLevels[level],
                            TRUE)) {
                        testOK = FALSE;
                    }
                }
            }
        }
    }
    if (testOK == TRUE) {
        log_verbose("\nReordering mode test OK\n");
    }
    ubidi_close(pBiDi3);
    ubidi_close(pBiDi2);
    ubidi_close(pBiDi);
}

static const char* inverseBasic(UBiDi *pBiDi, const UChar *src, int32_t srcLen,
                                uint32_t option, UBiDiLevel level, char *result) {
    UErrorCode rc = U_ZERO_ERROR;
    int32_t destLen;
    UChar dest2 [MAXLEN];

    if (pBiDi == NULL || src == NULL) {
        return NULL;
    }
    ubidi_setReorderingOptions(pBiDi, option);
    ubidi_setPara(pBiDi, src, srcLen, level, NULL, &rc);
    assertSuccessful("ubidi_setPara", &rc);

    *dest2 = 0;
    destLen = ubidi_writeReordered(pBiDi, dest2, MAXLEN,
                                   UBIDI_DO_MIRRORING, &rc);
    assertSuccessful("ubidi_writeReordered", &rc);
    u16ToPseudo(destLen, dest2, result);
    return result;
}

#define NULL_CHAR '\0'

static void
doBidiStreamingTest() {
#define MAXPORTIONS 10

    static const struct {
        const char* textIn;
        short int chunk;
        short int nPortions[2];
        char  portionLens[2][MAXPORTIONS];
        const char* message[2];
    } testData[] = {
        {   "123\\u000A"
            "abc45\\u000D"
            "67890\\u000A"
            "\\u000D"
            "02468\\u000D"
            "ghi",
            6, { 6, 6 }, {{ 6, 4, 6, 1, 6, 3}, { 4, 6, 6, 1, 6, 3 }},
            {"6, 4, 6, 1, 6, 3", "4, 6, 6, 1, 6, 3"}
        },
        {   "abcd\\u000Afgh\\u000D12345\\u000A456",
            6, { 4, 4 }, {{ 6, 3, 6, 3 }, { 5, 4, 6, 3 }},
            {"6, 3, 6, 3", "5, 4, 6, 3"}
        },
        {   "abcd\\u000Afgh\\u000D12345\\u000A45\\u000D",
            6, { 4, 4 }, {{ 6, 3, 6, 3 }, { 5, 4, 6, 3 }},
            {"6, 3, 6, 3", "5, 4, 6, 3"}
        },
        {   "abcde\\u000Afghi",
            10, { 1, 2 }, {{ 10 }, { 6, 4 }},
            {"10", "6, 4"}
        }
    };
    UChar src[MAXLEN];
    UBiDi *pBiDi = NULL;
    UChar *pSrc;
    UErrorCode rc = U_ZERO_ERROR;
    int32_t srcLen, processedLen, chunk, len, nPortions;
    int i, j, levelIndex;
    UBiDiLevel level;
    int nTests = LENGTHOF(testData), nLevels = LENGTHOF(paraLevels);
    UBool mismatch, testOK = TRUE;
    char processedLenStr[MAXPORTIONS * 5];

    log_verbose("\n*** Bidi streaming test ***\n");

    pBiDi = getBiDiObject();

    ubidi_orderParagraphsLTR(pBiDi, TRUE);

    for (levelIndex = 0; levelIndex < nLevels; levelIndex++) {
        for (i = 0; i < nTests; i++) {
            srcLen = u_unescape(testData[i].textIn, src, MAXLEN);
            chunk = testData[i].chunk;
            nPortions = testData[i].nPortions[levelIndex];
            level = paraLevels[levelIndex];
            *processedLenStr = NULL_CHAR;

            mismatch = FALSE;

            ubidi_setReorderingOptions(pBiDi, UBIDI_OPTION_STREAMING);
            for (j = 0, pSrc = src; j < MAXPORTIONS && srcLen > 0; j++) {

                len = chunk < srcLen ? chunk : srcLen;
                ubidi_setPara(pBiDi, pSrc, len, level, NULL, &rc);
                assertSuccessful("ubidi_setPara", &rc);

                processedLen = ubidi_getProcessedLength(pBiDi);
                if (processedLen == 0) {
                    ubidi_setReorderingOptions(pBiDi, UBIDI_OPTION_DEFAULT);
                    j--;
                    continue;
                }
                ubidi_setReorderingOptions(pBiDi, UBIDI_OPTION_STREAMING);

                mismatch = j >= nPortions ||
                           processedLen != testData[i].portionLens[levelIndex][j];

                sprintf(processedLenStr + j * 4, "%4d", processedLen);
                srcLen -= processedLen, pSrc += processedLen;
            }

            if (mismatch || j != nPortions) {
                testOK = FALSE;
                log_err("\nProcessed lengths mismatch.\n"
                    "\tParagraph level: %u\n"
                    "\tInput string: %s\n"
                    "\tActually processed portion lengths: { %s }\n"
                    "\tExpected portion lengths          : { %s }\n",
                    paraLevels[levelIndex], testData[i].textIn,
                    processedLenStr, testData[i].message[levelIndex]);
            }
        }
    }
    ubidi_close(pBiDi);
    if (testOK == TRUE) {
        log_verbose("\nBiDi streaming test OK\n");
    }
}

U_CDECL_BEGIN

static UCharDirection U_CALLCONV
overrideBidiClass(const void *context, UChar32 c) {

#define DEF U_BIDI_CLASS_DEFAULT

    static const UCharDirection customClasses[] = {
       /* 0/8    1/9    2/A    3/B    4/C    5/D    6/E    7/F  */
          DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF, /* 00-07 */
          DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF, /* 08-0F */
          DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF, /* 10-17 */
          DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF, /* 18-1F */
          DEF,   DEF,   DEF,   DEF,   DEF,   DEF,     R,   DEF, /* 20-27 */
          DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF, /* 28-2F */
           EN,    EN,    EN,    EN,    EN,    EN,    AN,    AN, /* 30-37 */
           AN,    AN,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF, /* 38-3F */
            L,    AL,    AL,    AL,    AL,    AL,    AL,     R, /* 40-47 */
            R,     R,     R,     R,     R,     R,     R,     R, /* 48-4F */
            R,     R,     R,     R,     R,     R,     R,     R, /* 50-57 */
            R,     R,     R,   LRE,   DEF,   RLE,   PDF,     S, /* 58-5F */
          NSM,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF, /* 60-67 */
          DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF, /* 68-6F */
          DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF,   DEF, /* 70-77 */
          DEF,   DEF,   DEF,   LRO,     B,   RLO,    BN,   DEF  /* 78-7F */
    };
    static const int nEntries = LENGTHOF(customClasses);

    return c >= nEntries ? U_BIDI_CLASS_DEFAULT : customClasses[c];
}

U_CDECL_END

static void verifyCallbackParams(UBiDiClassCallback* fn, const void* context,
                                 UBiDiClassCallback* expectedFn,
                                 const void* expectedContext,
                                 int32_t sizeOfContext) {
    if (fn != expectedFn) {
        log_err("Class callback pointer is not set properly.\n");
    }
    if (context != expectedContext) {
        log_err("Class callback context is not set properly.\n");
    }
    else if (context != NULL &&
            memcmp(context, expectedContext, sizeOfContext)) {
        log_err("Callback context content doesn't match the expected one.\n");
    }
}

static void doBidiClassOverrideTest(void) {
    static const char* const textSrc  = "JIH.>12->a \\u05D0\\u05D1 6 ABC78";
    static const char* const textResult = "12<.HIJ->a 78CBA 6 \\u05D1\\u05D0";

    UChar src[MAXLEN], dest[MAXLEN];
    UErrorCode rc = U_ZERO_ERROR;
    UBiDi *pBiDi = NULL;
    UBiDiClassCallback* oldFn = NULL;
    UBiDiClassCallback* newFn = overrideBidiClass;
    const void* oldContext = NULL;
    int32_t srcLen, destLen, textSrcSize = (int32_t)uprv_strlen(textSrc);
    char* destChars = NULL;

    log_verbose("\n*** Bidi class override test ***\n");

    pBiDi = getBiDiObject();
    if(!pBiDi) {
        return;
    }

    ubidi_getClassCallback(pBiDi, &oldFn, &oldContext);
    verifyCallbackParams(oldFn, oldContext, NULL, NULL, 0);

    ubidi_setClassCallback(pBiDi, newFn, textSrc, &oldFn, &oldContext, &rc);
    if (!assertSuccessful("ubidi_setClassCallback", &rc)) {
        ubidi_close(pBiDi);
        return;
    }
    verifyCallbackParams(oldFn, oldContext, NULL, NULL, 0);

    ubidi_getClassCallback(pBiDi, &oldFn, &oldContext);
    verifyCallbackParams(oldFn, oldContext, newFn, textSrc, textSrcSize);

    ubidi_setClassCallback(pBiDi, newFn, textSrc, &oldFn, &oldContext, &rc);
    if (!assertSuccessful("ubidi_setClassCallback", &rc)) {
        ubidi_close(pBiDi);
        return;
    }
    verifyCallbackParams(oldFn, oldContext, newFn, textSrc, textSrcSize);

    srcLen = u_unescape(textSrc, src, MAXLEN);
    ubidi_setPara(pBiDi, src, srcLen, UBIDI_LTR, NULL, &rc);
    assertSuccessful("ubidi_setPara", &rc);

    destLen = ubidi_writeReordered(pBiDi, dest, MAXLEN,
                                   UBIDI_DO_MIRRORING, &rc);
    assertSuccessful("ubidi_writeReordered", &rc);

    destChars = aescstrdup(dest, destLen);
    if (uprv_strcmp(textResult, destChars)) {
        log_err("\nActual and expected output mismatch.\n"
            "%20s %s\n%20s %s\n%20s %s\n",
            "Input:", textSrc, "Actual output:", destChars,
            "Expected output:", textResult);
    }
    else {
        log_verbose("\nClass override test OK\n");
    }
    ubidi_close(pBiDi);
}

static char * formatMap(const int32_t * map, int len, char * buffer)
{
    int32_t i, k;
    char c;
    for (i = 0; i < len; i++) {
        k = map[i];
        if (k < 0)
            c = '-';
        else if (k >= sizeof columns)
            c = '+';
        else
            c = columns[k];
        buffer[i] = c;
    }
    buffer[len] = '\0';
    return buffer;
}

static UBool
testMaps(UBiDi *pBiDi, int32_t stringIndex, const char *src, const char *dest,
         const char *mode, const char* option, UBiDiLevel level, UBool forward)
{
    int32_t actualLogicalMap[MAX_MAP_LENGTH];
    int32_t actualVisualMap[MAX_MAP_LENGTH];
    int32_t getIndexMap[MAX_MAP_LENGTH];
    int32_t i, srcLen, resLen, index;
    const int32_t *expectedLogicalMap, *expectedVisualMap;
    UErrorCode rc = U_ZERO_ERROR;
    UBool testOK = TRUE;

    if (forward) {
        expectedLogicalMap = forwardMap[stringIndex];
        expectedVisualMap  = inverseMap[stringIndex];
    }
    else {
        expectedLogicalMap = inverseMap[stringIndex];
        expectedVisualMap  = forwardMap[stringIndex];
    }
    ubidi_getLogicalMap(pBiDi, actualLogicalMap, &rc);
    if (!assertSuccessful("ubidi_getLogicalMap", &rc)) {
        testOK = FALSE;
    }
    srcLen = ubidi_getProcessedLength(pBiDi);
    if (memcmp(expectedLogicalMap, actualLogicalMap, srcLen * sizeof(int32_t))) {
        char expChars[MAX_MAP_LENGTH];
        char actChars[MAX_MAP_LENGTH];
        log_err("\nubidi_getLogicalMap() returns unexpected map for output string "
                "index %d\n"
                "source: %s\n"
                "dest  : %s\n"
                "Scale : %s\n"
                "ExpMap: %s\n"
                "Actual: %s\n"
                "Paragraph level  : %d == %d\n"
                "Reordering mode  : %s == %d\n"
                "Reordering option: %s == %d\n"
                "Forward flag     : %d\n",
                stringIndex, src, dest, columns,
                formatMap(expectedLogicalMap, srcLen, expChars),
                formatMap(actualLogicalMap, srcLen, actChars),
                level, ubidi_getParaLevel(pBiDi),
                mode, ubidi_getReorderingMode(pBiDi),
                option, ubidi_getReorderingOptions(pBiDi),
                forward
                );
        testOK = FALSE;
    }
    resLen = ubidi_getResultLength(pBiDi);
    ubidi_getVisualMap(pBiDi, actualVisualMap, &rc);
    assertSuccessful("ubidi_getVisualMap", &rc);
    if (memcmp(expectedVisualMap, actualVisualMap, resLen * sizeof(int32_t))) {
        char expChars[MAX_MAP_LENGTH];
        char actChars[MAX_MAP_LENGTH];
        log_err("\nubidi_getVisualMap() returns unexpected map for output string "
                "index %d\n"
                "source: %s\n"
                "dest  : %s\n"
                "Scale : %s\n"
                "ExpMap: %s\n"
                "Actual: %s\n"
                "Paragraph level  : %d == %d\n"
                "Reordering mode  : %s == %d\n"
                "Reordering option: %s == %d\n"
                "Forward flag     : %d\n",
                stringIndex, src, dest, columns,
                formatMap(expectedVisualMap, resLen, expChars),
                formatMap(actualVisualMap, resLen, actChars),
                level, ubidi_getParaLevel(pBiDi),
                mode, ubidi_getReorderingMode(pBiDi),
                option, ubidi_getReorderingOptions(pBiDi),
                forward
                );
        testOK = FALSE;
    }
    for (i = 0; i < srcLen; i++) {
        index = ubidi_getVisualIndex(pBiDi, i, &rc);
        assertSuccessful("ubidi_getVisualIndex", &rc);
        getIndexMap[i] = index;
    }
    if (memcmp(actualLogicalMap, getIndexMap, srcLen * sizeof(int32_t))) {
        char actChars[MAX_MAP_LENGTH];
        char gotChars[MAX_MAP_LENGTH];
        log_err("\nMismatch between ubidi_getLogicalMap and ubidi_getVisualIndex for output string "
                "index %d\n"
                "source: %s\n"
                "dest  : %s\n"
                "Scale : %s\n"
                "ActMap: %s\n"
                "IdxMap: %s\n"
                "Paragraph level  : %d == %d\n"
                "Reordering mode  : %s == %d\n"
                "Reordering option: %s == %d\n"
                "Forward flag     : %d\n",
                stringIndex, src, dest, columns,
                formatMap(actualLogicalMap, srcLen, actChars),
                formatMap(getIndexMap, srcLen, gotChars),
                level, ubidi_getParaLevel(pBiDi),
                mode, ubidi_getReorderingMode(pBiDi),
                option, ubidi_getReorderingOptions(pBiDi),
                forward
                );
        testOK = FALSE;
    }
    for (i = 0; i < resLen; i++) {
        index = ubidi_getLogicalIndex(pBiDi, i, &rc);
        assertSuccessful("ubidi_getLogicalIndex", &rc);
        getIndexMap[i] = index;
    }
    if (memcmp(actualVisualMap, getIndexMap, resLen * sizeof(int32_t))) {
        char actChars[MAX_MAP_LENGTH];
        char gotChars[MAX_MAP_LENGTH];
        log_err("\nMismatch between ubidi_getVisualMap and ubidi_getLogicalIndex for output string "
                "index %d\n"
                "source: %s\n"
                "dest  : %s\n"
                "Scale : %s\n"
                "ActMap: %s\n"
                "IdxMap: %s\n"
                "Paragraph level  : %d == %d\n"
                "Reordering mode  : %s == %d\n"
                "Reordering option: %s == %d\n"
                "Forward flag     : %d\n",
                stringIndex, src, dest, columns,
                formatMap(actualVisualMap, resLen, actChars),
                formatMap(getIndexMap, resLen, gotChars),
                level, ubidi_getParaLevel(pBiDi),
                mode, ubidi_getReorderingMode(pBiDi),
                option, ubidi_getReorderingOptions(pBiDi),
                forward
                );
        testOK = FALSE;
    }
    return testOK;
}