// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License

package com.ibm.icu.dev.test.shaping;

import java.util.Arrays;
import java.util.Collection;
import java.util.MissingResourceException;

import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.text.ArabicShaping;
import com.ibm.icu.text.ArabicShapingException;

/**
 * Regression test for Arabic shaping.
 */
@RunWith(Enclosed.class)
public class DataDrivenArabicShapingRegTest extends TestFmwk {

    /* constants copied from ArabicShaping for convenience */

    public static final int LENGTH_GROW_SHRINK = 0;
    public static final int LENGTH_FIXED_SPACES_NEAR = 1;
    public static final int LENGTH_FIXED_SPACES_AT_END = 2;
    public static final int LENGTH_FIXED_SPACES_AT_BEGINNING = 3;

    public static final int TEXT_DIRECTION_LOGICAL = 0;
    public static final int TEXT_DIRECTION_VISUAL_LTR = 4;

    public static final int LETTERS_NOOP = 0;
    public static final int LETTERS_SHAPE = 8;
    public static final int LETTERS_SHAPE_TASHKEEL_ISOLATED = 0x18;
    public static final int LETTERS_UNSHAPE = 0x10;

    public static final int DIGITS_NOOP = 0;
    public static final int DIGITS_EN2AN = 0x20;
    public static final int DIGITS_AN2EN = 0x40;
    public static final int DIGITS_EN2AN_INIT_LR = 0x60;
    public static final int DIGITS_EN2AN_INIT_AL = 0x80;
    //    private static final int DIGITS_RESERVED = 0xa0;

    public static final int DIGIT_TYPE_AN = 0;
    public static final int DIGIT_TYPE_AN_EXTENDED = 0x100;

    @RunWith(Parameterized.class)
    public static class StandardDataTest extends TestFmwk {
        private String source;
        private int flags;
        private String expected;

        public StandardDataTest(String source, int flags, String expected) {
            this.source = source;
            this.flags = flags;
            this.expected = expected;
        }

        @Parameterized.Parameters
        public static Collection testData() {
            String lamAlefSpecialVLTR =
                "\u0020\u0646\u0622\u0644\u0627\u0020\u0646\u0623\u064E\u0644\u0627\u0020" +
                "\u0646\u0627\u0670\u0644\u0627\u0020\u0646\u0622\u0653\u0644\u0627\u0020" +
                "\u0646\u0625\u0655\u0644\u0627\u0020\u0646\u0622\u0654\u0644\u0627\u0020" +
                "\uFEFC\u0639";
            String tashkeelSpecialVLTR =
                "\u064A\u0628\u0631\u0639\u0020\u064A\u0628\u0651\u0631\u064E\u0639\u0020" +
                "\u064C\u064A\u0628\u0631\u064F\u0639\u0020\u0628\u0670\u0631\u0670\u0639" +
                "\u0020\u0628\u0653\u0631\u0653\u0639\u0020\u0628\u0654\u0631\u0654\u0639" +
                "\u0020\u0628\u0655\u0631\u0655\u0639\u0020";
            String tashkeelShaddaRTL=
                "\u0634\u0651\u0645\u0652\u0633";
            String tashkeelShaddaLTR=
                "\u0633\u0652\u0645\u0651\u0634";
            String ArMathSym =
                "\uD83B\uDE00\uD83B\uDE01\uD83B\uDE02\uD83B\uDE03\u0020\uD83B\uDE24\uD83B" +
                "\uDE05\uD83B\uDE06\u0020\uD83B\uDE07\uD83B\uDE08\uD83B\uDE09\u0020\uD83B" +
                "\uDE0A\uD83B\uDE0B\uD83B\uDE0C\uD83B\uDE0D\u0020\uD83B\uDE0E\uD83B\uDE0F" +
                "\uD83B\uDE10\uD83B\uDE11\u0020\uD83B\uDE12\uD83B\uDE13\uD83B\uDE14\uD83B" +
                "\uDE15\u0020\uD83B\uDE16\uD83B\uDE17\uD83B\uDE18\u0020\uD83B\uDE19\uD83B" +
                "\uDE1A\uD83B\uDE1B";
            String ArMathSymLooped =
                "\uD83B\uDE80\uD83B\uDE81\uD83B\uDE82\uD83B\uDE83\u0020\uD83B\uDE84\uD83B" +
                "\uDE85\uD83B\uDE86\u0020\uD83B\uDE87\uD83B\uDE88\uD83B\uDE89\u0020\uD83B" +
                "\uDE8B\uD83B\uDE8C\uD83B\uDE8D\u0020\uD83B\uDE8E\uD83B\uDE8F\uD83B\uDE90" +
                "\uD83B\uDE91\u0020\uD83B\uDE92\uD83B\uDE93\uD83B\uDE94\uD83B\uDE95\u0020" +
                "\uD83B\uDE96\uD83B\uDE97\uD83B\uDE98\u0020\uD83B\uDE99\uD83B\uDE9A\uD83B" +
                "\uDE9B";
            String ArMathSymDoubleStruck =
                "\uD83B\uDEA1\uD83B\uDEA2\uD83B\uDEA3\u0020\uD83B\uDEA5\uD83B\uDEA6\u0020" +
                "\uD83B\uDEA7\uD83B\uDEA8\uD83B\uDEA9\u0020\uD83B\uDEAB\uD83B\uDEAC\uD83B" +
                "\uDEAD\u0020\uD83B\uDEAE\uD83B\uDEAF\uD83B\uDEB0\uD83B\uDEB1\u0020\uD83B" +
                "\uDEB2\uD83B\uDEB3\uD83B\uDEB4\uD83B\uDEB5\u0020\uD83B\uDEB6\uD83B\uDEB7" +
                "\uD83B\uDEB8\u0020\uD83B\uDEB9\uD83B\uDEBA\uD83B\uDEBB";
            String ArMathSymInitial =
                "\uD83B\uDE21\uD83B\uDE22\u0020\uD83B\uDE27\uD83B\uDE29\u0020\uD83B\uDE2A" +
                "\uD83B\uDE2B\uD83B\uDE2C\uD83B\uDE2D\u0020\uD83B\uDE2E\uD83B\uDE2F\uD83B" +
                "\uDE30\uD83B\uDE31\u0020\uD83B\uDE32\uD83B\uDE34\uD83B\uDE35\u0020\uD83B" +
                "\uDE36\uD83B\uDE37\u0020\uD83B\uDE39\uD83B\uDE3B";
            String ArMathSymTailed =
                "\uD83B\uDE42\uD83B\uDE47\uD83B\uDE49\uD83B\uDE4B\u0020\uD83B\uDE4D\uD83B" +
                "\uDE4E\uD83B\uDE4F\u0020\uD83B\uDE51\uD83B\uDE52\uD83B\uDE54\uD83B\uDE57" +
                "\u0020\uD83B\uDE59\uD83B\uDE5B\uD83B\uDE5D\uD83B\uDE5F";
            String ArMathSymStretched =
                "\uD83B\uDE21\u0633\uD83B\uDE62\u0647";
            String logicalUnshape =
                "\u0020\u0020\u0020\uFE8D\uFEF5\u0020\uFEE5\u0020\uFE8D\uFEF7\u0020\uFED7" +
                "\uFEFC\u0020\uFEE1\u0020\uFE8D\uFEDF\uFECC\uFEAE\uFE91\uFEF4\uFE94\u0020" +
                "\uFE8D\uFEDF\uFEA4\uFEAE\uFE93\u0020\u0020\u0020\u0020";
            String numSource =
                "\u0031" +  /* en:1 */
                "\u0627" +  /* arabic:alef */
                "\u0032" +  /* en:2 */
                "\u06f3" +  /* an:3 */
                "\u0061" +  /* latin:a */
                "\u0034";   /* en:4 */
                
            return Arrays.asList(new Object[][] {
                    /* lam alef special visual ltr */
                    {lamAlefSpecialVLTR,
                     LETTERS_SHAPE | TEXT_DIRECTION_VISUAL_LTR | LENGTH_FIXED_SPACES_NEAR,
                     "\u0020\ufee5\u0020\ufef5\ufe8d\u0020\ufee5\u0020\ufe76\ufef7\ufe8d\u0020" +
                     "\ufee5\u0020\u0670\ufefb\ufe8d\u0020\ufee5\u0020\u0653\ufef5\ufe8d\u0020" +
                     "\ufee5\u0020\u0655\ufef9\ufe8d\u0020\ufee5\u0020\u0654\ufef5\ufe8d\u0020" +
                     "\ufefc\ufecb"},
                    {lamAlefSpecialVLTR,
                     LETTERS_SHAPE | TEXT_DIRECTION_VISUAL_LTR | LENGTH_FIXED_SPACES_AT_END,
                     "\u0020\ufee5\ufef5\ufe8d\u0020\ufee5\ufe76\ufef7\ufe8d\u0020\ufee5\u0670" +
                     "\ufefb\ufe8d\u0020\ufee5\u0653\ufef5\ufe8d\u0020\ufee5\u0655\ufef9\ufe8d" +
                     "\u0020\ufee5\u0654\ufef5\ufe8d\u0020\ufefc\ufecb\u0020\u0020\u0020\u0020" +
                     "\u0020\u0020"},
                    {lamAlefSpecialVLTR,
                     LETTERS_SHAPE | TEXT_DIRECTION_VISUAL_LTR | LENGTH_FIXED_SPACES_AT_BEGINNING,
                     "\u0020\u0020\u0020\u0020\u0020\u0020\u0020\ufee5\ufef5\ufe8d\u0020\ufee5" +
                     "\ufe76\ufef7\ufe8d\u0020\ufee5\u0670\ufefb\ufe8d\u0020\ufee5\u0653\ufef5" +
                     "\ufe8d\u0020\ufee5\u0655\ufef9\ufe8d\u0020\ufee5\u0654\ufef5\ufe8d\u0020" +
                     "\ufefc\ufecb"},
                    {lamAlefSpecialVLTR,
                     LETTERS_SHAPE | TEXT_DIRECTION_VISUAL_LTR | LENGTH_GROW_SHRINK,
                     "\u0020\ufee5\ufef5\ufe8d\u0020\ufee5\ufe76\ufef7\ufe8d\u0020\ufee5\u0670" +
                     "\ufefb\ufe8d\u0020\ufee5\u0653\ufef5\ufe8d\u0020\ufee5\u0655\ufef9\ufe8d" +
                     "\u0020\ufee5\u0654\ufef5\ufe8d\u0020\ufefc\ufecb"},
                    /* TASHKEEL */
                    {lamAlefSpecialVLTR,
                     LETTERS_SHAPE_TASHKEEL_ISOLATED | TEXT_DIRECTION_VISUAL_LTR |
                     LENGTH_FIXED_SPACES_NEAR,
                     "\u0020\ufee5\u0020\ufef5\ufe8d\u0020\ufee5\u0020\ufe76\ufef7\ufe8d\u0020" +
                     "\ufee5\u0020\u0670\ufefb\ufe8d\u0020\ufee5\u0020\u0653\ufef5\ufe8d\u0020" +
                     "\ufee5\u0020\u0655\ufef9\ufe8d\u0020\ufee5\u0020\u0654\ufef5\ufe8d\u0020" +
                     "\ufefc\ufecb"},
                    {lamAlefSpecialVLTR,
                     LETTERS_SHAPE_TASHKEEL_ISOLATED | TEXT_DIRECTION_VISUAL_LTR |
                     LENGTH_FIXED_SPACES_AT_END,
                     "\u0020\ufee5\ufef5\ufe8d\u0020\ufee5\ufe76\ufef7\ufe8d\u0020\ufee5\u0670" +
                     "\ufefb\ufe8d\u0020\ufee5\u0653\ufef5\ufe8d\u0020\ufee5\u0655\ufef9\ufe8d" +
                     "\u0020\ufee5\u0654\ufef5\ufe8d\u0020\ufefc\ufecb\u0020\u0020\u0020\u0020" +
                     "\u0020\u0020"},
                    {lamAlefSpecialVLTR,
                     LETTERS_SHAPE_TASHKEEL_ISOLATED | TEXT_DIRECTION_VISUAL_LTR |
                     LENGTH_FIXED_SPACES_AT_BEGINNING,
                     "\u0020\u0020\u0020\u0020\u0020\u0020\u0020\ufee5\ufef5\ufe8d\u0020\ufee5" +
                     "\ufe76\ufef7\ufe8d\u0020\ufee5\u0670\ufefb\ufe8d\u0020\ufee5\u0653\ufef5" +
                     "\ufe8d\u0020\ufee5\u0655\ufef9\ufe8d\u0020\ufee5\u0654\ufef5\ufe8d\u0020" +
                     "\ufefc\ufecb"},
                    {lamAlefSpecialVLTR,
                     LETTERS_SHAPE_TASHKEEL_ISOLATED | TEXT_DIRECTION_VISUAL_LTR |
                     LENGTH_GROW_SHRINK,
                     "\u0020\ufee5\ufef5\ufe8d\u0020\ufee5\ufe76\ufef7\ufe8d\u0020\ufee5\u0670" +
                     "\ufefb\ufe8d\u0020\ufee5\u0653\ufef5\ufe8d\u0020\ufee5\u0655\ufef9\ufe8d" +
                     "\u0020\ufee5\u0654\ufef5\ufe8d\u0020\ufefc\ufecb"},
                    /* tashkeel special visual ltr */
                    {tashkeelSpecialVLTR,
                     LETTERS_SHAPE | TEXT_DIRECTION_VISUAL_LTR | LENGTH_FIXED_SPACES_NEAR,
                     "\ufef2\ufe91\ufeae\ufecb\u0020\ufef2\ufe91\ufe7c\ufeae\ufe77\ufecb\u0020" +
                     "\ufe72\ufef2\ufe91\ufeae\ufe79\ufecb\u0020\ufe8f\u0670\ufeae\u0670\ufecb" +
                     "\u0020\ufe8f\u0653\ufeae\u0653\ufecb\u0020\ufe8f\u0654\ufeae\u0654\ufecb" +
                     "\u0020\ufe8f\u0655\ufeae\u0655\ufecb\u0020"},
                    {tashkeelSpecialVLTR,
                     LETTERS_SHAPE_TASHKEEL_ISOLATED | TEXT_DIRECTION_VISUAL_LTR |
                     LENGTH_FIXED_SPACES_NEAR,
                     "\ufef2\ufe91\ufeae\ufecb\u0020\ufef2\ufe91\ufe7c\ufeae\ufe76\ufecb\u0020" +
                     "\ufe72\ufef2\ufe91\ufeae\ufe78\ufecb\u0020\ufe8f\u0670\ufeae\u0670\ufecb" +
                     "\u0020\ufe8f\u0653\ufeae\u0653\ufecb\u0020\ufe8f\u0654\ufeae\u0654\ufecb" +
                     "\u0020\ufe8f\u0655\ufeae\u0655\ufecb\u0020"},
                    {tashkeelShaddaRTL,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_BEGIN |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_RTL,
                     "\u0020\ufeb7\ufe7d\ufee4\ufeb2"},
                    {tashkeelShaddaRTL,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_END |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_RTL,
                     "\ufeb7\ufe7d\ufee4\ufeb2\u0020"},
                    {tashkeelShaddaRTL,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_RESIZE |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_RTL,
                     "\ufeb7\ufe7d\ufee4\ufeb2"},
                    {tashkeelShaddaRTL,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_REPLACE_BY_TATWEEL |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_RTL,
                     "\ufeb7\ufe7d\ufee4\u0640\ufeb2"},
                    {tashkeelShaddaLTR,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_BEGIN |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_LTR,
                     "\u0020\ufeb2\ufee4\ufe7d\ufeb7"},
                    {tashkeelShaddaLTR,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_END |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_LTR,
                     "\ufeb2\ufee4\ufe7d\ufeb7\u0020"},
                    {tashkeelShaddaLTR,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_RESIZE |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_LTR,
                     "\ufeb2\ufee4\ufe7d\ufeb7"},
                    {tashkeelShaddaLTR,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_REPLACE_BY_TATWEEL |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_LTR,
                     "\ufeb2\u0640\ufee4\ufe7d\ufeb7"},
                    {ArMathSym,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_BEGIN |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_RTL,
                     "\uD83B\uDE00\uD83B\uDE01\uD83B\uDE02\uD83B\uDE03\u0020\uD83B\uDE24\uD83B" +
                     "\uDE05\uD83B\uDE06\u0020\uD83B\uDE07\uD83B\uDE08\uD83B\uDE09\u0020\uD83B" +
                     "\uDE0A\uD83B\uDE0B\uD83B\uDE0C\uD83B\uDE0D\u0020\uD83B\uDE0E\uD83B\uDE0F" +
                     "\uD83B\uDE10\uD83B\uDE11\u0020\uD83B\uDE12\uD83B\uDE13\uD83B\uDE14\uD83B" +
                     "\uDE15\u0020\uD83B\uDE16\uD83B\uDE17\uD83B\uDE18\u0020\uD83B\uDE19\uD83B" +
                     "\uDE1A\uD83B\uDE1B"},
                    {ArMathSymLooped,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_END |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_RTL,
                     "\uD83B\uDE80\uD83B\uDE81\uD83B\uDE82\uD83B\uDE83\u0020\uD83B\uDE84\uD83B" +
                     "\uDE85\uD83B\uDE86\u0020\uD83B\uDE87\uD83B\uDE88\uD83B\uDE89\u0020\uD83B" +
                     "\uDE8B\uD83B\uDE8C\uD83B\uDE8D\u0020\uD83B\uDE8E\uD83B\uDE8F\uD83B\uDE90" +
                     "\uD83B\uDE91\u0020\uD83B\uDE92\uD83B\uDE93\uD83B\uDE94\uD83B\uDE95\u0020" +
                     "\uD83B\uDE96\uD83B\uDE97\uD83B\uDE98\u0020\uD83B\uDE99\uD83B\uDE9A\uD83B" +
                     "\uDE9B"},
                    {ArMathSymDoubleStruck,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_RESIZE|
                     ArabicShaping.TEXT_DIRECTION_VISUAL_RTL,
                     "\uD83B\uDEA1\uD83B\uDEA2\uD83B\uDEA3\u0020\uD83B\uDEA5\uD83B\uDEA6\u0020" +
                     "\uD83B\uDEA7\uD83B\uDEA8\uD83B\uDEA9\u0020\uD83B\uDEAB\uD83B\uDEAC\uD83B" +
                     "\uDEAD\u0020\uD83B\uDEAE\uD83B\uDEAF\uD83B\uDEB0\uD83B\uDEB1\u0020\uD83B" +
                     "\uDEB2\uD83B\uDEB3\uD83B\uDEB4\uD83B\uDEB5\u0020\uD83B\uDEB6\uD83B\uDEB7" +
                     "\uD83B\uDEB8\u0020\uD83B\uDEB9\uD83B\uDEBA\uD83B\uDEBB"},
                    {ArMathSymInitial,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_BEGIN |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_LTR,
                     "\uD83B\uDE21\uD83B\uDE22\u0020\uD83B\uDE27\uD83B\uDE29\u0020\uD83B\uDE2A" +
                     "\uD83B\uDE2B\uD83B\uDE2C\uD83B\uDE2D\u0020\uD83B\uDE2E\uD83B\uDE2F\uD83B" +
                     "\uDE30\uD83B\uDE31\u0020\uD83B\uDE32\uD83B\uDE34\uD83B\uDE35\u0020\uD83B" +
                     "\uDE36\uD83B\uDE37\u0020\uD83B\uDE39\uD83B\uDE3B"},
                    {ArMathSymTailed,
                     ArabicShaping.LETTERS_SHAPE | ArabicShaping.TASHKEEL_END |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_LTR,
                     "\uD83B\uDE42\uD83B\uDE47\uD83B\uDE49\uD83B\uDE4B\u0020\uD83B\uDE4D\uD83B" +
                     "\uDE4E\uD83B\uDE4F\u0020\uD83B\uDE51\uD83B\uDE52\uD83B\uDE54\uD83B\uDE57" +
                     "\u0020\uD83B\uDE59\uD83B\uDE5B\uD83B\uDE5D\uD83B\uDE5F"},
                    {ArMathSymStretched,
                     ArabicShaping.LETTERS_SHAPE|ArabicShaping.TASHKEEL_RESIZE |
                     ArabicShaping.TEXT_DIRECTION_VISUAL_LTR,
                     "\uD83B\uDE21\uFEB1\uD83B\uDE62\uFEE9"},
                     /* logical unshape */
                    {logicalUnshape,
                     LETTERS_UNSHAPE | TEXT_DIRECTION_LOGICAL | LENGTH_FIXED_SPACES_NEAR,
                     "\u0020\u0020\u0020\u0627\u0644\u0622\u0646\u0020\u0627\u0644\u0623\u0642" +
                     "\u0644\u0627\u0645\u0020\u0627\u0644\u0639\u0631\u0628\u064a\u0629\u0020" +
                     "\u0627\u0644\u062d\u0631\u0629\u0020\u0020\u0020\u0020"},
                    {logicalUnshape,
                     LETTERS_UNSHAPE | TEXT_DIRECTION_LOGICAL | LENGTH_FIXED_SPACES_AT_END,
                     "\u0020\u0020\u0020\u0627\u0644\u0622\u0020\u0646\u0020\u0627\u0644\u0623" +
                     "\u0020\u0642\u0644\u0627\u0020\u0645\u0020\u0627\u0644\u0639\u0631\u0628" +
                     "\u064a\u0629\u0020\u0627\u0644\u062d\u0631\u0629\u0020"},
                    {logicalUnshape,
                     LETTERS_UNSHAPE | TEXT_DIRECTION_LOGICAL | LENGTH_FIXED_SPACES_AT_BEGINNING,
                     "\u0627\u0644\u0622\u0020\u0646\u0020\u0627\u0644\u0623\u0020\u0642\u0644" +
                     "\u0627\u0020\u0645\u0020\u0627\u0644\u0639\u0631\u0628\u064a\u0629\u0020" +
                     "\u0627\u0644\u062d\u0631\u0629\u0020\u0020\u0020\u0020"},
                    {logicalUnshape,
                     LETTERS_UNSHAPE | TEXT_DIRECTION_LOGICAL | LENGTH_GROW_SHRINK,
                     "\u0020\u0020\u0020\u0627\u0644\u0622\u0020\u0646\u0020\u0627\u0644\u0623" +
                     "\u0020\u0642\u0644\u0627\u0020\u0645\u0020\u0627\u0644\u0639\u0631\u0628" +
                     "\u064a\u0629\u0020\u0627\u0644\u062d\u0631\u0629\u0020\u0020\u0020\u0020"},
                    /* numbers */
                    {numSource,
                     DIGITS_EN2AN | DIGIT_TYPE_AN,
                     "\u0661\u0627\u0662\u06f3\u0061\u0664"},
                    {numSource,
                     DIGITS_AN2EN | DIGIT_TYPE_AN_EXTENDED,
                    "\u0031\u0627\u0032\u0033\u0061\u0034"},
                    {numSource,
                     DIGITS_EN2AN_INIT_LR | DIGIT_TYPE_AN,
                     "\u0031\u0627\u0662\u06f3\u0061\u0034" },
                    {numSource,
                     DIGITS_EN2AN_INIT_AL | DIGIT_TYPE_AN_EXTENDED,
                     "\u06f1\u0627\u06f2\u06f3\u0061\u0034"},
                    {numSource,
                     DIGITS_EN2AN_INIT_LR | DIGIT_TYPE_AN | TEXT_DIRECTION_VISUAL_LTR,
                     "\u0661\u0627\u0032\u06f3\u0061\u0034"},
                    {numSource,
                     DIGITS_EN2AN_INIT_AL | DIGIT_TYPE_AN_EXTENDED | TEXT_DIRECTION_VISUAL_LTR,
                     "\u06f1\u0627\u0032\u06f3\u0061\u06f4"},
                    /* no-op */
                    {numSource, 0, numSource}
                });
        }

        @Test
        public void TestStandard() {
            Exception ex = null;
            String actual = null;
            ArabicShaping shaper = null;

            try {
                shaper = new ArabicShaping(flags);
                actual = shaper.shape(source);
            }
            catch(MissingResourceException e){
                throw e;
            }
            catch (IllegalStateException ie){
                warnln("IllegalStateException: "+ ie.toString());
                return;
            }
            catch (Exception e) {
                ex = e;
            }

            if (ex != null) {
                err("Error: Shaper " + shaper + "\n throws exception '" + ex + "'\n for input '" + source);
            } else if (!expected.equals(actual)) {
                StringBuffer buf = new StringBuffer();
                buf.append("Error: Shaper: " + shaper + "\n Input: " + source + "\n Actual: " + actual +
                           "\n Expected: " + expected + "\n");

                for (int i = 0; i < Math.max(expected.length(), actual.length()); ++i) {
                    String temp = Integer.toString(i);
                    if (temp.length() < 2) {
                        temp = " ".concat(temp);
                    }
                    char trg = i < expected.length() ? expected.charAt(i) : '\uffff';
                    char res = i < actual.length() ? actual.charAt(i) : '\uffff';

                    buf.append("[" + temp + "] ");
                    buf.append(escapedString("" + trg) + " ");
                    buf.append(escapedString("" + res) + " ");
                    if (trg != res) {
                        buf.append("***");
                    }
                    buf.append("\n");
                }
                err(buf.toString());
            }
        }

        private static String escapedString(String str) {
            if (str == null) {
                return null;
            }

            StringBuffer buf = new StringBuffer(str.length() * 6);
            for (int i = 0; i < str.length(); ++i) {
                char ch = str.charAt(i);
                buf.append("\\u");
                if (ch < 0x1000) {
                    buf.append('0');
                }
                if (ch < 0x0100) {
                    buf.append('0');
                }
                if (ch < 0x0010) {
                    buf.append('0');
                }
                buf.append(Integer.toHexString(ch));
            }
            return buf.toString();
        }
    }

    @RunWith(Parameterized.class)
    public static class PreflightDataTest extends TestFmwk {
        private String source;
        private int flags;
        private int length;

        public PreflightDataTest(String source, int flags, int length) {
            this.source = source;
            this.flags = flags;
            this.length = length;
        }

        @Parameterized.Parameters
        public static Collection testData() {
            return Arrays.asList(new Object[][] {
                    {"\u0644\u0627", LETTERS_SHAPE | LENGTH_GROW_SHRINK, 1},
                    {"\u0644\u0627\u0031",
                     DIGITS_EN2AN | DIGIT_TYPE_AN_EXTENDED | LENGTH_GROW_SHRINK, 3},
                    {"\u0644\u0644", LETTERS_SHAPE | LENGTH_GROW_SHRINK, 2},
                    {"\ufef7", LETTERS_UNSHAPE | LENGTH_GROW_SHRINK, 2}
                });
        }

        @Test
        public void TestPreflight() {
            Exception ex = null;
            char src[] = null;
            int len = 0;
            ArabicShaping shaper = null;

            if (source != null) {
                src = source.toCharArray();
            }

            try {
                shaper = new ArabicShaping(flags);
                len = shaper.shape(src, 0, src.length, null, 0, 0);
            }
            catch (Exception e) {
                ex = e;
            }

            if (ex != null) {
                err("Error: Shaper " + shaper + "\n throws exception '" + ex + "'\n for input '" + source);
            } else if (length != len) {
                err("Error: Shaper " + shaper + "\n returns " + len + " characters for input '" +
                    source + "'\n Expected were " + length + " characters");
            }
        }
    }

    @RunWith(Parameterized.class)
    public static class ErrorDataTest extends TestFmwk {
        private String source;
        private int flags;
        private Class error;

        public ErrorDataTest(String source, int flags, Class error) {
            this.source = source;
            this.flags = flags;
            this.error = error;
        }

        @Parameterized.Parameters
        public static Collection testData() {
            return Arrays.asList(new Object[][] {
                    /* bad data */
                    {"\u0020\ufef7\u0644\u0020", LETTERS_UNSHAPE | LENGTH_FIXED_SPACES_NEAR,
                     ArabicShapingException.class},
                    {"\u0020\ufef7", LETTERS_UNSHAPE | LENGTH_FIXED_SPACES_AT_END,
                     ArabicShapingException.class},
                    {"\ufef7\u0020", LETTERS_UNSHAPE | LENGTH_FIXED_SPACES_AT_BEGINNING,
                     ArabicShapingException.class},
                    /* bad options */
                    {"\ufef7", 0xffffffff, IllegalArgumentException.class},
                    {"\ufef7", LETTERS_UNSHAPE | LENGTH_GROW_SHRINK, ArabicShapingException.class},
                    {null, LETTERS_UNSHAPE | LENGTH_FIXED_SPACES_AT_END,
                     IllegalArgumentException.class}
                });
        }

        @Test
        public void TestError() {
            Exception ex = null;
            char src[] = null;
            int len = 0;
            ArabicShaping shaper = null;

            if (source != null) {
                src = source.toCharArray();
                len = src.length;
            }

            try {
                shaper = new ArabicShaping(flags);
                shaper.shape(src, 0, len);
            }
            catch (Exception e) {
                ex = e;
            }

            if (!error.isInstance(ex)) {
                err("Error: Shaper " + shaper + "\n throws exception '" + ex + "'\n for input '" +
                    source + "'\n Expected exception: " + error);
            }
        }
    }
}
