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;
018
019import ca.stellardrift.permissionsex.context.ContextDefinitionProvider;
020import ca.stellardrift.permissionsex.minecraft.MinecraftPermissionsEx;
021import ca.stellardrift.permissionsex.subject.CalculatedSubject;
022import ca.stellardrift.permissionsex.subject.SubjectRef;
023import ca.stellardrift.permissionsex.subject.SubjectTypeCollection;
024import net.kyori.adventure.text.BuildableComponent;
025import net.kyori.adventure.text.Component;
026import net.kyori.adventure.text.ComponentBuilder;
027import net.kyori.adventure.text.ComponentLike;
028import net.kyori.adventure.text.event.ClickEvent;
029import net.kyori.adventure.text.event.HoverEvent;
030import net.kyori.adventure.text.format.NamedTextColor;
031import net.kyori.adventure.text.format.Style;
032import net.kyori.adventure.text.format.TextColor;
033import net.kyori.adventure.text.format.TextDecoration;
034import org.checkerframework.checker.nullness.qual.Nullable;
035
036import java.util.concurrent.ExecutionException;
037
038import static net.kyori.adventure.text.Component.space;
039import static net.kyori.adventure.text.Component.text;
040
041/**
042 * Message formatting parameters for the current engine instance.
043 */
044public class MessageFormatter {
045    public static final TextColor DEFAULT_MESSAGE_COLOR = NamedTextColor.DARK_AQUA;
046    public static final TextColor DEFAULT_HIGHLIGHT_COLOR = TextColor.color(0x55cccc);
047    public static final Component EQUALS_SIGN = text("=", NamedTextColor.GRAY);
048    public static final Component SLASH = text("/");
049
050    private final MinecraftPermissionsEx<?> manager;
051    private final TextColor highlightColor;
052    private final TextColor messageColor;
053
054    /**
055     * Create a new formatter with default message and highlight colours.
056     *
057     * @param manager the permissions manager
058     */
059    public MessageFormatter(final MinecraftPermissionsEx<?> manager) {
060        this(manager, DEFAULT_MESSAGE_COLOR, DEFAULT_HIGHLIGHT_COLOR);
061    }
062
063    public MessageFormatter(
064            final MinecraftPermissionsEx<?> manager,
065            final TextColor messageColor,
066            final TextColor highlightColor) {
067        this.manager = manager;
068        this.messageColor = messageColor;
069        this.highlightColor = highlightColor;
070    }
071
072    final MinecraftPermissionsEx<?> manager() {
073        return this.manager;
074    }
075
076    /**
077     * Get the colour to be used for standard command responses.
078     *
079     * @return the response color
080     */
081    public final TextColor responseColor() {
082        return this.messageColor;
083    }
084
085    /**
086     * The color to be used for highlighted parts of messages.
087     *
088     * @return the highlight color
089     */
090    public final TextColor highlightColor() {
091        return this.highlightColor;
092    }
093
094    /**
095     * Given a command in standard format, correct it to refer to specifically the proxy format.
096     *
097     * @param cmd the original command
098     * @return the transformed command
099     */
100    protected String transformCommand(final String cmd) {
101        return cmd;
102    }
103
104    protected <I> @Nullable String friendlyName(final SubjectRef<I> reference) {
105        return null;
106    }
107
108    public final Component subject(final CalculatedSubject subject) {
109        return this.subject(subject.identifier());
110    }
111
112    /**
113     * Print the subject in a user-friendly manner. May link to the subject info printout
114     *
115     * @param subject The subject to show
116     * @return the formatted value
117     */
118    public <I> Component subject(final SubjectRef<I> subject) {
119        final SubjectTypeCollection<I> type = this.manager.engine().subjects(subject.type());
120        final String serializedIdent = subject.type().serializeIdentifier(subject.identifier());
121        @Nullable String name = this.friendlyName(subject);
122        if (name == null) {
123            try {
124                name = type.persistentData().data(subject.identifier(), null).get()
125                        .segment(ContextDefinitionProvider.GLOBAL_CONTEXT).options().get("name");
126            } catch (final ExecutionException | InterruptedException ex) {
127                throw new RuntimeException(ex);
128            }
129        }
130
131        final Component nameText;
132        if (name != null) {
133            nameText = text()
134                    .append(text(serializedIdent, NamedTextColor.GRAY))
135                    .append(SLASH)
136                    .append(text(name))
137                    .build();
138        } else {
139            nameText = text(serializedIdent);
140        }
141
142        return text()
143                .append(text(subject.type().name()).decorate(TextDecoration.BOLD))
144                .append(space())
145                .append(nameText)
146                .hoverEvent(HoverEvent.showText(Messages.FORMATTER_BUTTON_INFO_PROMPT.tr()))
147                .clickEvent(ClickEvent.runCommand(transformCommand("/pex " + subject.type().name() + ' ' + serializedIdent + " info")))
148                .build();
149    }
150
151    /**
152     * Create a clickable button that will execute a command or suggest a command to be executed
153     *
154     * @param type    The style of button to present
155     * @param tooltip A tooltip to optionally show when hovering over a button
156     * @param command The command to execute
157     * @param execute Whether the command provided will be executed or only added to the user's input
158     * @return the formatted text
159     */
160    public <C extends BuildableComponent<C, B>, B extends ComponentBuilder<C, B>> Component button(
161            B builder,
162            ButtonType type,
163            final @Nullable ComponentLike tooltip,
164            final String command,
165            final boolean execute
166    ) {
167        final TextColor buttonColor;
168        switch (type) {
169            case POSITIVE:
170                buttonColor = NamedTextColor.GREEN;
171                break;
172            case NEGATIVE:
173                buttonColor = NamedTextColor.RED;
174                break;
175            default:
176                buttonColor = this.highlightColor;
177        }
178        builder.color(buttonColor);
179
180        if (tooltip != null) {
181            builder.hoverEvent(HoverEvent.showText(tooltip));
182        }
183
184        if (execute) {
185            builder.clickEvent(ClickEvent.runCommand(transformCommand(command)));
186        } else {
187            builder.clickEvent(ClickEvent.suggestCommand(transformCommand(command)));
188        }
189        return builder.build();
190    }
191
192
193    public final <C extends BuildableComponent<C, B>, B extends ComponentBuilder<C, B>> B header(final B builder) {
194        return builder.decoration(TextDecoration.BOLD, true);
195    }
196
197    public final <C extends BuildableComponent<C, B>, B extends ComponentBuilder<C, B>> B hl(final B builder) {
198        return builder.color(this.highlightColor);
199    }
200
201    public final Style.Builder header(final Style.Builder builder) {
202        return builder.decoration(TextDecoration.BOLD, true);
203    }
204
205    public final Style.Builder hl(final Style.Builder builder) {
206        return builder.color(this.highlightColor);
207    }
208}