package org.jivesoftware.smack.proxy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.net.SocketFactory;

import org.jivesoftware.smack.util.Base64;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Http Proxy Socket Factory which returns socket connected to Http Proxy
 *
 * @author Atul Aggarwal
 */
class HTTPProxySocketFactory
        extends SocketFactory {

    private ProxyInfo proxy;

    public HTTPProxySocketFactory(ProxyInfo proxy) {
        this.proxy = proxy;
    }

    public Socket createSocket(String host, int port)
            throws IOException, UnknownHostException {
        return httpProxifiedSocket(host, port);
    }

    public Socket createSocket(String host, int port, InetAddress localHost,
                               int localPort)
            throws IOException, UnknownHostException {
        return httpProxifiedSocket(host, port);
    }

    public Socket createSocket(InetAddress host, int port)
            throws IOException {
        return httpProxifiedSocket(host.getHostAddress(), port);

    }

    public Socket createSocket(InetAddress address, int port,
                               InetAddress localAddress, int localPort)
            throws IOException {
        return httpProxifiedSocket(address.getHostAddress(), port);
    }

    private Socket httpProxifiedSocket(String host, int port)
            throws IOException {
        String proxyhost = proxy.getProxyAddress();
        int proxyPort = proxy.getProxyPort();
        Socket socket = new Socket(proxyhost, proxyPort);
        String hostport = "CONNECT " + host + ":" + port;
        String proxyLine;
        String username = proxy.getProxyUsername();
        if (username == null) {
            proxyLine = "";
        } else {
            String password = proxy.getProxyPassword();
            proxyLine = "\r\nProxy-Authorization: Basic "
                    + new String(Base64.encodeBytes((username + ":"
                    + password).getBytes("UTF-8")));
        }
        socket.getOutputStream().write((hostport + " HTTP/1.1\r\nHost: "
                + hostport + proxyLine + "\r\n\r\n").getBytes("UTF-8"));

        InputStream in = socket.getInputStream();
        StringBuilder got = new StringBuilder(100);
        int nlchars = 0;

        while (true) {
            char c = (char) in.read();
            got.append(c);
            if (got.length() > 1024) {
                throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Recieved " +
                        "header of >1024 characters from "
                        + proxyhost + ", cancelling connection");
            }
            if (c == -1) {
                throw new ProxyException(ProxyInfo.ProxyType.HTTP);
            }
            if ((nlchars == 0 || nlchars == 2) && c == '\r') {
                nlchars++;
            } else if ((nlchars == 1 || nlchars == 3) && c == '\n') {
                nlchars++;
            } else {
                nlchars = 0;
            }
            if (nlchars == 4) {
                break;
            }
        }

        if (nlchars != 4) {
            throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Never " +
                    "received blank line from "
                    + proxyhost + ", cancelling connection");
        }

        String gotstr = got.toString();

        BufferedReader br = new BufferedReader(new StringReader(gotstr));
        String response = br.readLine();

        if (response == null) {
            throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Empty proxy " +
                    "response from " + proxyhost + ", cancelling");
        }

        Matcher m = RESPONSE_PATTERN.matcher(response);
        if (!m.matches()) {
            throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Unexpected " +
                    "proxy response from " + proxyhost + ": " + response);
        }

        int code = Integer.parseInt(m.group(1));

        if (code != HttpURLConnection.HTTP_OK) {
            throw new ProxyException(ProxyInfo.ProxyType.HTTP);
        }

        return socket;
    }

    private static final Pattern RESPONSE_PATTERN
            = Pattern.compile("HTTP/\\S+\\s(\\d+)\\s(.*)\\s*");

}
