/**
 * This file is part of the Meeds project (https://meeds.io/).
 *
 * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * This program 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.exoplatform.webui.form.validator;

import org.exoplatform.commons.serialization.api.annotations.Serialized;
import org.exoplatform.web.application.CompoundApplicationMessage;
import org.exoplatform.webui.form.UIFormInput;

/**
 * Validate username whether the value is only alpha lower, digit, dot and underscore with first, last character is alpha lower
 * or digit and cannot contain consecutive underscore, dot or both.
 *
 */

@Serialized
public class UsernameValidator extends MultipleConditionsValidator {
    protected static final int DEFAULT_MIN_LENGTH = 3;
    protected static final int DEFAULT_MAX_LENGTH = 30;
    protected Integer min = DEFAULT_MIN_LENGTH;
    protected Integer max = DEFAULT_MAX_LENGTH;
    public static final String ALLOWED_SYMBOLS = "'_', '.'";

    // required by @Serialized
    public UsernameValidator() {
    }

    public UsernameValidator(Integer min, Integer max) {
        this.min = min;
        this.max = max;
    }

    protected void validate(String value, String label, CompoundApplicationMessage messages, UIFormInput uiInput) {
        validate(value, label, messages, min, max);
    }

    static void validate(String value, String label, CompoundApplicationMessage messages, Integer min, Integer max) {
        char[] buff = value.toCharArray();
        if (buff.length < min || buff.length > max) {
            messages.addMessage("StringLengthValidator.msg.length-invalid",
                    new Object[] { label, min.toString(), max.toString() });
        }

        if (!Character.isLowerCase(buff[0])) {
            messages.addMessage("FirstCharacterUsernameValidator.msg", new Object[] { label });
        }

        char c = buff[buff.length - 1];
        if (!isLowerCaseLetterOrDigit(c)) {
            messages.addMessage("LastCharacterUsernameValidator.msg", new Object[] { label, c });
        }

        boolean hasConsecutive = false;
        boolean hasInvalid = false;
        for (int i = 1; i < buff.length - 1; i++) {
            c = buff[i];

            if (isLowerCaseLetterOrDigit(c)) {
                continue;
            }

            if (isSymbol(c)) {
                char next = buff[i + 1];
                if (isSymbol(next)) {
                    if (!hasConsecutive) {
                        messages.addMessage("ConsecutiveSymbolValidator.msg", new Object[] { label, ALLOWED_SYMBOLS });
                        hasConsecutive = true;
                    }
                } else if (!Character.isLetterOrDigit(next)) {
                    if (!hasInvalid) {
                        messages.addMessage("UsernameValidator.msg.Invalid-char", new Object[] { label });
                        hasInvalid = true;
                    }
                }
            } else {
                if (!hasInvalid) {
                    messages.addMessage("UsernameValidator.msg.Invalid-char", new Object[] { label });
                    hasInvalid = true;
                }
            }

            // if we have both error conditions, fail "fast" instead of going on
            if (hasConsecutive && hasInvalid) {
                break;
            }
        }
    }

    private static boolean isLowerCaseLetterOrDigit(char character) {
        return Character.isDigit(character) || (character >= 'a' && character <= 'z');
    }

    private static boolean isSymbol(char c) {
        return c == '_' || c == '.';
    }
}
