/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2013, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.as.domain.management.security.adduser;

import static org.jboss.as.domain.management.DomainManagementMessages.MESSAGES;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;

/**
 * State to perform validation of the supplied username.
 *
 * Checks include: - Valid characters. Easy to guess user names. Duplicate users.
 *
 * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
 */
public class ValidateUserState extends AbstractValidationState {

    private static final String[] BAD_USER_NAMES = {"admin", "administrator", "root"};

    private static final char[] VALID_PUNCTUATION;
    private static final String VALID_SYMBOLS;

    static {
        char[] validPunctuation = { '.', '-', ',', '@', '/', '\\', '=' };
        Arrays.sort(validPunctuation);
        VALID_PUNCTUATION = validPunctuation;

        StringBuilder sb = new StringBuilder();
        for (int i=0 ; i < VALID_PUNCTUATION.length ; i++) {
            sb.append("\"");
            sb.append(VALID_PUNCTUATION[i]);
            sb.append("\"");
            if (i < VALID_PUNCTUATION.length -1) {
                sb.append(", ");
            }
        }
        VALID_SYMBOLS = sb.toString();
    }

    private final StateValues stateValues;
    private final ConsoleWrapper theConsole;

    public ValidateUserState(ConsoleWrapper theConsole, final StateValues stateValues) {
        this.theConsole = theConsole;
        this.stateValues = stateValues;
    }

    @Override
    protected Collection<State> getValidationStates() {
        List<State> validationStates = new ArrayList<State>(3);
        validationStates.add(getValidCharactersState());
        validationStates.add(getDuplicateCheckState());
        validationStates.add(getCommonNamesCheckState());

        return validationStates;
    }

    @Override
    protected State getSuccessState() {
        return new PromptPasswordState(theConsole, stateValues, false);
    }

    private State getRetryState() {
        return stateValues.isSilentOrNonInteractive() ? null : new PromptNewUserState(theConsole, stateValues);
    }

    private State getValidCharactersState() {
        return new State() {

            @Override
            public State execute() {
                for (char currentChar : stateValues.getUserName().toCharArray()) {
                    if ((!isValidPunctuation(currentChar))
                            && (Character.isLetter(currentChar) || Character.isDigit(currentChar)) == false) {
                        return new ErrorState(theConsole, MESSAGES.usernameNotAlphaNumeric(VALID_SYMBOLS), getRetryState(), stateValues);
                    }
                }

                return ValidateUserState.this;
            }

            private boolean isValidPunctuation(char currentChar) {
                return (Arrays.binarySearch(VALID_PUNCTUATION, currentChar) >= 0);
            }
        };
    }



    private State getDuplicateCheckState() {
        return new State() {

            @Override
            public State execute() {
                if (stateValues.isExistingDisabledUser() || stateValues.isExistingEnabledUser()) {
                    State duplicateContinuing = stateValues.isSilentOrNonInteractive() ? null :
                            new PromptNewUserState(theConsole, stateValues);
                    stateValues.setExistingUser(true);
                    if (stateValues.isSilentOrNonInteractive()) {
                        return ValidateUserState.this;
                    } else {
                        final boolean existingDisabledUser = stateValues.isExistingDisabledUser();
                        if (existingDisabledUser) {
                            theConsole.printf(MESSAGES.aboutToUpdateDisabledUser(stateValues.getUserName()));
                        } else {
                            theConsole.printf(MESSAGES.aboutToUpdateEnabledUser(stateValues.getUserName()));
                        }
                        theConsole.printf(AddUser.NEW_LINE);
                        String response = theConsole.readLine("(a): ");
                        if (response == null) {
                            // This will return user to the command prompt so add a new line to ensure
                            // the command prompt is on the next line.
                            theConsole.printf(AddUser.NEW_LINE);
                            return null;
                        }
                        Option option = convertResponse(response, existingDisabledUser);
                        switch (option) {
                            case NEW:
                                return duplicateContinuing;
                            case UPDATE:
                                break;
                            case ENABLE:
                                stateValues.getOptions().setEnableDisableMode(true);
                                stateValues.getOptions().setDisable(false);
                                return new PreModificationState(theConsole, stateValues);
                            case DISABLE:
                                stateValues.getOptions().setEnableDisableMode(true);
                                stateValues.getOptions().setDisable(true);
                                return new PreModificationState(theConsole, stateValues);
                            default:
                                return new ErrorState(theConsole, MESSAGES.invalidChoiceUpdateUserResponse(), this);
                        }
                        return ValidateUserState.this;
                    }
                } else {
                    stateValues.setExistingUser(false);
                    return ValidateUserState.this;
                }
            }
        };
    }

    private State getCommonNamesCheckState() {
        return new State() {

            @Override
            public State execute() {
                // If this is updating an existing user then the name is already accepted.
                if (stateValues.isExistingUser() == false && stateValues.isSilentOrNonInteractive() == false) {
                    for (String current : BAD_USER_NAMES) {
                        if (current.equals(stateValues.getUserName().toLowerCase(Locale.ENGLISH))) {
                            String message = MESSAGES.usernameEasyToGuess(stateValues.getUserName());
                            String prompt = MESSAGES.sureToAddUser(stateValues.getUserName());

                            return new ConfirmationChoice(theConsole, message, prompt, ValidateUserState.this, getRetryState());
                        }
                    }
                }

                return ValidateUserState.this;
            }
        };
    }

    private Option convertResponse(final String response, final boolean existingDisabledUser) {
        String responseLowerCase = response.toLowerCase(Locale.ENGLISH);
        if ("".equals(responseLowerCase) || "a".equals(responseLowerCase)) {
            return Option.UPDATE;
        } else if ("b".equals(responseLowerCase)) {
            // Opposite option...
            if (existingDisabledUser) {
                // ... when the existing user is disabled, enable!
                return Option.ENABLE;
            } else {
                // ... when the existing user is enabled, disable!
                return Option.DISABLE;
            }
        } else if ("c".equals(responseLowerCase)) {
            return Option.NEW;
        }
        return Option.INVALID;
    }

    private enum Option {
        UPDATE,
        DISABLE,
        ENABLE,
        NEW,
        INVALID
    }
}
