// 
// Decompiled by Procyon v0.6.0
// 

package io.sentry.transport;

import java.io.IOException;
import io.sentry.ISentryLifecycleToken;
import java.util.TimerTask;
import io.sentry.util.StringUtils;
import java.util.Arrays;
import java.util.Collections;
import io.sentry.hints.DiskFlushNotification;
import io.sentry.hints.Retryable;
import io.sentry.util.HintUtils;
import io.sentry.hints.SubmissionResult;
import java.util.Iterator;
import io.sentry.SentryLevel;
import io.sentry.clientreport.DiscardReason;
import java.util.ArrayList;
import io.sentry.SentryEnvelopeItem;
import io.sentry.Hint;
import io.sentry.SentryEnvelope;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ConcurrentHashMap;
import io.sentry.util.AutoClosableReentrantLock;
import org.jetbrains.annotations.Nullable;
import java.util.Timer;
import java.util.List;
import java.util.Date;
import io.sentry.DataCategory;
import java.util.Map;
import io.sentry.SentryOptions;
import org.jetbrains.annotations.NotNull;
import java.io.Closeable;

public final class RateLimiter implements Closeable
{
    private static final int HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS = 60000;
    @NotNull
    private final ICurrentDateProvider currentDateProvider;
    @NotNull
    private final SentryOptions options;
    @NotNull
    private final Map<DataCategory, Date> sentryRetryAfterLimit;
    @NotNull
    private final List<IRateLimitObserver> rateLimitObservers;
    @Nullable
    private Timer timer;
    @NotNull
    private final AutoClosableReentrantLock timerLock;
    
    public RateLimiter(@NotNull final ICurrentDateProvider currentDateProvider, @NotNull final SentryOptions options) {
        this.sentryRetryAfterLimit = new ConcurrentHashMap<DataCategory, Date>();
        this.rateLimitObservers = new CopyOnWriteArrayList<IRateLimitObserver>();
        this.timer = null;
        this.timerLock = new AutoClosableReentrantLock();
        this.currentDateProvider = currentDateProvider;
        this.options = options;
    }
    
    public RateLimiter(@NotNull final SentryOptions options) {
        this(CurrentDateProvider.getInstance(), options);
    }
    
    @Nullable
    public SentryEnvelope filter(@NotNull final SentryEnvelope envelope, @NotNull final Hint hint) {
        List<SentryEnvelopeItem> dropItems = null;
        for (final SentryEnvelopeItem item : envelope.getItems()) {
            if (this.isRetryAfter(item.getHeader().getType().getItemType())) {
                if (dropItems == null) {
                    dropItems = new ArrayList<SentryEnvelopeItem>();
                }
                dropItems.add(item);
                this.options.getClientReportRecorder().recordLostEnvelopeItem(DiscardReason.RATELIMIT_BACKOFF, item);
            }
        }
        if (dropItems == null) {
            return envelope;
        }
        this.options.getLogger().log(SentryLevel.WARNING, "%d envelope items will be dropped due rate limiting.", dropItems.size());
        final List<SentryEnvelopeItem> toSend = new ArrayList<SentryEnvelopeItem>();
        for (final SentryEnvelopeItem item2 : envelope.getItems()) {
            if (!dropItems.contains(item2)) {
                toSend.add(item2);
            }
        }
        if (toSend.isEmpty()) {
            this.options.getLogger().log(SentryLevel.WARNING, "Envelope discarded due all items rate limited.", new Object[0]);
            this.markHintWhenSendingFailed(hint, false);
            return null;
        }
        return new SentryEnvelope(envelope.getHeader(), toSend);
    }
    
    public boolean isActiveForCategory(@NotNull final DataCategory dataCategory) {
        final Date currentDate = new Date(this.currentDateProvider.getCurrentTimeMillis());
        final Date dateAllCategories = this.sentryRetryAfterLimit.get(DataCategory.All);
        if (dateAllCategories != null && !currentDate.after(dateAllCategories)) {
            return true;
        }
        if (DataCategory.Unknown.equals(dataCategory)) {
            return false;
        }
        final Date dateCategory = this.sentryRetryAfterLimit.get(dataCategory);
        return dateCategory != null && !currentDate.after(dateCategory);
    }
    
    public boolean isAnyRateLimitActive() {
        final Date currentDate = new Date(this.currentDateProvider.getCurrentTimeMillis());
        for (final DataCategory dataCategory : this.sentryRetryAfterLimit.keySet()) {
            final Date dateCategory = this.sentryRetryAfterLimit.get(dataCategory);
            if (dateCategory != null && !currentDate.after(dateCategory)) {
                return true;
            }
        }
        return false;
    }
    
    private void markHintWhenSendingFailed(@NotNull final Hint hint, final boolean retry) {
        HintUtils.runIfHasType(hint, SubmissionResult.class, result -> result.setResult(false));
        HintUtils.runIfHasType(hint, Retryable.class, retryable -> retryable.setRetry(retry));
        HintUtils.runIfHasType(hint, DiskFlushNotification.class, diskFlushNotification -> {
            diskFlushNotification.markFlushed();
            this.options.getLogger().log(SentryLevel.DEBUG, "Disk flush envelope fired due to rate limit", new Object[0]);
        });
    }
    
    private boolean isRetryAfter(@NotNull final String itemType) {
        final List<DataCategory> dataCategory = this.getCategoryFromItemType(itemType);
        for (final DataCategory category : dataCategory) {
            if (this.isActiveForCategory(category)) {
                return true;
            }
        }
        return false;
    }
    
    @NotNull
    private List<DataCategory> getCategoryFromItemType(@NotNull final String itemType) {
        switch (itemType) {
            case "event": {
                return Collections.singletonList(DataCategory.Error);
            }
            case "session": {
                return Collections.singletonList(DataCategory.Session);
            }
            case "attachment": {
                return Collections.singletonList(DataCategory.Attachment);
            }
            case "profile": {
                return Collections.singletonList(DataCategory.Profile);
            }
            case "profile_chunk": {
                return Arrays.asList(DataCategory.ProfileChunkUi, DataCategory.ProfileChunk);
            }
            case "transaction": {
                return Collections.singletonList(DataCategory.Transaction);
            }
            case "check_in": {
                return Collections.singletonList(DataCategory.Monitor);
            }
            case "replay_video": {
                return Collections.singletonList(DataCategory.Replay);
            }
            case "feedback": {
                return Collections.singletonList(DataCategory.Feedback);
            }
            case "log": {
                return Collections.singletonList(DataCategory.LogItem);
            }
            case "span": {
                return Collections.singletonList(DataCategory.Span);
            }
            case "trace_metric": {
                return Collections.singletonList(DataCategory.TraceMetric);
            }
            default: {
                return Collections.singletonList(DataCategory.Unknown);
            }
        }
    }
    
    public void updateRetryAfterLimits(@Nullable final String sentryRateLimitHeader, @Nullable final String retryAfterHeader, final int errorCode) {
        if (sentryRateLimitHeader != null) {
            for (String limit : sentryRateLimitHeader.split(",", -1)) {
                limit = limit.replace(" ", "");
                final String[] rateLimit = limit.split(":", -1);
                if (rateLimit.length > 0) {
                    final String retryAfter = rateLimit[0];
                    final long retryAfterMillis = this.parseRetryAfterOrDefault(retryAfter);
                    if (rateLimit.length > 1) {
                        final String allCategories = rateLimit[1];
                        final Date date = new Date(this.currentDateProvider.getCurrentTimeMillis() + retryAfterMillis);
                        if (allCategories != null && !allCategories.isEmpty()) {
                            final String[] split2;
                            final String[] categories = split2 = allCategories.split(";", -1);
                            for (final String catItem : split2) {
                                DataCategory dataCategory = DataCategory.Unknown;
                                try {
                                    final String catItemCapitalized = StringUtils.camelCase(catItem);
                                    if (catItemCapitalized != null) {
                                        dataCategory = DataCategory.valueOf(catItemCapitalized);
                                    }
                                    else {
                                        this.options.getLogger().log(SentryLevel.ERROR, "Couldn't capitalize: %s", catItem);
                                    }
                                }
                                catch (final IllegalArgumentException e) {
                                    this.options.getLogger().log(SentryLevel.INFO, e, "Unknown category: %s", catItem);
                                }
                                if (!DataCategory.Unknown.equals(dataCategory)) {
                                    this.applyRetryAfterOnlyIfLonger(dataCategory, date);
                                }
                            }
                        }
                        else {
                            this.applyRetryAfterOnlyIfLonger(DataCategory.All, date);
                        }
                    }
                }
            }
        }
        else if (errorCode == 429) {
            final long retryAfterMillis2 = this.parseRetryAfterOrDefault(retryAfterHeader);
            final Date date2 = new Date(this.currentDateProvider.getCurrentTimeMillis() + retryAfterMillis2);
            this.applyRetryAfterOnlyIfLonger(DataCategory.All, date2);
        }
    }
    
    private void applyRetryAfterOnlyIfLonger(@NotNull final DataCategory dataCategory, @NotNull final Date date) {
        final Date oldDate = this.sentryRetryAfterLimit.get(dataCategory);
        if (oldDate == null || date.after(oldDate)) {
            this.sentryRetryAfterLimit.put(dataCategory, date);
            this.notifyRateLimitObservers();
            try (final ISentryLifecycleToken ignored = this.timerLock.acquire()) {
                if (this.timer == null) {
                    this.timer = new Timer(true);
                }
                this.timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        RateLimiter.this.notifyRateLimitObservers();
                    }
                }, date);
            }
        }
    }
    
    private long parseRetryAfterOrDefault(@Nullable final String retryAfterHeader) {
        long retryAfterMillis = 60000L;
        if (retryAfterHeader != null) {
            try {
                retryAfterMillis = (long)(Double.parseDouble(retryAfterHeader) * 1000.0);
            }
            catch (final NumberFormatException ex) {}
        }
        return retryAfterMillis;
    }
    
    private void notifyRateLimitObservers() {
        for (final IRateLimitObserver observer : this.rateLimitObservers) {
            observer.onRateLimitChanged(this);
        }
    }
    
    public void addRateLimitObserver(@NotNull final IRateLimitObserver observer) {
        this.rateLimitObservers.add(observer);
    }
    
    public void removeRateLimitObserver(@NotNull final IRateLimitObserver observer) {
        this.rateLimitObservers.remove(observer);
    }
    
    @Override
    public void close() throws IOException {
        try (final ISentryLifecycleToken ignored = this.timerLock.acquire()) {
            if (this.timer != null) {
                this.timer.cancel();
                this.timer = null;
            }
        }
        this.rateLimitObservers.clear();
    }
    
    public interface IRateLimitObserver
    {
        void onRateLimitChanged(@NotNull final RateLimiter p0);
    }
}
