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

package io.sentry;

import java.util.Arrays;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.text.DecimalFormat;
import io.sentry.util.SampleRateUtils;
import io.sentry.protocol.TransactionNameSource;
import java.util.Map;
import java.net.URLDecoder;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Set;
import java.util.Collection;
import java.util.TreeSet;
import java.util.Collections;
import io.sentry.protocol.SentryId;
import java.util.ArrayList;
import io.sentry.util.StringUtils;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import io.sentry.util.AutoClosableReentrantLock;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Experimental
public final class Baggage
{
    @NotNull
    static final String CHARSET = "UTF-8";
    @NotNull
    static final Integer MAX_BAGGAGE_STRING_LENGTH;
    @NotNull
    static final Integer MAX_BAGGAGE_LIST_MEMBER_COUNT;
    @NotNull
    static final String SENTRY_BAGGAGE_PREFIX = "sentry-";
    private static final DecimalFormatterThreadLocal decimalFormatter;
    @NotNull
    private final ConcurrentHashMap<String, String> keyValues;
    @NotNull
    private final AutoClosableReentrantLock keyValuesLock;
    @Nullable
    private Double sampleRate;
    @Nullable
    private Double sampleRand;
    @Nullable
    private final String thirdPartyHeader;
    private boolean mutable;
    private final boolean shouldFreeze;
    @NotNull
    final ILogger logger;
    
    @NotNull
    public static Baggage fromHeader(@Nullable final String headerValue) {
        return fromHeader(headerValue, false, ScopesAdapter.getInstance().getOptions().getLogger());
    }
    
    @NotNull
    public static Baggage fromHeader(@Nullable final List<String> headerValues) {
        return fromHeader(headerValues, false, ScopesAdapter.getInstance().getOptions().getLogger());
    }
    
    @ApiStatus.Internal
    @NotNull
    public static Baggage fromHeader(final String headerValue, @NotNull final ILogger logger) {
        return fromHeader(headerValue, false, logger);
    }
    
    @ApiStatus.Internal
    @NotNull
    public static Baggage fromHeader(@Nullable final List<String> headerValues, @NotNull final ILogger logger) {
        return fromHeader(headerValues, false, logger);
    }
    
    @ApiStatus.Internal
    @NotNull
    public static Baggage fromHeader(@Nullable final List<String> headerValues, final boolean includeThirdPartyValues, @NotNull final ILogger logger) {
        if (headerValues != null) {
            return fromHeader(StringUtils.join(",", headerValues), includeThirdPartyValues, logger);
        }
        return fromHeader((String)null, includeThirdPartyValues, logger);
    }
    
    @ApiStatus.Internal
    @NotNull
    public static Baggage fromHeader(@Nullable final String headerValue, final boolean includeThirdPartyValues, @NotNull final ILogger logger) {
        final ConcurrentHashMap<String, String> keyValues = new ConcurrentHashMap<String, String>();
        final List<String> thirdPartyKeyValueStrings = new ArrayList<String>();
        boolean shouldFreeze = false;
        Double sampleRate = null;
        Double sampleRand = null;
        if (headerValue != null) {
            try {
                final String[] split;
                final String[] keyValueStrings = split = headerValue.split(",", -1);
                for (final String keyValueString : split) {
                    if (keyValueString.trim().startsWith("sentry-")) {
                        try {
                            final int separatorIndex = keyValueString.indexOf("=");
                            final String key = keyValueString.substring(0, separatorIndex).trim();
                            final String keyDecoded = decode(key);
                            final String value = keyValueString.substring(separatorIndex + 1).trim();
                            final String valueDecoded = decode(value);
                            if ("sentry-sample_rate".equals(keyDecoded)) {
                                sampleRate = toDouble(valueDecoded);
                            }
                            else if ("sentry-sample_rand".equals(keyDecoded)) {
                                sampleRand = toDouble(valueDecoded);
                            }
                            else {
                                keyValues.put(keyDecoded, valueDecoded);
                            }
                            if (!"sentry-sample_rand".equalsIgnoreCase(key)) {
                                shouldFreeze = true;
                            }
                        }
                        catch (final Throwable e) {
                            logger.log(SentryLevel.ERROR, e, "Unable to decode baggage key value pair %s", keyValueString);
                        }
                    }
                    else if (includeThirdPartyValues) {
                        thirdPartyKeyValueStrings.add(keyValueString.trim());
                    }
                }
            }
            catch (final Throwable e2) {
                logger.log(SentryLevel.ERROR, e2, "Unable to decode baggage header %s", headerValue);
            }
        }
        final String thirdPartyHeader = thirdPartyKeyValueStrings.isEmpty() ? null : StringUtils.join(",", thirdPartyKeyValueStrings);
        return new Baggage(keyValues, sampleRate, sampleRand, thirdPartyHeader, true, shouldFreeze, logger);
    }
    
    @ApiStatus.Internal
    @NotNull
    public static Baggage fromEvent(@NotNull final SentryBaseEvent event, @Nullable final String transaction, @NotNull final SentryOptions options) {
        final Baggage baggage = new Baggage(options.getLogger());
        final SpanContext trace = event.getContexts().getTrace();
        baggage.setTraceId((trace != null) ? trace.getTraceId().toString() : null);
        baggage.setPublicKey(options.retrieveParsedDsn().getPublicKey());
        baggage.setRelease(event.getRelease());
        baggage.setEnvironment(event.getEnvironment());
        baggage.setTransaction(transaction);
        baggage.setSampleRate(null);
        baggage.setSampled(null);
        baggage.setSampleRand(null);
        final Object replayId = event.getContexts().get("replay_id");
        if (replayId != null && !replayId.toString().equals(SentryId.EMPTY_ID.toString())) {
            baggage.setReplayId(replayId.toString());
            event.getContexts().remove("replay_id");
        }
        baggage.freeze();
        return baggage;
    }
    
    @ApiStatus.Internal
    public Baggage(@NotNull final ILogger logger) {
        this(new ConcurrentHashMap<String, String>(), null, null, null, true, false, logger);
    }
    
    @ApiStatus.Internal
    public Baggage(@NotNull final Baggage baggage) {
        this(baggage.keyValues, baggage.sampleRate, baggage.sampleRand, baggage.thirdPartyHeader, baggage.mutable, baggage.shouldFreeze, baggage.logger);
    }
    
    @ApiStatus.Internal
    public Baggage(@NotNull final ConcurrentHashMap<String, String> keyValues, @Nullable final Double sampleRate, @Nullable final Double sampleRand, @Nullable final String thirdPartyHeader, final boolean isMutable, final boolean shouldFreeze, @NotNull final ILogger logger) {
        this.keyValuesLock = new AutoClosableReentrantLock();
        this.keyValues = keyValues;
        this.sampleRate = sampleRate;
        this.sampleRand = sampleRand;
        this.logger = logger;
        this.thirdPartyHeader = thirdPartyHeader;
        this.mutable = isMutable;
        this.shouldFreeze = shouldFreeze;
    }
    
    @ApiStatus.Internal
    public void freeze() {
        this.mutable = false;
    }
    
    @ApiStatus.Internal
    public boolean isMutable() {
        return this.mutable;
    }
    
    @ApiStatus.Internal
    public boolean isShouldFreeze() {
        return this.shouldFreeze;
    }
    
    @Nullable
    public String getThirdPartyHeader() {
        return this.thirdPartyHeader;
    }
    
    @NotNull
    public String toHeaderString(@Nullable final String thirdPartyBaggageHeaderString) {
        final StringBuilder sb = new StringBuilder();
        String separator = "";
        int listMemberCount = 0;
        if (thirdPartyBaggageHeaderString != null && !thirdPartyBaggageHeaderString.isEmpty()) {
            sb.append(thirdPartyBaggageHeaderString);
            listMemberCount = StringUtils.countOf(thirdPartyBaggageHeaderString, ',') + 1;
            separator = ",";
        }
        Set<String> keys;
        try (final ISentryLifecycleToken ignored = this.keyValuesLock.acquire()) {
            keys = new TreeSet<String>(Collections.list(this.keyValues.keys()));
        }
        keys.add("sentry-sample_rate");
        keys.add("sentry-sample_rand");
        for (final String key : keys) {
            String value;
            if ("sentry-sample_rate".equals(key)) {
                value = sampleRateToString(this.sampleRate);
            }
            else if ("sentry-sample_rand".equals(key)) {
                value = sampleRateToString(this.sampleRand);
            }
            else {
                value = this.keyValues.get(key);
            }
            if (value != null) {
                if (listMemberCount >= Baggage.MAX_BAGGAGE_LIST_MEMBER_COUNT) {
                    this.logger.log(SentryLevel.ERROR, "Not adding baggage value %s as the total number of list members would exceed the maximum of %s.", key, Baggage.MAX_BAGGAGE_LIST_MEMBER_COUNT);
                }
                else {
                    try {
                        final String encodedKey = this.encode(key);
                        final String encodedValue = this.encode(value);
                        final String encodedKeyValue = separator + encodedKey + "=" + encodedValue;
                        final int valueLength = encodedKeyValue.length();
                        final int totalLengthIfValueAdded = sb.length() + valueLength;
                        if (totalLengthIfValueAdded > Baggage.MAX_BAGGAGE_STRING_LENGTH) {
                            this.logger.log(SentryLevel.ERROR, "Not adding baggage value %s as the total header value length would exceed the maximum of %s.", key, Baggage.MAX_BAGGAGE_STRING_LENGTH);
                        }
                        else {
                            ++listMemberCount;
                            sb.append(encodedKeyValue);
                            separator = ",";
                        }
                    }
                    catch (final Throwable e) {
                        this.logger.log(SentryLevel.ERROR, e, "Unable to encode baggage key value pair (key=%s,value=%s).", key, value);
                    }
                }
            }
        }
        return sb.toString();
    }
    
    private String encode(@NotNull final String value) throws UnsupportedEncodingException {
        return URLEncoder.encode(value, "UTF-8").replaceAll("\\+", "%20");
    }
    
    private static String decode(@NotNull final String value) throws UnsupportedEncodingException {
        return URLDecoder.decode(value, "UTF-8");
    }
    
    @ApiStatus.Internal
    @Nullable
    public String get(@Nullable final String key) {
        if (key == null) {
            return null;
        }
        return this.keyValues.get(key);
    }
    
    @ApiStatus.Internal
    @Nullable
    public String getTraceId() {
        return this.get("sentry-trace_id");
    }
    
    @ApiStatus.Internal
    public void setTraceId(@Nullable final String traceId) {
        this.set("sentry-trace_id", traceId);
    }
    
    @ApiStatus.Internal
    @Nullable
    public String getPublicKey() {
        return this.get("sentry-public_key");
    }
    
    @ApiStatus.Internal
    public void setPublicKey(@Nullable final String publicKey) {
        this.set("sentry-public_key", publicKey);
    }
    
    @ApiStatus.Internal
    @Nullable
    public String getEnvironment() {
        return this.get("sentry-environment");
    }
    
    @ApiStatus.Internal
    public void setEnvironment(@Nullable final String environment) {
        this.set("sentry-environment", environment);
    }
    
    @ApiStatus.Internal
    @Nullable
    public String getRelease() {
        return this.get("sentry-release");
    }
    
    @ApiStatus.Internal
    public void setRelease(@Nullable final String release) {
        this.set("sentry-release", release);
    }
    
    @ApiStatus.Internal
    @Nullable
    public String getUserId() {
        return this.get("sentry-user_id");
    }
    
    @ApiStatus.Internal
    public void setUserId(@Nullable final String userId) {
        this.set("sentry-user_id", userId);
    }
    
    @ApiStatus.Internal
    @Nullable
    public String getTransaction() {
        return this.get("sentry-transaction");
    }
    
    @ApiStatus.Internal
    public void setTransaction(@Nullable final String transaction) {
        this.set("sentry-transaction", transaction);
    }
    
    @ApiStatus.Internal
    @Nullable
    public Double getSampleRate() {
        return this.sampleRate;
    }
    
    @ApiStatus.Internal
    @Nullable
    public String getSampled() {
        return this.get("sentry-sampled");
    }
    
    @ApiStatus.Internal
    public void setSampleRate(@Nullable final Double sampleRate) {
        if (this.isMutable()) {
            this.sampleRate = sampleRate;
        }
    }
    
    @ApiStatus.Internal
    public void forceSetSampleRate(@Nullable final Double sampleRate) {
        this.sampleRate = sampleRate;
    }
    
    @ApiStatus.Internal
    @Nullable
    public Double getSampleRand() {
        return this.sampleRand;
    }
    
    @ApiStatus.Internal
    public void setSampleRand(@Nullable final Double sampleRand) {
        if (this.isMutable()) {
            this.sampleRand = sampleRand;
        }
    }
    
    @ApiStatus.Internal
    public void setSampled(@Nullable final String sampled) {
        this.set("sentry-sampled", sampled);
    }
    
    @ApiStatus.Internal
    @Nullable
    public String getReplayId() {
        return this.get("sentry-replay_id");
    }
    
    @ApiStatus.Internal
    public void setReplayId(@Nullable final String replayId) {
        this.set("sentry-replay_id", replayId);
    }
    
    @ApiStatus.Internal
    public void set(@NotNull final String key, @Nullable final String value) {
        if (this.mutable) {
            if (value == null) {
                this.keyValues.remove(key);
            }
            else {
                this.keyValues.put(key, value);
            }
        }
    }
    
    @ApiStatus.Internal
    @NotNull
    public Map<String, Object> getUnknown() {
        final Map<String, Object> unknown = new ConcurrentHashMap<String, Object>();
        try (final ISentryLifecycleToken ignored = this.keyValuesLock.acquire()) {
            for (final Map.Entry<String, String> keyValue : this.keyValues.entrySet()) {
                final String key = keyValue.getKey();
                final String value = keyValue.getValue();
                if (!DSCKeys.ALL.contains(key) && value != null) {
                    final String unknownKey = key.replaceFirst("sentry-", "");
                    unknown.put(unknownKey, value);
                }
            }
        }
        return unknown;
    }
    
    @ApiStatus.Internal
    public void setValuesFromTransaction(@NotNull final SentryId traceId, @Nullable final SentryId replayId, @NotNull final SentryOptions sentryOptions, @Nullable final TracesSamplingDecision samplingDecision, @Nullable final String transactionName, @Nullable final TransactionNameSource transactionNameSource) {
        this.setTraceId(traceId.toString());
        this.setPublicKey(sentryOptions.retrieveParsedDsn().getPublicKey());
        this.setRelease(sentryOptions.getRelease());
        this.setEnvironment(sentryOptions.getEnvironment());
        this.setTransaction(isHighQualityTransactionName(transactionNameSource) ? transactionName : null);
        if (replayId != null && !SentryId.EMPTY_ID.equals(replayId)) {
            this.setReplayId(replayId.toString());
        }
        this.setSampleRate(sampleRate(samplingDecision));
        this.setSampled(StringUtils.toString(sampled(samplingDecision)));
        this.setSampleRand(sampleRand(samplingDecision));
    }
    
    @ApiStatus.Internal
    public void setValuesFromSamplingDecision(@Nullable final TracesSamplingDecision samplingDecision) {
        if (samplingDecision == null) {
            return;
        }
        this.setSampled(StringUtils.toString(sampled(samplingDecision)));
        if (samplingDecision.getSampleRand() != null) {
            this.setSampleRand(sampleRand(samplingDecision));
        }
        if (samplingDecision.getSampleRate() != null) {
            this.forceSetSampleRate(sampleRate(samplingDecision));
        }
    }
    
    @ApiStatus.Internal
    public void setValuesFromScope(@NotNull final IScope scope, @NotNull final SentryOptions options) {
        final PropagationContext propagationContext = scope.getPropagationContext();
        final SentryId replayId = scope.getReplayId();
        this.setTraceId(propagationContext.getTraceId().toString());
        this.setPublicKey(options.retrieveParsedDsn().getPublicKey());
        this.setRelease(options.getRelease());
        this.setEnvironment(options.getEnvironment());
        if (!SentryId.EMPTY_ID.equals(replayId)) {
            this.setReplayId(replayId.toString());
        }
        this.setTransaction(null);
        this.setSampleRate(null);
        this.setSampled(null);
    }
    
    @Nullable
    private static Double sampleRate(@Nullable final TracesSamplingDecision samplingDecision) {
        if (samplingDecision == null) {
            return null;
        }
        return samplingDecision.getSampleRate();
    }
    
    @Nullable
    private static Double sampleRand(@Nullable final TracesSamplingDecision samplingDecision) {
        if (samplingDecision == null) {
            return null;
        }
        return samplingDecision.getSampleRand();
    }
    
    @Nullable
    private static String sampleRateToString(@Nullable final Double sampleRateAsDouble) {
        if (!SampleRateUtils.isValidTracesSampleRate(sampleRateAsDouble, false)) {
            return null;
        }
        return Baggage.decimalFormatter.get().format(sampleRateAsDouble);
    }
    
    @Nullable
    private static Boolean sampled(@Nullable final TracesSamplingDecision samplingDecision) {
        if (samplingDecision == null) {
            return null;
        }
        return samplingDecision.getSampled();
    }
    
    private static boolean isHighQualityTransactionName(@Nullable final TransactionNameSource transactionNameSource) {
        return transactionNameSource != null && !TransactionNameSource.URL.equals(transactionNameSource);
    }
    
    @Nullable
    private static Double toDouble(@Nullable final String stringValue) {
        if (stringValue != null) {
            try {
                final double doubleValue = Double.parseDouble(stringValue);
                if (SampleRateUtils.isValidTracesSampleRate(doubleValue, false)) {
                    return doubleValue;
                }
            }
            catch (final NumberFormatException e) {
                return null;
            }
        }
        return null;
    }
    
    @ApiStatus.Internal
    @Nullable
    public TraceContext toTraceContext() {
        final String traceIdString = this.getTraceId();
        final String replayIdString = this.getReplayId();
        final String publicKey = this.getPublicKey();
        if (traceIdString != null && publicKey != null) {
            final TraceContext traceContext = new TraceContext(new SentryId(traceIdString), publicKey, this.getRelease(), this.getEnvironment(), this.getUserId(), this.getTransaction(), sampleRateToString(this.getSampleRate()), this.getSampled(), (replayIdString == null) ? null : new SentryId(replayIdString), sampleRateToString(this.getSampleRand()));
            traceContext.setUnknown(this.getUnknown());
            return traceContext;
        }
        return null;
    }
    
    static {
        MAX_BAGGAGE_STRING_LENGTH = 8192;
        MAX_BAGGAGE_LIST_MEMBER_COUNT = 64;
        decimalFormatter = new DecimalFormatterThreadLocal();
    }
    
    private static class DecimalFormatterThreadLocal extends ThreadLocal<DecimalFormat>
    {
        @Override
        protected DecimalFormat initialValue() {
            return new DecimalFormat("#.################", DecimalFormatSymbols.getInstance(Locale.ROOT));
        }
    }
    
    @ApiStatus.Internal
    public static final class DSCKeys
    {
        public static final String TRACE_ID = "sentry-trace_id";
        public static final String PUBLIC_KEY = "sentry-public_key";
        public static final String RELEASE = "sentry-release";
        public static final String USER_ID = "sentry-user_id";
        public static final String ENVIRONMENT = "sentry-environment";
        public static final String TRANSACTION = "sentry-transaction";
        public static final String SAMPLE_RATE = "sentry-sample_rate";
        public static final String SAMPLE_RAND = "sentry-sample_rand";
        public static final String SAMPLED = "sentry-sampled";
        public static final String REPLAY_ID = "sentry-replay_id";
        public static final List<String> ALL;
        
        static {
            ALL = Arrays.asList("sentry-trace_id", "sentry-public_key", "sentry-release", "sentry-user_id", "sentry-environment", "sentry-transaction", "sentry-sample_rate", "sentry-sample_rand", "sentry-sampled", "sentry-replay_id");
        }
    }
}
