import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.net.URL;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Main {
    private static final String ZEROES = "000";
    public static String codePointToHexString(int codePoint) {
        assert codePoint >= 0;
        String str = Integer.toString(codePoint, 16).toUpperCase();
        if (str.length() < 4) str = ZEROES.substring(3 - 4 + str.length()) + str;
        return str;
    }

    private static class Block {
        public final int startCodePoint;
        public final int endCodePoint;
        public final String description;

        public Block(int startCodePoint, int endCodePoint, String description) {
            this.startCodePoint = startCodePoint;
            this.endCodePoint = endCodePoint;
            this.description = description;
        }

        @Override
        public String toString() {
            return codePointToHexString(startCodePoint) + ".." + codePointToHexString(endCodePoint) + "; " + description;
        }
    }

    public static void main(String[] args) throws Throwable {
        final URL blocksTxtURL = new URL("http://w...content-available-to-author-only...e.org/Public/UCD/latest/ucd/Blocks.txt");
        final LineNumberReader lnr = new LineNumberReader(new InputStreamReader(blocksTxtURL.openStream()));

        final List<Block> blocks = new ArrayList<Block>();

        String line;
        while ((line = lnr.readLine()) != null) {
            if (line.isEmpty() || line.charAt(0) == '#') continue;
            final int semicolonPos = line.indexOf(';');
            if (semicolonPos < 0) continue;

            final int dotdotPos = line.lastIndexOf("..", semicolonPos);
            if (dotdotPos < 0) continue;

            final int startCodePoint = Integer.parseInt(line.substring(0, dotdotPos), 16);
            final int endCodePoint = Integer.parseInt(line.substring(dotdotPos + 2, semicolonPos), 16);
            final String description = line.substring(semicolonPos + 1).trim();
            blocks.add(new Block(startCodePoint, endCodePoint, description));
        }

        blocks.add(new Block(0, 0xFFFF, "(BMP)"));

        for (final Block b : blocks) {
            System.out.println(b);

            final Map<Integer, List<String>> m = new HashMap<Integer, List<String>>();
            for (int i = b.startCodePoint; i < b.endCodePoint; ++i) {
                final String str = new String(new int[] { i }, 0, 1);
                final String normStr = Normalizer.normalize(str, Normalizer.Form.NFC);
                final int codePointCount = normStr.codePointCount(0, normStr.length());
                if (!m.containsKey(codePointCount)) m.put(codePointCount, new ArrayList<String>());
                m.get(codePointCount).add(str);
            }

            for (final Map.Entry<Integer, List<String>> e : m.entrySet()) {
                System.out.println(e.getKey() + ": " + e.getValue().size());
                if (e.getKey() > 1) {
                    for (final String str : e.getValue()) {
                        System.out.print(" " + codePointToHexString(str.codePointAt(0)));
                    }
                    System.out.println();
                }
            }

            System.out.println();
        }
    }
}