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

package io.sentry;

import java.util.concurrent.TimeUnit;
import java.util.TimerTask;
import java.util.Iterator;
import java.util.ArrayList;
import io.sentry.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import java.util.Timer;
import org.jetbrains.annotations.NotNull;
import io.sentry.util.AutoClosableReentrantLock;
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
public final class DefaultCompositePerformanceCollector implements CompositePerformanceCollector
{
    private static final long TRANSACTION_COLLECTION_INTERVAL_MILLIS = 100L;
    private static final long TRANSACTION_COLLECTION_TIMEOUT_MILLIS = 30000L;
    @NotNull
    private final AutoClosableReentrantLock timerLock;
    @Nullable
    private volatile Timer timer;
    @NotNull
    private final Map<String, CompositeData> compositeDataMap;
    @NotNull
    private final List<IPerformanceSnapshotCollector> snapshotCollectors;
    @NotNull
    private final List<IPerformanceContinuousCollector> continuousCollectors;
    private final boolean hasNoCollectors;
    @NotNull
    private final SentryOptions options;
    @NotNull
    private final AtomicBoolean isStarted;
    private long lastCollectionTimestamp;
    
    public DefaultCompositePerformanceCollector(@NotNull final SentryOptions options) {
        this.timerLock = new AutoClosableReentrantLock();
        this.timer = null;
        this.compositeDataMap = new ConcurrentHashMap<String, CompositeData>();
        this.isStarted = new AtomicBoolean(false);
        this.lastCollectionTimestamp = 0L;
        this.options = Objects.requireNonNull(options, "The options object is required.");
        this.snapshotCollectors = new ArrayList<IPerformanceSnapshotCollector>();
        this.continuousCollectors = new ArrayList<IPerformanceContinuousCollector>();
        final List<IPerformanceCollector> performanceCollectors = options.getPerformanceCollectors();
        for (final IPerformanceCollector performanceCollector : performanceCollectors) {
            if (performanceCollector instanceof IPerformanceSnapshotCollector) {
                this.snapshotCollectors.add((IPerformanceSnapshotCollector)performanceCollector);
            }
            if (performanceCollector instanceof IPerformanceContinuousCollector) {
                this.continuousCollectors.add((IPerformanceContinuousCollector)performanceCollector);
            }
        }
        this.hasNoCollectors = (this.snapshotCollectors.isEmpty() && this.continuousCollectors.isEmpty());
    }
    
    @Override
    public void start(@NotNull final ITransaction transaction) {
        if (this.hasNoCollectors) {
            this.options.getLogger().log(SentryLevel.INFO, "No collector found. Performance stats will not be captured during transactions.", new Object[0]);
            return;
        }
        for (final IPerformanceContinuousCollector collector : this.continuousCollectors) {
            collector.onSpanStarted(transaction);
        }
        final String id = transaction.getEventId().toString();
        if (!this.compositeDataMap.containsKey(id)) {
            this.compositeDataMap.put(id, new CompositeData(transaction));
        }
        this.start(id);
    }
    
    @Override
    public void start(@NotNull final String id) {
        if (this.hasNoCollectors) {
            this.options.getLogger().log(SentryLevel.INFO, "No collector found. Performance stats will not be captured during transactions.", new Object[0]);
            return;
        }
        if (!this.compositeDataMap.containsKey(id)) {
            this.compositeDataMap.put(id, new CompositeData((ITransaction)null));
        }
        if (!this.isStarted.getAndSet(true)) {
            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() {
                        for (final IPerformanceSnapshotCollector collector : DefaultCompositePerformanceCollector.this.snapshotCollectors) {
                            collector.setup();
                        }
                    }
                }, 0L);
                final List<ITransaction> timedOutTransactions = new ArrayList<ITransaction>();
                final TimerTask timerTask = new TimerTask() {
                    @Override
                    public void run() {
                        final long now = System.currentTimeMillis();
                        if (now - DefaultCompositePerformanceCollector.this.lastCollectionTimestamp <= 10L) {
                            return;
                        }
                        timedOutTransactions.clear();
                        DefaultCompositePerformanceCollector.this.lastCollectionTimestamp = now;
                        final PerformanceCollectionData tempData = new PerformanceCollectionData(DefaultCompositePerformanceCollector.this.options.getDateProvider().now().nanoTimestamp());
                        for (final IPerformanceSnapshotCollector collector : DefaultCompositePerformanceCollector.this.snapshotCollectors) {
                            collector.collect(tempData);
                        }
                        for (final CompositeData data : DefaultCompositePerformanceCollector.this.compositeDataMap.values()) {
                            if (data.addDataAndCheckTimeout(tempData) && data.transaction != null) {
                                timedOutTransactions.add(data.transaction);
                            }
                        }
                        for (final ITransaction t : timedOutTransactions) {
                            DefaultCompositePerformanceCollector.this.stop(t);
                        }
                    }
                };
                this.timer.scheduleAtFixedRate(timerTask, 100L, 100L);
            }
        }
    }
    
    @Override
    public void onSpanStarted(@NotNull final ISpan span) {
        for (final IPerformanceContinuousCollector collector : this.continuousCollectors) {
            collector.onSpanStarted(span);
        }
    }
    
    @Override
    public void onSpanFinished(@NotNull final ISpan span) {
        for (final IPerformanceContinuousCollector collector : this.continuousCollectors) {
            collector.onSpanFinished(span);
        }
    }
    
    @Nullable
    @Override
    public List<PerformanceCollectionData> stop(@NotNull final ITransaction transaction) {
        this.options.getLogger().log(SentryLevel.DEBUG, "stop collecting performance info for transactions %s (%s)", transaction.getName(), transaction.getSpanContext().getTraceId().toString());
        for (final IPerformanceContinuousCollector collector : this.continuousCollectors) {
            collector.onSpanFinished(transaction);
        }
        return this.stop(transaction.getEventId().toString());
    }
    
    @Nullable
    @Override
    public List<PerformanceCollectionData> stop(@NotNull final String id) {
        final CompositeData data = this.compositeDataMap.remove(id);
        this.options.getLogger().log(SentryLevel.DEBUG, "stop collecting performance info for " + id, new Object[0]);
        if (this.compositeDataMap.isEmpty()) {
            this.close();
        }
        return (data != null) ? data.dataList : null;
    }
    
    @Override
    public void close() {
        this.options.getLogger().log(SentryLevel.DEBUG, "stop collecting all performance info for transactions", new Object[0]);
        this.compositeDataMap.clear();
        for (final IPerformanceContinuousCollector collector : this.continuousCollectors) {
            collector.clear();
        }
        if (this.isStarted.getAndSet(false)) {
            try (final ISentryLifecycleToken ignored = this.timerLock.acquire()) {
                if (this.timer != null) {
                    this.timer.cancel();
                    this.timer = null;
                }
            }
        }
    }
    
    private class CompositeData
    {
        @NotNull
        private final List<PerformanceCollectionData> dataList;
        @Nullable
        private final ITransaction transaction;
        private final long startTimestamp;
        
        private CompositeData(final ITransaction transaction) {
            this.dataList = new ArrayList<PerformanceCollectionData>();
            this.transaction = transaction;
            this.startTimestamp = DefaultCompositePerformanceCollector.this.options.getDateProvider().now().nanoTimestamp();
        }
        
        boolean addDataAndCheckTimeout(@NotNull final PerformanceCollectionData data) {
            this.dataList.add(data);
            return this.transaction != null && DefaultCompositePerformanceCollector.this.options.getDateProvider().now().nanoTimestamp() > this.startTimestamp + TimeUnit.MILLISECONDS.toNanos(30000L);
        }
    }
}
