/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

package org.elasticsearch.analysis.common;

import java.util.HashSet;
import java.util.Set;

/**
 * A class to match character code points.
 */
public interface CharMatcher {

    class ByUnicodeCategory implements CharMatcher {

        public static CharMatcher of(byte unicodeCategory) {
            return new ByUnicodeCategory(unicodeCategory);
        }

        private final byte unicodeType;

        ByUnicodeCategory(byte unicodeType) {
            this.unicodeType = unicodeType;
        }

        @Override
        public boolean isTokenChar(int c) {
            return Character.getType(c) == unicodeType;
        }
    }

    enum Basic implements CharMatcher {
        LETTER {
            @Override
            public boolean isTokenChar(int c) {
                return Character.isLetter(c);
            }
        },
        DIGIT {
            @Override
            public boolean isTokenChar(int c) {
                return Character.isDigit(c);
            }
        },
        WHITESPACE {
            @Override
            public boolean isTokenChar(int c) {
                return Character.isWhitespace(c);
            }
        },
        PUNCTUATION {
            @Override
            public boolean isTokenChar(int c) {
                switch (Character.getType(c)) {
                    case Character.START_PUNCTUATION:
                    case Character.END_PUNCTUATION:
                    case Character.OTHER_PUNCTUATION:
                    case Character.CONNECTOR_PUNCTUATION:
                    case Character.DASH_PUNCTUATION:
                    case Character.INITIAL_QUOTE_PUNCTUATION:
                    case Character.FINAL_QUOTE_PUNCTUATION:
                        return true;
                    default:
                        return false;
                }
            }
        },
        SYMBOL {
            @Override
            public boolean isTokenChar(int c) {
                switch (Character.getType(c)) {
                    case Character.CURRENCY_SYMBOL:
                    case Character.MATH_SYMBOL:
                    case Character.OTHER_SYMBOL:
                    case Character.MODIFIER_SYMBOL:
                        return true;
                    default:
                        return false;
                }
            }
        }
    }

    final class Builder {
        private final Set<CharMatcher> matchers;

        Builder() {
            matchers = new HashSet<>();
        }

        public Builder or(CharMatcher matcher) {
            matchers.add(matcher);
            return this;
        }

        public CharMatcher build() {
            return switch (matchers.size()) {
                case 0 -> c -> false;
                case 1 -> matchers.iterator().next();
                default -> c -> {
                    for (CharMatcher matcher : matchers) {
                        if (matcher.isTokenChar(c)) {
                            return true;
                        }
                    }
                    return false;
                };
            };
        }
    }

    /** Returns true if, and only if, the provided character matches this character class. */
    boolean isTokenChar(int c);
}
