/**
 * Class to represent an HTTP Proxy.  Not recommended to be used
 * outside of the internal jscape library.  Use the higher level
 * functions to connect to the proxy vs these internal classes
 *
 * @author Michael Ingardia
 *
 */

package com.jscape.inet.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class ProxyHTTP
        implements Proxy {

    private static final int DEFAULTPORT = 80;

    private String proxyHost;

    private int proxyPort;

    private String host;

    private int port;

    private InputStream in;

    private OutputStream out;

    private Socket socket;

    private String user;

    private String passwd;

    /**
     * Create an instance of this class with the proxyHost.  Will
     * assume the default port which is defined in the static var(
     * DEFAULTPORT ) in this class
     *
     * @param proxyHost
     */
    public ProxyHTTP(String proxyHost) {
        int port = DEFAULTPORT;
        String host = proxyHost;
        if (proxyHost.indexOf(':') != -1) {
            try {
                host = proxyHost.substring(0, proxyHost.indexOf(':'));
                port = Integer.parseInt(proxyHost.substring(proxyHost.indexOf(':') + 1));
            } catch (Exception e) {
                // todo: ignore?
            }
        }
        this.proxyHost = host;
        this.proxyPort = port;
    }

    /**
     * create class specifing host and port
     *
     * @param proxyHost
     * @param proxyPort
     */
    public ProxyHTTP(String proxyHost, int proxyPort) {
        this.proxyHost = proxyHost;
        this.proxyPort = proxyPort;
    }

    /**
     * Set the credentials to connect to the proxy
     *
     * @param user
     * @param passwd
     */
    public void setUserPasswd(String user, String passwd) {
        this.user = user;
        this.passwd = passwd;
    }

    /**
     * connect to the proxy.
     *
     * @param socketFactory
     * @param host
     * @param port
     * @param timeout
     */
    public void connect(SocketFactory socketFactory, String host, int port, long timeout)
            throws Exception {
        this.host = host;
        this.port = port;
        try {
            if (socketFactory == null) {
                socket = Util.createSocket(proxyHost, proxyPort, timeout);
                in = socket.getInputStream();
                out = socket.getOutputStream();
            } else {
                socket = socketFactory.createSocket(proxyHost, proxyPort);
                in = socketFactory.getInputStream(socket);
                out = socketFactory.getOutputStream(socket);
            }
            if (timeout > 0) {
                socket.setSoTimeout((int) timeout);
            }
            socket.setTcpNoDelay(true);

            out.write(("CONNECT " + host + ":" + port + " HTTP/1.0\r\n")
                              .getBytes(StandardCharsets.UTF_8));

            if (user != null && passwd != null) {
                byte[] code = (user + ":" + passwd).getBytes(StandardCharsets.UTF_8);
                code = Util.toBase64(code, 0, code.length);
                out.write("Proxy-Authorization: Basic ".getBytes(StandardCharsets.US_ASCII));
                out.write(code);
                out.write("\r\n".getBytes(StandardCharsets.US_ASCII));
            }

            out.write("\r\n".getBytes(StandardCharsets.US_ASCII));
            out.flush();

            int foo = 0;

            StringBuffer sb = new StringBuffer();
            while (foo >= 0) {
                foo = in.read();
                if (foo != 13) {
                    sb.append((char) foo);
                    continue;
                }
                foo = in.read();
                if (foo != 10) {
                    continue;
                }
                break;
            }
            if (foo < 0) {
                throw new IOException();
            }

            String response = sb.toString();
            String reason = "Unknow reason";
            int code = -1;
            try {
                foo = response.indexOf(' ');
                int bar = response.indexOf(' ', foo + 1);
                code = Integer.parseInt(response.substring(foo + 1, bar));
                reason = response.substring(bar + 1);
            } catch (Exception e) {
                // todo: ignore?
            }
            if (code != 200) {
                throw new IOException("proxy error: " + reason);
            }

            int count = 0;
            while (true) {
                count = 0;
                while (foo >= 0) {
                    foo = in.read();
                    if (foo != 13) {
                        count++;
                        continue;
                    }
                    foo = in.read();
                    if (foo != 10) {
                        continue;
                    }
                    break;
                }
                if (foo < 0) {
                    throw new IOException();
                }
                if (count == 0) {
                    break;
                }
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (Exception eee) {
                // todo: ignore?
            }
            throw new Exception("ProxyHTTP: " + e.toString());
        }
    }

    /**
     * return the inputstream for this connection
     *
     * @return InputStream
     */
    public InputStream getInputStream() {
        return in;
    }

    /**
     * return the outputstream for this connection
     *
     * @return OutputStream
     */
    public OutputStream getOutputStream() {
        return out;
    }

    /**
     * get the actual socket
     *
     * @return Socket
     */
    public Socket getSocket() {
        return socket;
    }

    /**
     * close the connection
     */
    public void close() {
        try {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
            if (socket != null) {
                socket.close();
            }
        } catch (Exception e) {
            // todo: ignore?
        }
        in = null;
        out = null;
        socket = null;
    }

    /**
     * return the default port
     *
     * @return int
     */
    public static int getDefaultPort() {
        return DEFAULTPORT;
    }
}
