001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.plot.expiration;
020
021import com.plotsquared.core.PlotSquared;
022import com.plotsquared.core.configuration.Settings;
023import com.plotsquared.core.plot.Plot;
024import com.plotsquared.core.plot.PlotArea;
025import com.plotsquared.core.plot.world.PlotAreaManager;
026import com.plotsquared.core.util.query.PlotQuery;
027import org.checkerframework.checker.nullness.qual.NonNull;
028
029import java.util.ArrayList;
030import java.util.Collection;
031import java.util.Collections;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.Set;
035import java.util.concurrent.TimeUnit;
036import java.util.stream.Collectors;
037
038public class ExpiryTask {
039
040    private final Settings.Auto_Clear settings;
041    private final PlotAreaManager plotAreaManager;
042    private long cutoffThreshold = Long.MIN_VALUE;
043
044    public ExpiryTask(final Settings.Auto_Clear settings, final @NonNull PlotAreaManager plotAreaManager) {
045        this.settings = settings;
046        this.plotAreaManager = plotAreaManager;
047    }
048
049    public Settings.Auto_Clear getSettings() {
050        return settings;
051    }
052
053    public boolean allowsArea(PlotArea area) {
054        return settings.WORLDS.contains(area.toString()) || settings.WORLDS
055                .contains(area.getWorldName()) || settings.WORLDS.contains("*");
056    }
057
058    public boolean applies(PlotArea area) {
059        if (allowsArea(area)) {
060            if (settings.REQUIRED_PLOTS <= 0) {
061                return true;
062            }
063            Set<Plot> plots = null;
064            if (cutoffThreshold != Long.MAX_VALUE
065                    && area.getPlots().size() > settings.REQUIRED_PLOTS
066                    || (plots = getPlotsToCheck()).size() > settings.REQUIRED_PLOTS) {
067                // calculate cutoff
068                if (cutoffThreshold == Long.MIN_VALUE) {
069                    plots = plots != null ? plots : getPlotsToCheck();
070                    int diff = settings.REQUIRED_PLOTS;
071                    boolean min = true;
072                    if (plots.size() > settings.REQUIRED_PLOTS) {
073                        min = false;
074                        diff = plots.size() - settings.REQUIRED_PLOTS;
075                    }
076                    ExpireManager expireManager = PlotSquared.platform().expireManager();
077                    List<Long> entireList =
078                            plots.stream().map(plot -> expireManager.getAge(plot, settings.DELETE_IF_OWNER_IS_UNKNOWN))
079                                    .collect(Collectors.toList());
080                    List<Long> top = new ArrayList<>(diff + 1);
081                    if (diff > 1000) {
082                        Collections.sort(entireList);
083                        cutoffThreshold = entireList.get(settings.REQUIRED_PLOTS);
084                    } else {
085                        loop:
086                        for (long num : entireList) {
087                            int size = top.size();
088                            if (size == 0) {
089                                top.add(num);
090                                continue;
091                            }
092                            long end = top.get(size - 1);
093                            if (min ? num < end : num > end) {
094                                for (int i = 0; i < size; i++) {
095                                    long existing = top.get(i);
096                                    if (min ? num < existing : num > existing) {
097                                        top.add(i, num);
098                                        if (size == diff) {
099                                            top.remove(size);
100                                        }
101                                        continue loop;
102                                    }
103                                }
104                            }
105                            if (size < diff) {
106                                top.add(num);
107                            }
108                        }
109                        cutoffThreshold = top.get(top.size() - 1);
110                    }
111                    // Add half a day, as expiry is performed each day
112                    cutoffThreshold += (TimeUnit.DAYS.toMillis(1) / 2);
113                }
114                return true;
115            } else {
116                cutoffThreshold = Long.MAX_VALUE;
117            }
118        }
119        return false;
120    }
121
122    public Set<Plot> getPlotsToCheck() {
123        final Collection<PlotArea> areas = new LinkedList<>();
124        for (final PlotArea plotArea : this.plotAreaManager.getAllPlotAreas()) {
125            if (this.allowsArea(plotArea)) {
126                areas.add(plotArea);
127            }
128        }
129        return PlotQuery.newQuery().inAreas(areas).asSet();
130    }
131
132    public boolean applies(long diff) {
133        return diff > TimeUnit.DAYS.toMillis(settings.DAYS) && diff > cutoffThreshold;
134    }
135
136    public boolean appliesAccountAge(long accountAge) {
137        if (settings.SKIP_ACCOUNT_AGE_DAYS != -1) {
138            return accountAge <= TimeUnit.DAYS.toMillis(settings.SKIP_ACCOUNT_AGE_DAYS);
139        }
140        return false;
141    }
142
143    public boolean needsAnalysis() {
144        return settings.THRESHOLD > 0;
145    }
146
147    public boolean applies(PlotAnalysis analysis) {
148        return analysis.getComplexity(settings) <= settings.THRESHOLD;
149    }
150
151    public boolean requiresConfirmation() {
152        return settings.CONFIRMATION;
153    }
154
155    /**
156     * Returns {@code true} if this task respects unknown owners
157     *
158     * @return {@code true} if unknown owners should be counted as never online
159     * @since 6.4.0
160     */
161    public boolean shouldDeleteForUnknownOwner() {
162        return settings.DELETE_IF_OWNER_IS_UNKNOWN;
163    }
164
165}