/*
 * Copyright (c) 2007, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test
 * @bug 4830803 4886934 6565620 6959267 7070436 7198195 8032446 8072600 8202771
 *      8221431
 * @summary  Check that the UnicodeBlock forName() method works as expected
 *      and block ranges are correct for all Unicode characters.
 * @library /lib/testlibrary/java/lang
 * @run main CheckBlocks
 * @author John O'Conner
 */

import java.lang.Character.UnicodeBlock;
import java.lang.reflect.Field;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.HashSet;
import java.util.Locale;

public class CheckBlocks {

    static boolean err = false;
    static Class<?> clazzUnicodeBlock;

    public static void main(String[] args) throws Exception {
        generateBlockList();

        try {
            clazzUnicodeBlock = Class.forName("java.lang.Character$UnicodeBlock");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Class.forName(\"java.lang.Character$UnicodeBlock\") failed.");
        }

        for (Block blk : blocks) {
            test4830803_1(blk);
            test4830803_2();
            test4886934(blk);
        }

        test8202771();

        if (err) {
            throw new RuntimeException("Failed");
        } else {
            System.out.println("Passed");
        }
    }

    /**
     * Check that the UnicodeBlock forName() method works as expected.
     */
    private static void test4830803_1(Block blk) throws Exception {

        /*
         * Try 3 forms of block name in the forName() method. Each form should
         * produce the same expected block.
         */
        String blkName = blk.getName();

        // For backward compatibility
        switch (blkName) {
            case "COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS":
                blkName = "COMBINING_MARKS_FOR_SYMBOLS";
                System.out.println("*** COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS"
                        + " is replaced with COMBINING_MARKS_FOR_SYMBOLS"
                        + " for backward compatibility.");
                break;
            case "GREEK_AND_COPTIC":
                blkName = "GREEK";
                System.out.println("*** GREEK_AND_COPTIC is replaced with GREEK"
                        + " for backward compatibility.");
                break;
            case "CYRILLIC_SUPPLEMENT":
                blkName = "CYRILLIC_SUPPLEMENTARY";
                System.out.println("*** CYRILLIC_SUPPLEMENT is replaced with"
                        + " CYRILLIC_SUPPLEMENTARY for backward compatibility.");
                break;
            default:
                break;
        }

        String expectedBlock = null;
        try {
            expectedBlock = clazzUnicodeBlock.getField(blkName).getName();
        } catch (NoSuchFieldException | SecurityException e) {
            System.err.println("Error: " + blkName + " was not found.");
            err = true;
            return;
        }

        String canonicalBlockName = blk.getOriginalName();
        String idBlockName = expectedBlock;
        String regexBlockName = toRegExString(canonicalBlockName);

        if (regexBlockName == null) {
            System.err.println("Error: Block name which was processed with regex was null.");
            err = true;
            return;
        }

        if (!expectedBlock.equals(UnicodeBlock.forName(canonicalBlockName).toString())) {
            System.err.println("Error #1: UnicodeBlock.forName(\"" +
                    canonicalBlockName + "\") returned wrong value.\n\tGot: " +
                    UnicodeBlock.forName(canonicalBlockName) +
                    "\n\tExpected: " + expectedBlock);
            err = true;
        }

        if (!expectedBlock.equals(UnicodeBlock.forName(idBlockName).toString())) {
            System.err.println("Error #2: UnicodeBlock.forName(\"" +
                    idBlockName + "\") returned wrong value.\n\tGot: " +
                    UnicodeBlock.forName(idBlockName) +
                    "\n\tExpected: " + expectedBlock);
            err = true;
        }

        if (!expectedBlock.equals(UnicodeBlock.forName(regexBlockName).toString())) {
            System.err.println("Error #3: UnicodeBlock.forName(\"" +
                    regexBlockName + "\") returned wrong value.\n\tGot: " +
                    UnicodeBlock.forName(regexBlockName) +
                    "\n\tExpected: " + expectedBlock);
            err = true;
        }
    }

    /**
     * now try a bad block name. This should produce an IAE.
     */
    private static void test4830803_2() {
        boolean threwExpected = false;

        try {
            UnicodeBlock block = UnicodeBlock.forName("notdefined");
        }
        catch(IllegalArgumentException e) {
            threwExpected = true;
        }

        if (threwExpected == false) {
            System.err.println("Error: UnicodeBlock.forName(\"notdefined\") should throw IllegalArgumentException.");
            err = true;
        }
    }

    /**
     * Convert the argument to a block name form used by the regex package.
     * That is, remove all spaces.
     */
    private static String toRegExString(String str) {
        String[] tokens = null;
        StringBuilder retStr = new StringBuilder();
        try {
            tokens = str.split(" ");
        }
        catch(java.util.regex.PatternSyntaxException e) {
            return null;
        }
        for(int x=0; x < tokens.length; ++x) {
            retStr.append(tokens[x]);
        }
        return retStr.toString();
    }

    private static void test4886934(Block blk) {
        String blkName = blk.getName();
        String blkOrigName = blk.getOriginalName();
        UnicodeBlock block;
        String blockName;

        // For backward compatibility
        switch (blkName) {
            case "COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS":
                blkName = "COMBINING_MARKS_FOR_SYMBOLS";
                System.out.println("*** COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS"
                        + " is replaced with COMBINING_MARKS_FOR_SYMBOLS"
                        + " for backward compatibility.");
                break;
            case "GREEK_AND_COPTIC":
                blkName = "GREEK";
                System.out.println("*** GREEK_AND_COPTIC is replaced with GREEK"
                        + " for backward compatibility.");
                break;
            case "CYRILLIC_SUPPLEMENT":
                blkName = "CYRILLIC_SUPPLEMENTARY";
                System.out.println("*** CYRILLIC_SUPPLEMENT is replaced with"
                        + " CYRILLIC_SUPPLEMENTARY for backward compatibility.");
                break;
            default:
                break;
        }

        for (int ch = blk.getBegin(); ch <= blk.getEnd(); ch++) {
            block = UnicodeBlock.of(ch);
            if (block == null) {
                System.err.println("Error: The block for " + blkName
                        + " is missing. Please check java.lang.Character.UnicodeBlock.");
                err = true;
                break;
            }
            blockName = block.toString();
            if (!blockName.equals(blkName)) {
                System.err.println("Error: Character(0x"
                        + Integer.toHexString(ch).toUpperCase()
                        + ") should be in \"" + blkName + "\" block "
                        + "(Block name is \"" + blkOrigName + "\")"
                        + " but found in \"" + blockName + "\" block.");
                err = true;
            }
        }
    }

    /**
     * Check if every Field of Character.UnicodeBlock is a valid Unicode Block.
     */
    private static void test8202771() {
        Field[] fields = clazzUnicodeBlock.getFields();

        for (Field f : fields) {
            // Handle Deprecated field "SURROGATES_AREA".
            if (f.getAnnotation(Deprecated.class) != null) {
                continue;
            }

            String blkName = f.getName();
            switch (blkName) {
                case "COMBINING_MARKS_FOR_SYMBOLS":
                    validateBlock("COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS");
                    break;
                case "GREEK":
                    validateBlock("GREEK_AND_COPTIC");
                    break;
                case "CYRILLIC_SUPPLEMENTARY":
                    validateBlock("CYRILLIC_SUPPLEMENT");
                    break;
                default:
                    validateBlock(blkName);
                    break;
            }
        }
    }

    private static void validateBlock(String blkName) {
        for (Block block : blocks) {
            String blockName = block.getName();
            if (blockName.equals(blkName)) {
                return;
            }
        }
        err = true;
        System.err.println(blkName + " is not a valid Unicode Block.");
    }

    // List of all Unicode blocks, their start, and end codepoints.
    public static HashSet<Block> blocks = new HashSet<>();

    private static void generateBlockList() throws Exception {
        File blockData = UCDFiles.BLOCKS.toFile();
        try (BufferedReader f = new BufferedReader(new FileReader(blockData))) {
            String line;
            while ((line = f.readLine()) != null) {
                if (line.length() == 0 || line.charAt(0) == '#') {
                    continue;
                }

                int index1 = line.indexOf('.');
                int begin = Integer.parseInt(line.substring(0, index1), 16);
                int index2 = line.indexOf(';');
                int end = Integer.parseInt(line.substring(index1 + 2, index2), 16);
                String name = line.substring(index2 + 1).trim();

                System.out.println("  Adding a Block(" + Integer.toHexString(begin) + ", " + Integer.toHexString(end)
                        + ", " + name + ")");
                blocks.add(new Block(begin, end, name));
            }
        }
    }
}

class Block {

    public Block() {
        blockBegin = 0;
        blockEnd = 0;
        blockName = null;
    }

    public Block(int begin, int end, String name) {
        blockBegin = begin;
        blockEnd = end;
        blockName = name.replaceAll("[ -]", "_").toUpperCase(Locale.ENGLISH);
        originalBlockName = name;
    }

    public int getBegin() {
        return blockBegin;
    }

    public int getEnd() {
        return blockEnd;
    }

    public String getName() {
        return blockName;
    }

    public String getOriginalName() {
        return originalBlockName;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        if (!(obj instanceof Block)) return false;

        Block other = (Block)obj;
        return other.blockBegin == blockBegin &&
                other.blockEnd == blockEnd &&
                other.blockName.equals(blockName) &&
                other.originalBlockName.equals(originalBlockName);
    }
    int blockBegin, blockEnd;
    String blockName, originalBlockName;
}
