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

package io.netty.handler.codec.http;

import io.netty.util.internal.StringUtil;
import io.netty.util.internal.PlatformDependent;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Collections;
import io.netty.util.internal.ObjectUtil;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.nio.charset.Charset;

public class QueryStringDecoder
{
    private static final int DEFAULT_MAX_PARAMS = 1024;
    private final Charset charset;
    private final String uri;
    private final int maxParams;
    private final boolean semicolonIsNormalChar;
    private final boolean htmlQueryDecoding;
    private int pathEndIdx;
    private String path;
    private Map<String, List<String>> params;
    
    public QueryStringDecoder(final String uri) {
        this(builder(), uri);
    }
    
    public QueryStringDecoder(final String uri, final boolean hasPath) {
        this(builder().hasPath(hasPath), uri);
    }
    
    public QueryStringDecoder(final String uri, final Charset charset) {
        this(builder().charset(charset), uri);
    }
    
    public QueryStringDecoder(final String uri, final Charset charset, final boolean hasPath) {
        this(builder().hasPath(hasPath).charset(charset), uri);
    }
    
    public QueryStringDecoder(final String uri, final Charset charset, final boolean hasPath, final int maxParams) {
        this(builder().hasPath(hasPath).charset(charset).maxParams(maxParams), uri);
    }
    
    public QueryStringDecoder(final String uri, final Charset charset, final boolean hasPath, final int maxParams, final boolean semicolonIsNormalChar) {
        this(builder().hasPath(hasPath).charset(charset).maxParams(maxParams).semicolonIsNormalChar(semicolonIsNormalChar), uri);
    }
    
    public QueryStringDecoder(final URI uri) {
        this(builder(), uri);
    }
    
    public QueryStringDecoder(final URI uri, final Charset charset) {
        this(builder().charset(charset), uri);
    }
    
    public QueryStringDecoder(final URI uri, final Charset charset, final int maxParams) {
        this(builder().charset(charset).maxParams(maxParams), uri);
    }
    
    public QueryStringDecoder(final URI uri, final Charset charset, final int maxParams, final boolean semicolonIsNormalChar) {
        this(builder().charset(charset).maxParams(maxParams).semicolonIsNormalChar(semicolonIsNormalChar), uri);
    }
    
    private QueryStringDecoder(final Builder builder, final String uri) {
        this.uri = ObjectUtil.checkNotNull(uri, "uri");
        this.charset = ObjectUtil.checkNotNull(builder.charset, "charset");
        this.maxParams = ObjectUtil.checkPositive(builder.maxParams, "maxParams");
        this.semicolonIsNormalChar = builder.semicolonIsNormalChar;
        this.htmlQueryDecoding = builder.htmlQueryDecoding;
        this.pathEndIdx = (builder.hasPath ? -1 : 0);
    }
    
    private QueryStringDecoder(final Builder builder, final URI uri) {
        String rawPath = uri.getRawPath();
        if (rawPath == null) {
            rawPath = "";
        }
        final String rawQuery = uri.getRawQuery();
        this.uri = ((rawQuery == null) ? rawPath : (rawPath + '?' + rawQuery));
        this.charset = ObjectUtil.checkNotNull(builder.charset, "charset");
        this.maxParams = ObjectUtil.checkPositive(builder.maxParams, "maxParams");
        this.semicolonIsNormalChar = builder.semicolonIsNormalChar;
        this.htmlQueryDecoding = builder.htmlQueryDecoding;
        this.pathEndIdx = rawPath.length();
    }
    
    @Override
    public String toString() {
        return this.uri();
    }
    
    public String uri() {
        return this.uri;
    }
    
    public String path() {
        if (this.path == null) {
            this.path = decodeComponent(this.uri, 0, this.pathEndIdx(), this.charset, false);
        }
        return this.path;
    }
    
    public Map<String, List<String>> parameters() {
        if (this.params == null) {
            this.params = this.decodeParams(this.uri, this.pathEndIdx(), this.charset, this.maxParams);
        }
        return this.params;
    }
    
    public String rawPath() {
        return this.uri.substring(0, this.pathEndIdx());
    }
    
    public String rawQuery() {
        final int start = this.pathEndIdx() + 1;
        return (start < this.uri.length()) ? this.uri.substring(start) : "";
    }
    
    private int pathEndIdx() {
        if (this.pathEndIdx == -1) {
            this.pathEndIdx = findPathEndIndex(this.uri);
        }
        return this.pathEndIdx;
    }
    
    private Map<String, List<String>> decodeParams(final String s, int from, final Charset charset, int paramsLimit) {
        final int len = s.length();
        if (from >= len) {
            return Collections.emptyMap();
        }
        if (s.charAt(from) == '?') {
            ++from;
        }
        final Map<String, List<String>> params = new LinkedHashMap<String, List<String>>();
        int nameStart = from;
        int valueStart = -1;
        int i = 0;
    Label_0192:
        for (i = from; i < len; ++i) {
            switch (s.charAt(i)) {
                case '=': {
                    if (nameStart == i) {
                        nameStart = i + 1;
                        break;
                    }
                    if (valueStart < nameStart) {
                        valueStart = i + 1;
                        break;
                    }
                    break;
                }
                case ';': {
                    if (this.semicolonIsNormalChar) {
                        break;
                    }
                }
                case '&': {
                    if (this.addParam(s, nameStart, valueStart, i, params, charset) && --paramsLimit == 0) {
                        return params;
                    }
                    nameStart = i + 1;
                    break;
                }
                case '#': {
                    break Label_0192;
                }
            }
        }
        this.addParam(s, nameStart, valueStart, i, params, charset);
        return params;
    }
    
    private boolean addParam(final String s, final int nameStart, int valueStart, final int valueEnd, final Map<String, List<String>> params, final Charset charset) {
        if (nameStart >= valueEnd) {
            return false;
        }
        if (valueStart <= nameStart) {
            valueStart = valueEnd + 1;
        }
        final String name = decodeComponent(s, nameStart, valueStart - 1, charset, this.htmlQueryDecoding);
        final String value = decodeComponent(s, valueStart, valueEnd, charset, this.htmlQueryDecoding);
        List<String> values = params.get(name);
        if (values == null) {
            values = new ArrayList<String>(1);
            params.put(name, values);
        }
        values.add(value);
        return true;
    }
    
    public static String decodeComponent(final String s) {
        return decodeComponent(s, HttpConstants.DEFAULT_CHARSET);
    }
    
    public static String decodeComponent(final String s, final Charset charset) {
        if (s == null) {
            return "";
        }
        return decodeComponent(s, 0, s.length(), charset, true);
    }
    
    private static String decodeComponent(final String s, final int from, final int toExcluded, final Charset charset, final boolean plusToSpace) {
        final int len = toExcluded - from;
        if (len <= 0) {
            return "";
        }
        int firstEscaped = -1;
        for (int i = from; i < toExcluded; ++i) {
            final char c = s.charAt(i);
            if (c == '%' || (c == '+' && plusToSpace)) {
                firstEscaped = i;
                break;
            }
        }
        if (firstEscaped == -1) {
            return s.substring(from, toExcluded);
        }
        final int decodedCapacity = (toExcluded - firstEscaped) / 3;
        final byte[] buf = PlatformDependent.allocateUninitializedArray(decodedCapacity);
        final StringBuilder strBuf = new StringBuilder(len);
        strBuf.append(s, from, firstEscaped);
    Label_0273:
        for (int j = firstEscaped; j < toExcluded; ++j) {
            final char c2 = s.charAt(j);
            if (c2 == '%') {
                int bufIdx = 0;
                while (j + 3 <= toExcluded) {
                    buf[bufIdx++] = StringUtil.decodeHexByte(s, j + 1);
                    j += 3;
                    if (j >= toExcluded || s.charAt(j) != '%') {
                        --j;
                        strBuf.append(new String(buf, 0, bufIdx, charset));
                        continue Label_0273;
                    }
                }
                throw new IllegalArgumentException("unterminated escape sequence at index " + j + " of: " + s);
            }
            strBuf.append((c2 != '+' || !plusToSpace) ? c2 : ' ');
        }
        return strBuf.toString();
    }
    
    private static int findPathEndIndex(final String uri) {
        final int len = uri.length();
        for (int i = 0; i < len; ++i) {
            final char c = uri.charAt(i);
            if (c == '?' || c == '#') {
                return i;
            }
        }
        return len;
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    public static final class Builder
    {
        private boolean hasPath;
        private int maxParams;
        private boolean semicolonIsNormalChar;
        private Charset charset;
        private boolean htmlQueryDecoding;
        
        private Builder() {
            this.hasPath = true;
            this.maxParams = 1024;
            this.charset = HttpConstants.DEFAULT_CHARSET;
            this.htmlQueryDecoding = true;
        }
        
        public Builder hasPath(final boolean hasPath) {
            this.hasPath = hasPath;
            return this;
        }
        
        public Builder maxParams(final int maxParams) {
            this.maxParams = maxParams;
            return this;
        }
        
        public Builder semicolonIsNormalChar(final boolean semicolonIsNormalChar) {
            this.semicolonIsNormalChar = semicolonIsNormalChar;
            return this;
        }
        
        public Builder charset(final Charset charset) {
            this.charset = charset;
            return this;
        }
        
        public Builder htmlQueryDecoding(final boolean htmlQueryDecoding) {
            this.htmlQueryDecoding = htmlQueryDecoding;
            return this;
        }
        
        public QueryStringDecoder build(final String uri) {
            return new QueryStringDecoder(this, uri, null);
        }
        
        public QueryStringDecoder build(final URI uri) {
            return new QueryStringDecoder(this, uri, null);
        }
    }
}
