001/*
002 * PermissionsEx
003 * Copyright (C) zml and PermissionsEx contributors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package ca.stellardrift.permissionsex.minecraft.command.argument;
018
019import ca.stellardrift.permissionsex.context.ContextDefinition;
020import ca.stellardrift.permissionsex.context.ContextValue;
021import ca.stellardrift.permissionsex.impl.util.PCollections;
022import ca.stellardrift.permissionsex.minecraft.MinecraftPermissionsEx;
023import ca.stellardrift.permissionsex.minecraft.command.CommandException;
024import ca.stellardrift.permissionsex.minecraft.command.Commander;
025import ca.stellardrift.permissionsex.minecraft.command.PEXCommandPreprocessor;
026import ca.stellardrift.permissionsex.subject.CalculatedSubject;
027import ca.stellardrift.permissionsex.subject.SubjectRef;
028import cloud.commandframework.arguments.parser.ArgumentParseResult;
029import cloud.commandframework.arguments.parser.ArgumentParser;
030import cloud.commandframework.context.CommandContext;
031import cloud.commandframework.exceptions.parsing.NoInputProvidedException;
032import org.checkerframework.checker.nullness.qual.NonNull;
033import org.checkerframework.checker.nullness.qual.Nullable;
034
035import java.util.Collections;
036import java.util.List;
037import java.util.Queue;
038import java.util.regex.Pattern;
039import java.util.stream.Collectors;
040
041public final class ContextValueParser implements ArgumentParser<Commander, ContextValue<?>> {
042    private static final Pattern CONTEXT_SPLIT = Pattern.compile("=");
043
044    ContextValueParser() {
045    }
046
047    @Override
048    public @NonNull ArgumentParseResult<@NonNull ContextValue<?>> parse(
049        final @NonNull CommandContext<@NonNull Commander> ctx,
050        final @NonNull Queue<@NonNull String> queue
051    ) {
052        final @Nullable String provided = queue.peek();
053        if (provided == null) {
054            return ArgumentParseResult.failure(new NoInputProvidedException(ContextValueParser.class, ctx));
055        }
056        final String[] contexts = CONTEXT_SPLIT.split(provided, 2);
057        if (contexts.length != 2) {
058            return ArgumentParseResult.failure(new CommandException(Messages.CONTEXT_ERROR_FORMAT.tr()));
059        }
060
061        final @Nullable ContextDefinition<?> definition = ctx.<MinecraftPermissionsEx<?>>get(PEXCommandPreprocessor.PEX_MANAGER).engine()
062            .contextDefinition(contexts[0]);
063
064        if (definition == null) {
065            return ArgumentParseResult.failure(new CommandException(Messages.CONTEXT_ERROR_TYPE.tr(contexts[0])));
066        }
067
068        final ArgumentParseResult<ContextValue<?>> result = toContextValue(definition, contexts[1]);
069        if (result.getParsedValue().isPresent()) {
070            queue.remove();
071        }
072        return result;
073    }
074
075    private <V> ArgumentParseResult<ContextValue<?>> toContextValue(final ContextDefinition<V> definition, final String input) {
076        final @Nullable V value = definition.deserialize(input);
077
078        if (value == null) {
079            return ArgumentParseResult.failure(new CommandException(Messages.CONTEXT_ERROR_VALUE.tr(definition.name())));
080        } else {
081            return ArgumentParseResult.success(definition.createValue(value));
082        }
083    }
084
085    @Override
086    public @NonNull List<@NonNull String> suggestions(final @NonNull CommandContext<Commander> ctx, final @NonNull String input) {
087        final MinecraftPermissionsEx<?> manager = ctx.get(PEXCommandPreprocessor.PEX_MANAGER);
088        final String[] split = CONTEXT_SPLIT.split(input, 2);
089        if (split.length < 2) { // before =
090            return manager.engine().registeredContextTypes().stream()
091                .map(def -> def.name() + "=")
092                .collect(Collectors.toList());
093        } else { // <fully written type>=<partial value
094            final @Nullable ContextDefinition<?> definition = manager.engine().contextDefinition(split[0]);
095            final @Nullable SubjectRef<?> senderId = ctx.getSender().subjectIdentifier();
096            if (definition == null || senderId == null) {
097                return Collections.emptyList();
098            }
099
100            return serializeSuggestions(definition, manager.engine().subject(senderId).join());
101        }
102    }
103
104    private <V> List<String> serializeSuggestions(final ContextDefinition<V> definition, final CalculatedSubject subject) {
105        return PCollections.asVector(definition.suggestValues(subject), ent -> definition.name() + "=" + definition.serialize(ent));
106    }
107
108    @Override
109    public boolean isContextFree() {
110        return true;
111    }
112
113}