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

package com.google.crypto.tink.jwt;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gson.JsonPrimitive;
import java.util.Iterator;
import java.util.HashSet;
import java.util.Set;
import java.time.Instant;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import com.google.gson.JsonParseException;
import com.google.gson.JsonNull;
import com.google.crypto.tink.jwt.internal.JwtNames;
import java.util.List;
import com.google.crypto.tink.jwt.internal.JsonUtil;
import java.util.Optional;
import com.google.gson.JsonObject;
import com.google.errorprone.annotations.Immutable;

@Immutable
public final class RawJwt
{
    private static final long MAX_TIMESTAMP_VALUE = 253402300799L;
    private final JsonObject payload;
    private final Optional<String> typeHeader;
    
    private RawJwt(final Builder builder) {
        if (!builder.payload.has("exp") && !builder.withoutExpiration) {
            throw new IllegalArgumentException("neither setExpiration() nor withoutExpiration() was called");
        }
        if (builder.payload.has("exp") && builder.withoutExpiration) {
            throw new IllegalArgumentException("setExpiration() and withoutExpiration() must not be called together");
        }
        this.typeHeader = builder.typeHeader;
        this.payload = builder.payload.deepCopy();
    }
    
    private RawJwt(final Optional<String> typeHeader, final String jsonPayload) throws JwtInvalidException {
        this.typeHeader = typeHeader;
        this.payload = JsonUtil.parseJson(jsonPayload);
        this.validateStringClaim("iss");
        this.validateStringClaim("sub");
        this.validateStringClaim("jti");
        this.validateTimestampClaim("exp");
        this.validateTimestampClaim("nbf");
        this.validateTimestampClaim("iat");
        this.validateAudienceClaim();
    }
    
    private void validateStringClaim(final String name) throws JwtInvalidException {
        if (!this.payload.has(name)) {
            return;
        }
        if (!this.payload.get(name).isJsonPrimitive() || !this.payload.get(name).getAsJsonPrimitive().isString()) {
            throw new JwtInvalidException("invalid JWT payload: claim " + name + " is not a string.");
        }
    }
    
    private void validateTimestampClaim(final String name) throws JwtInvalidException {
        if (!this.payload.has(name)) {
            return;
        }
        if (!this.payload.get(name).isJsonPrimitive() || !this.payload.get(name).getAsJsonPrimitive().isNumber()) {
            throw new JwtInvalidException("invalid JWT payload: claim " + name + " is not a number.");
        }
        final double timestamp = this.payload.get(name).getAsJsonPrimitive().getAsDouble();
        if (timestamp > 2.53402300799E11 || timestamp < 0.0) {
            throw new JwtInvalidException("invalid JWT payload: claim " + name + " has an invalid timestamp");
        }
    }
    
    private void validateAudienceClaim() throws JwtInvalidException {
        if (!this.payload.has("aud")) {
            return;
        }
        if (this.payload.get("aud").isJsonPrimitive() && this.payload.get("aud").getAsJsonPrimitive().isString()) {
            return;
        }
        final List<String> audiences = this.getAudiences();
        if (audiences.size() < 1) {
            throw new JwtInvalidException("invalid JWT payload: claim aud is present but empty.");
        }
    }
    
    static RawJwt fromJsonPayload(final Optional<String> typeHeader, final String jsonPayload) throws JwtInvalidException {
        return new RawJwt(typeHeader, jsonPayload);
    }
    
    public static Builder newBuilder() {
        return new Builder();
    }
    
    public String getJsonPayload() {
        return this.payload.toString();
    }
    
    boolean hasBooleanClaim(final String name) {
        JwtNames.validate(name);
        return this.payload.has(name) && this.payload.get(name).isJsonPrimitive() && this.payload.get(name).getAsJsonPrimitive().isBoolean();
    }
    
    Boolean getBooleanClaim(final String name) throws JwtInvalidException {
        JwtNames.validate(name);
        if (!this.payload.has(name)) {
            throw new JwtInvalidException("claim " + name + " does not exist");
        }
        if (!this.payload.get(name).isJsonPrimitive() || !this.payload.get(name).getAsJsonPrimitive().isBoolean()) {
            throw new JwtInvalidException("claim " + name + " is not a boolean");
        }
        return this.payload.get(name).getAsBoolean();
    }
    
    boolean hasNumberClaim(final String name) {
        JwtNames.validate(name);
        return this.payload.has(name) && this.payload.get(name).isJsonPrimitive() && this.payload.get(name).getAsJsonPrimitive().isNumber();
    }
    
    Double getNumberClaim(final String name) throws JwtInvalidException {
        JwtNames.validate(name);
        if (!this.payload.has(name)) {
            throw new JwtInvalidException("claim " + name + " does not exist");
        }
        if (!this.payload.get(name).isJsonPrimitive() || !this.payload.get(name).getAsJsonPrimitive().isNumber()) {
            throw new JwtInvalidException("claim " + name + " is not a number");
        }
        return this.payload.get(name).getAsDouble();
    }
    
    boolean hasStringClaim(final String name) {
        JwtNames.validate(name);
        return this.payload.has(name) && this.payload.get(name).isJsonPrimitive() && this.payload.get(name).getAsJsonPrimitive().isString();
    }
    
    String getStringClaim(final String name) throws JwtInvalidException {
        JwtNames.validate(name);
        return this.getStringClaimInternal(name);
    }
    
    private String getStringClaimInternal(final String name) throws JwtInvalidException {
        if (!this.payload.has(name)) {
            throw new JwtInvalidException("claim " + name + " does not exist");
        }
        if (!this.payload.get(name).isJsonPrimitive() || !this.payload.get(name).getAsJsonPrimitive().isString()) {
            throw new JwtInvalidException("claim " + name + " is not a string");
        }
        return this.payload.get(name).getAsString();
    }
    
    boolean isNullClaim(final String name) {
        JwtNames.validate(name);
        try {
            return JsonNull.INSTANCE.equals(this.payload.get(name));
        }
        catch (final JsonParseException ex) {
            return false;
        }
    }
    
    boolean hasJsonObjectClaim(final String name) {
        JwtNames.validate(name);
        return this.payload.has(name) && this.payload.get(name).isJsonObject();
    }
    
    String getJsonObjectClaim(final String name) throws JwtInvalidException {
        JwtNames.validate(name);
        if (!this.payload.has(name)) {
            throw new JwtInvalidException("claim " + name + " does not exist");
        }
        if (!this.payload.get(name).isJsonObject()) {
            throw new JwtInvalidException("claim " + name + " is not a JSON object");
        }
        return this.payload.get(name).getAsJsonObject().toString();
    }
    
    boolean hasJsonArrayClaim(final String name) {
        JwtNames.validate(name);
        return this.payload.has(name) && this.payload.get(name).isJsonArray();
    }
    
    String getJsonArrayClaim(final String name) throws JwtInvalidException {
        JwtNames.validate(name);
        if (!this.payload.has(name)) {
            throw new JwtInvalidException("claim " + name + " does not exist");
        }
        if (!this.payload.get(name).isJsonArray()) {
            throw new JwtInvalidException("claim " + name + " is not a JSON array");
        }
        return this.payload.get(name).getAsJsonArray().toString();
    }
    
    boolean hasTypeHeader() {
        return this.typeHeader.isPresent();
    }
    
    String getTypeHeader() throws JwtInvalidException {
        if (!this.typeHeader.isPresent()) {
            throw new JwtInvalidException("type header is not set");
        }
        return this.typeHeader.get();
    }
    
    boolean hasIssuer() {
        return this.payload.has("iss");
    }
    
    String getIssuer() throws JwtInvalidException {
        return this.getStringClaimInternal("iss");
    }
    
    boolean hasSubject() {
        return this.payload.has("sub");
    }
    
    String getSubject() throws JwtInvalidException {
        return this.getStringClaimInternal("sub");
    }
    
    boolean hasJwtId() {
        return this.payload.has("jti");
    }
    
    String getJwtId() throws JwtInvalidException {
        return this.getStringClaimInternal("jti");
    }
    
    boolean hasAudiences() {
        return this.payload.has("aud");
    }
    
    List<String> getAudiences() throws JwtInvalidException {
        if (!this.hasAudiences()) {
            throw new JwtInvalidException("claim aud does not exist");
        }
        final JsonElement aud = this.payload.get("aud");
        if (aud.isJsonPrimitive()) {
            if (!aud.getAsJsonPrimitive().isString()) {
                throw new JwtInvalidException(String.format("invalid audience: got %s; want a string", aud));
            }
            return Collections.unmodifiableList((List<? extends String>)Arrays.asList(aud.getAsString()));
        }
        else {
            if (!aud.isJsonArray()) {
                throw new JwtInvalidException("claim aud is not a string or a JSON array");
            }
            final JsonArray audiences = aud.getAsJsonArray();
            final List<String> result = new ArrayList<String>(audiences.size());
            for (int i = 0; i < audiences.size(); ++i) {
                if (!audiences.get(i).isJsonPrimitive() || !audiences.get(i).getAsJsonPrimitive().isString()) {
                    throw new JwtInvalidException(String.format("invalid audience: got %s; want a string", audiences.get(i)));
                }
                final String audience = audiences.get(i).getAsString();
                result.add(audience);
            }
            return Collections.unmodifiableList((List<? extends String>)result);
        }
    }
    
    private Instant getInstant(final String name) throws JwtInvalidException {
        if (!this.payload.has(name)) {
            throw new JwtInvalidException("claim " + name + " does not exist");
        }
        if (!this.payload.get(name).isJsonPrimitive() || !this.payload.get(name).getAsJsonPrimitive().isNumber()) {
            throw new JwtInvalidException("claim " + name + " is not a timestamp");
        }
        try {
            final double millis = this.payload.get(name).getAsJsonPrimitive().getAsDouble() * 1000.0;
            return Instant.ofEpochMilli((long)millis);
        }
        catch (final NumberFormatException ex) {
            throw new JwtInvalidException("claim " + name + " is not a timestamp: " + ex);
        }
    }
    
    boolean hasExpiration() {
        return this.payload.has("exp");
    }
    
    Instant getExpiration() throws JwtInvalidException {
        return this.getInstant("exp");
    }
    
    boolean hasNotBefore() {
        return this.payload.has("nbf");
    }
    
    Instant getNotBefore() throws JwtInvalidException {
        return this.getInstant("nbf");
    }
    
    boolean hasIssuedAt() {
        return this.payload.has("iat");
    }
    
    Instant getIssuedAt() throws JwtInvalidException {
        return this.getInstant("iat");
    }
    
    Set<String> customClaimNames() {
        final HashSet<String> names = new HashSet<String>();
        for (final String name : this.payload.keySet()) {
            if (!JwtNames.isRegisteredName(name)) {
                names.add(name);
            }
        }
        return Collections.unmodifiableSet((Set<? extends String>)names);
    }
    
    @Override
    public String toString() {
        final JsonObject header = new JsonObject();
        if (this.typeHeader.isPresent()) {
            header.add("typ", new JsonPrimitive(this.typeHeader.get()));
        }
        return header + "." + this.payload;
    }
    
    public static final class Builder
    {
        private Optional<String> typeHeader;
        private boolean withoutExpiration;
        private final JsonObject payload;
        
        private Builder() {
            this.typeHeader = Optional.empty();
            this.withoutExpiration = false;
            this.payload = new JsonObject();
        }
        
        @CanIgnoreReturnValue
        public Builder setTypeHeader(final String value) {
            this.typeHeader = Optional.of(value);
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder setIssuer(final String value) {
            if (!JsonUtil.isValidString(value)) {
                throw new IllegalArgumentException();
            }
            this.payload.add("iss", new JsonPrimitive(value));
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder setSubject(final String value) {
            if (!JsonUtil.isValidString(value)) {
                throw new IllegalArgumentException();
            }
            this.payload.add("sub", new JsonPrimitive(value));
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder setAudience(final String value) {
            if (this.payload.has("aud") && this.payload.get("aud").isJsonArray()) {
                throw new IllegalArgumentException("setAudience can't be used together with setAudiences or addAudience");
            }
            if (!JsonUtil.isValidString(value)) {
                throw new IllegalArgumentException("invalid string");
            }
            this.payload.add("aud", new JsonPrimitive(value));
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder setAudiences(final List<String> values) {
            if (this.payload.has("aud") && !this.payload.get("aud").isJsonArray()) {
                throw new IllegalArgumentException("setAudiences can't be used together with setAudience");
            }
            if (values.isEmpty()) {
                throw new IllegalArgumentException("audiences must not be empty");
            }
            final JsonArray audiences = new JsonArray();
            for (final String value : values) {
                if (!JsonUtil.isValidString(value)) {
                    throw new IllegalArgumentException("invalid string");
                }
                audiences.add(value);
            }
            this.payload.add("aud", audiences);
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder addAudience(final String value) {
            if (!JsonUtil.isValidString(value)) {
                throw new IllegalArgumentException("invalid string");
            }
            JsonArray audiences;
            if (this.payload.has("aud")) {
                final JsonElement aud = this.payload.get("aud");
                if (!aud.isJsonArray()) {
                    throw new IllegalArgumentException("addAudience can't be used together with setAudience");
                }
                audiences = aud.getAsJsonArray();
            }
            else {
                audiences = new JsonArray();
            }
            audiences.add(value);
            this.payload.add("aud", audiences);
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder setJwtId(final String value) {
            if (!JsonUtil.isValidString(value)) {
                throw new IllegalArgumentException();
            }
            this.payload.add("jti", new JsonPrimitive(value));
            return this;
        }
        
        private void setTimestampClaim(final String name, final Instant value) {
            final long timestamp = value.getEpochSecond();
            if (timestamp > 253402300799L || timestamp < 0L) {
                throw new IllegalArgumentException("timestamp of claim " + name + " is out of range");
            }
            this.payload.add(name, new JsonPrimitive(timestamp));
        }
        
        @CanIgnoreReturnValue
        public Builder setExpiration(final Instant value) {
            this.setTimestampClaim("exp", value);
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder withoutExpiration() {
            this.withoutExpiration = true;
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder setNotBefore(final Instant value) {
            this.setTimestampClaim("nbf", value);
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder setIssuedAt(final Instant value) {
            this.setTimestampClaim("iat", value);
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder addBooleanClaim(final String name, final boolean value) {
            JwtNames.validate(name);
            this.payload.add(name, new JsonPrimitive(value));
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder addNumberClaim(final String name, final long value) {
            JwtNames.validate(name);
            this.payload.add(name, new JsonPrimitive(value));
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder addNumberClaim(final String name, final double value) {
            JwtNames.validate(name);
            this.payload.add(name, new JsonPrimitive(value));
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder addStringClaim(final String name, final String value) {
            if (!JsonUtil.isValidString(value)) {
                throw new IllegalArgumentException();
            }
            JwtNames.validate(name);
            this.payload.add(name, new JsonPrimitive(value));
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder addNullClaim(final String name) {
            JwtNames.validate(name);
            this.payload.add(name, JsonNull.INSTANCE);
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder addJsonObjectClaim(final String name, final String encodedJsonObject) throws JwtInvalidException {
            JwtNames.validate(name);
            this.payload.add(name, JsonUtil.parseJson(encodedJsonObject));
            return this;
        }
        
        @CanIgnoreReturnValue
        public Builder addJsonArrayClaim(final String name, final String encodedJsonArray) throws JwtInvalidException {
            JwtNames.validate(name);
            this.payload.add(name, JsonUtil.parseJsonArray(encodedJsonArray));
            return this;
        }
        
        public RawJwt build() {
            return new RawJwt(this, null);
        }
    }
}
