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.impl.rank;
018
019import ca.stellardrift.permissionsex.rank.RankLadder;
020import ca.stellardrift.permissionsex.subject.SubjectRef;
021import ca.stellardrift.permissionsex.context.ContextValue;
022import ca.stellardrift.permissionsex.subject.ImmutableSubjectData;
023import net.kyori.adventure.text.Component;
024import net.kyori.adventure.text.event.ClickEvent;
025import net.kyori.adventure.text.event.HoverEvent;
026import net.kyori.adventure.text.format.TextDecoration;
027import org.pcollections.PVector;
028
029import java.util.ArrayList;
030import java.util.List;
031import java.util.ListIterator;
032import java.util.Set;
033
034public abstract class AbstractRankLadder implements RankLadder {
035    private final String name;
036
037    protected AbstractRankLadder(String name) {
038        this.name = name;
039    }
040
041    @Override
042    public String name() {
043        return this.name;
044    }
045
046    @Override
047    public final ImmutableSubjectData promote(Set<ContextValue<?>> contexts, ImmutableSubjectData input) {
048        if (ranks().isEmpty()) {
049            return input;
050        }
051
052        return input.withSegment(contexts, seg -> {
053            final List<? extends SubjectRef<?>> originalParents = seg.parents();
054            if (originalParents.isEmpty()) {
055                return seg.plusParent(ranks().get(0));
056            } else {
057                int index;
058                final List<SubjectRef<?>> parents = new ArrayList<>(originalParents);
059                boolean found = false;
060                for (ListIterator<SubjectRef<?>> it = parents.listIterator(); it.hasNext();) {
061                    SubjectRef<?> parent = it.next();
062                    if ((index = ranks().indexOf(parent)) > -1) {
063                        if (index == ranks().size() - 1) {
064                            return seg;
065                        } else {
066                            it.set(ranks().get(index + 1));
067                            found = true;
068                        }
069                    }
070                }
071
072                if (found) {
073                    return seg.withParents(parents); // Promotion happened
074                } else {
075                    return seg.plusParent(ranks().get(0));
076                }
077            }
078        });
079
080    }
081
082    @Override
083    public final ImmutableSubjectData demote(Set<ContextValue<?>> contexts, ImmutableSubjectData input) {
084        if (ranks().isEmpty()) {
085            return input;
086        }
087
088        return input.withSegment(contexts, seg -> {
089            List<? extends SubjectRef<?>> originalParents = seg.parents();
090            if (originalParents.isEmpty()) {
091                return seg;
092            } else {
093                int index;
094                final List<SubjectRef<?>> parents = new ArrayList<>(originalParents); // TODO: rewrite to work with immutable login
095                boolean found = false;
096                for (ListIterator<SubjectRef<?>> it = parents.listIterator(); it.hasNext();) {
097                    SubjectRef<?> parent = it.next();
098                    if ((index = ranks().indexOf(parent)) > -1) {
099                        if (index == 0) {
100                            // At bottom of rank ladder, remove the rank entirely
101                            it.remove();
102                        } else {
103                            it.set(ranks().get(index - 1));
104                        }
105                        found = true;
106                    }
107                }
108                if (found) {
109                    return seg.withParents(parents);
110                } else {
111                    return seg;
112                }
113            }
114        });
115    }
116
117    @Override
118    public final boolean isOnLadder(final Set<ContextValue<?>> contexts, final ImmutableSubjectData subject) {
119        if (ranks().isEmpty()) {
120            return false;
121        }
122
123        for (final SubjectRef<?> par : subject.segment(contexts).parents()) {
124            if (ranks().contains(par)) {
125                return true;
126            }
127        }
128        return false;
129    }
130
131    @Override
132    public final int indexOf(final SubjectRef<?> subject) {
133        return ranks().indexOf(subject);
134    }
135
136    @Override
137    public final RankLadder with(final SubjectRef<?> subject) {
138        int indexOf = ranks().indexOf(subject);
139        if (indexOf != -1) {
140            return newWithRanks(this.ranks().minus(indexOf).plus(SubjectRef.mapKeySafe(subject)));
141        } else {
142            return newWithRanks(this.ranks().plus(SubjectRef.mapKeySafe(subject)));
143        }
144
145    }
146
147
148    @Override
149    public final RankLadder with(final SubjectRef<?> subject, int index) {
150        if (index > ranks().size() || index < 0) {
151            return this;
152        }
153
154        PVector<SubjectRef<?>> entries = this.ranks();
155        int indexOf = entries.indexOf(subject);
156        entries = entries.plus(index, subject);
157        if (indexOf != -1) {
158            if (indexOf >= index) {
159                ++indexOf;
160            }
161            entries = entries.minus(indexOf);
162        }
163        return newWithRanks(entries);
164    }
165
166    @Override
167    public final RankLadder without(final SubjectRef<?> subject) {
168        final PVector<SubjectRef<?>> out = this.ranks().minus(subject);
169        return this.ranks() == out ? this : newWithRanks(out);
170    }
171
172    @Override
173    public abstract PVector<SubjectRef<?>> ranks();
174
175    protected abstract RankLadder newWithRanks(final PVector<SubjectRef<?>> ents);
176
177    @Override
178    public final Component asComponent() {
179        return Component.text(build -> build.content(name())
180                .decoration(TextDecoration.BOLD, true)
181                .hoverEvent(HoverEvent.showText(Messages.FORMATTER_BUTTON_INFO_PROMPT.tr()))
182                .clickEvent(ClickEvent.runCommand("/pex rank " + name())));
183    }
184}