package dareka.processor;

import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import dareka.common.HttpIOException;

public class HttpRequestHeader extends HttpHeader {
    /**
     * Request-Line
     *
     * <pre>
     * Request-Line   = Method SP Request-URI SP HTTP-Version CRLF
     * </pre>
     */
    private static final Pattern PROXY_REQUEST_LINE_PATTERN = // [nl] host部にnullを許容する
            Pattern.compile("^([A-Z]+) ((?:http://)?([^/:]+)?(?::(\\d+))?(/\\S*)?) (HTTP/1\\.[01])\r\n");
    private static final Pattern GZIP_ACCEPTED_PATTERN = // [nl]
            Pattern.compile("(^|,\\s*)gzip(\\s*,|$)", Pattern.CASE_INSENSITIVE);
    private String method;
    private String uri;
    private String scheme;
    private String host;
    private int port;
    private String path;
    private String version;

    public HttpRequestHeader(InputStream source) throws IOException {
        super(source);
        init(false);
    }
    
    public HttpRequestHeader(InputStream source, boolean tlsLoopback) throws IOException {
        super(source);
        init(tlsLoopback);
    }

    public HttpRequestHeader(String source) throws IOException {
        super(source);
        init(false);
    }

    private void init(boolean tlsLoopback) throws HttpIOException {
        Matcher m = PROXY_REQUEST_LINE_PATTERN.matcher(getStartLine());
        if (m.find()) {
            method = m.group(1);
            uri = m.group(2);
            scheme = "http";
            host = m.group(3);
            port = m.group(4) == null ? 80 : Integer.parseInt(m.group(4));
            path = m.group(5) == null ? "" : m.group(5);
            version = m.group(6);
            
            if (host == null) {
                if (tlsLoopback && (host = getMessageHeader("Host")) != null) {
                    // TLS MitM機能
                    if (host.isEmpty() || host.contains("@") || host.contains("/")
                            || !path.startsWith("/")) {
                        throw new HttpIOException(
                                "invalid tlsLooback request:\r\n" + super.toString());
                    }
                    scheme = "https";
                    uri = "https://" + host + path;
                } else if (Boolean.getBoolean("localFileServer")) {
                    // [nl] ローカルファイルサーバ機能
                    host = "LOCAL";
                    uri = "http://" + host + path;
                } else {
                    throw new HttpIOException(
                            "invalid proxy request:\r\n" + super.toString());
                }
            } else {
                // setProxyMyself()時に本来httpsだった場合
                String xnlParam = getMessageHeader(X_NICOCACHE_NL);
                if (xnlParam != null && xnlParam.contains(XNL_TLS)) {
                    scheme = "https";
                    uri = "https" + uri.substring(4);
                }
            }
        } else {
            throw new HttpIOException("invalid request:\r\n" + super.toString());
        }
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
        updateStartLine();
    }

    public String getURI() {
        // The return value is usually used by HttpURLConnection.
        // HttpURLConnection (more specifically, sun.net.NetworkClient) uses
        // default encoding to convert Java string to byte sequence.
        // Some UAs violate RFC, and send URI of non-ASCII.
        // To make these UAs work, it may be necessary to convert the encoding.
        return uri;
    }

    public void setURI(String uri) {
        this.uri = uri;
        updateStartLine();
        // [nl] 内部変数(pathとか)にも反映する
        try {
            init(false);
        } catch (HttpIOException e) {
            // ignore
        }
    }
    
    public String getScheme() {
        return scheme;
    }

    public String getHost() {
        return host;
    }

    public int getPort() {
        return port;
    }

    public String getPath() {
        return path;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
        updateStartLine();
    }

    public void replaceUriWithPath() {
        if (GET.equals(method) || POST.equals(method)) {
            uri = path;
            updateStartLine();
        }
    }

    private void updateStartLine() {
        setStartLine(method + " " + uri + " " + version + "\r\n");
    }

    /**
     * [nl] Accept-Encoding に gzip が含まれているか？
     * @return gzip が含まれていれば true
     * @since NicoCache_nl+111111mod
     */
    public boolean isGzipAccepted() {
        String acceptEncoding = getMessageHeader(ACCEPT_ENCODING);
        if (acceptEncoding == null) {
            return false;
        }
        return GZIP_ACCEPTED_PATTERN.matcher(acceptEncoding).find();
    }

}
