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

package io.sentry.logger;

import java.util.List;
import io.sentry.SentryLogEvents;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import io.sentry.ISentryLifecycleToken;
import java.util.concurrent.RejectedExecutionException;
import io.sentry.SentryLevel;
import io.sentry.JsonSerializable;
import io.sentry.util.JsonSerializationUtils;
import io.sentry.DataCategory;
import io.sentry.clientreport.DiscardReason;
import io.sentry.SentryExecutorService;
import java.util.concurrent.ConcurrentLinkedQueue;
import io.sentry.transport.ReusableCountLatch;
import io.sentry.util.AutoClosableReentrantLock;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.Future;
import io.sentry.ISentryExecutorService;
import io.sentry.SentryLogEvent;
import java.util.Queue;
import io.sentry.ISentryClient;
import org.jetbrains.annotations.NotNull;
import io.sentry.SentryOptions;

public class LoggerBatchProcessor implements ILoggerBatchProcessor
{
    public static final int FLUSH_AFTER_MS = 5000;
    public static final int MAX_BATCH_SIZE = 100;
    public static final int MAX_QUEUE_SIZE = 1000;
    @NotNull
    protected final SentryOptions options;
    @NotNull
    private final ISentryClient client;
    @NotNull
    private final Queue<SentryLogEvent> queue;
    @NotNull
    private final ISentryExecutorService executorService;
    @Nullable
    private volatile Future<?> scheduledFlush;
    @NotNull
    private static final AutoClosableReentrantLock scheduleLock;
    private volatile boolean hasScheduled;
    @NotNull
    private final ReusableCountLatch pendingCount;
    
    public LoggerBatchProcessor(@NotNull final SentryOptions options, @NotNull final ISentryClient client) {
        this.hasScheduled = false;
        this.pendingCount = new ReusableCountLatch();
        this.options = options;
        this.client = client;
        this.queue = new ConcurrentLinkedQueue<SentryLogEvent>();
        this.executorService = new SentryExecutorService(options);
    }
    
    @Override
    public void add(@NotNull final SentryLogEvent logEvent) {
        if (this.pendingCount.getCount() >= 1000) {
            this.options.getClientReportRecorder().recordLostEvent(DiscardReason.QUEUE_OVERFLOW, DataCategory.LogItem);
            final long lostBytes = JsonSerializationUtils.byteSizeOf(this.options.getSerializer(), this.options.getLogger(), logEvent);
            this.options.getClientReportRecorder().recordLostEvent(DiscardReason.QUEUE_OVERFLOW, DataCategory.Attachment, lostBytes);
            return;
        }
        this.pendingCount.increment();
        this.queue.offer(logEvent);
        this.maybeSchedule(false, false);
    }
    
    @Override
    public void close(final boolean isRestarting) {
        if (isRestarting) {
            this.maybeSchedule(true, true);
            this.executorService.submit(() -> this.executorService.close(this.options.getShutdownTimeoutMillis()));
        }
        else {
            this.executorService.close(this.options.getShutdownTimeoutMillis());
            while (!this.queue.isEmpty()) {
                this.flushBatch();
            }
        }
    }
    
    private void maybeSchedule(final boolean forceSchedule, final boolean immediately) {
        if (this.hasScheduled && !forceSchedule) {
            return;
        }
        try (final ISentryLifecycleToken ignored = LoggerBatchProcessor.scheduleLock.acquire()) {
            final Future<?> latestScheduledFlush = this.scheduledFlush;
            if (forceSchedule || latestScheduledFlush == null || latestScheduledFlush.isDone() || latestScheduledFlush.isCancelled()) {
                this.hasScheduled = true;
                final int flushAfterMs = immediately ? 0 : 5000;
                try {
                    this.scheduledFlush = this.executorService.schedule(new BatchRunnable(), flushAfterMs);
                }
                catch (final RejectedExecutionException e) {
                    this.hasScheduled = false;
                    this.options.getLogger().log(SentryLevel.WARNING, "Logs batch processor flush task rejected", e);
                }
            }
        }
    }
    
    @Override
    public void flush(final long timeoutMillis) {
        this.maybeSchedule(true, true);
        try {
            this.pendingCount.waitTillZero(timeoutMillis, TimeUnit.MILLISECONDS);
        }
        catch (final InterruptedException e) {
            this.options.getLogger().log(SentryLevel.ERROR, "Failed to flush log events", e);
            Thread.currentThread().interrupt();
        }
    }
    
    private void flush() {
        this.flushInternal();
        try (final ISentryLifecycleToken ignored = LoggerBatchProcessor.scheduleLock.acquire()) {
            if (!this.queue.isEmpty()) {
                this.maybeSchedule(true, false);
            }
            else {
                this.hasScheduled = false;
            }
        }
    }
    
    private void flushInternal() {
        do {
            this.flushBatch();
        } while (this.queue.size() >= 100);
    }
    
    private void flushBatch() {
        final List<SentryLogEvent> logEvents = new ArrayList<SentryLogEvent>(100);
        do {
            final SentryLogEvent logEvent = this.queue.poll();
            if (logEvent != null) {
                logEvents.add(logEvent);
            }
        } while (!this.queue.isEmpty() && logEvents.size() < 100);
        if (!logEvents.isEmpty()) {
            this.client.captureBatchedLogEvents(new SentryLogEvents(logEvents));
            for (int i = 0; i < logEvents.size(); ++i) {
                this.pendingCount.decrement();
            }
        }
    }
    
    static {
        scheduleLock = new AutoClosableReentrantLock();
    }
    
    private class BatchRunnable implements Runnable
    {
        @Override
        public void run() {
            LoggerBatchProcessor.this.flush();
        }
    }
}
