package org.tmurakam.android.emufix;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

/**
 * Created by tmurakam on 2016/01/24.
 */
public class Server {
    private int fLocalPort;
    private String fProxyServer;
    private int fProxyPort;

    private enum ProxyState {
        READING_STATUS_LINE,
        READING_HEADERS,
        READING_BODY
    }

    public Server(int localPort, String proxyServer, int proxyPort) {
        fLocalPort = localPort;
        fProxyServer = proxyServer;
        fProxyPort = proxyPort;
    }

    public void run() {
        try (ServerSocket server = new ServerSocket(fLocalPort)) {
            while (true) {
                Socket socket = server.accept();
                new ServerForwarderThread(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private class ServerForwarderThread extends Thread {
        private Socket fClient;
        private Socket fServer = null;

        public ServerForwarderThread(Socket client) {
            fClient = client;
        }

        public void run() {
            try {
                // connect proxy server
                try {
                    fServer = new Socket(fProxyServer, fProxyPort);
                } catch (UnknownHostException e) {
                    //e.printStackTrace();
                    System.err.println("Unknown host: " + fProxyServer);
                    return;
                } catch (IOException e) {
                    System.err.println(e.getMessage());
                    return;
                }

                // クライアントから先頭6バイトを読む : CONNECT メソッド判別
                InputStream in = fClient.getInputStream();
                ByteArrayOutputStream bos = new ByteArrayOutputStream();

                while (true) {
                    int ch = in.read();
                    bos.write(ch);
                    if (ch == -1) {
                        throw new RuntimeException("No request line");
                    }
                    if (ch == '\n') break;
                }
                String requestLine = bos.toString("UTF-8");
                fServer.getOutputStream().write(bos.toByteArray());

                // client -> server フォワーダ起動
                new PlainForwarderThread(fClient, fServer).start();

                // server -> client フォワーダ起動
                forwarder(requestLine.startsWith("CONNECT"));
            }
            catch (Exception e) {
                System.err.println(e.getMessage());
            }
            finally {
                try {
                    fServer.close();
                    fClient.close();
                } catch (IOException e) {
                    // ignore
                }
            }
        }

        private void forwarder(boolean isConnectMethod) throws IOException {
            InputStream in = fServer.getInputStream();
            OutputStream out = fClient.getOutputStream();

            final int BufferSize = 10240;
            byte[] serverBuffer = new byte[BufferSize];

            ProxyState state = ProxyState.READING_STATUS_LINE;
            int headerLength = 0;

            while (true) {
                int ch;
                boolean end = false;

                switch (state) {
                    case READING_STATUS_LINE:
                        ch = in.read();
                        out.write(ch);
                        if (ch == '\n') {
                            state = ProxyState.READING_HEADERS;
                        }
                        break;

                    case READING_HEADERS:
                        ch = in.read();
                        if (!isConnectMethod) { // skip response header for CONNECT method.
                            out.write(ch);
                        }
                        if (ch != '\r' && ch != '\n') {
                            headerLength++;
                        }
                        else if (ch == '\n') {
                            if (headerLength == 0) {
                                if (isConnectMethod) {
                                    out.write('\r');
                                    out.write('\n');
                                }
                                state = ProxyState.READING_BODY;
                            }
                            headerLength = 0;
                        }
                        break;

                    case READING_BODY:
                        int len = in.read(serverBuffer);
                        if (len < 0) {
                            end = true;
                            break;
                        }
                        out.write(serverBuffer, 0, len);
                        break;
                }

                if (end) break;
            }
        }
    }

    /**
     * 無加工でフォワードするスレッド
     */
    private static class PlainForwarderThread extends Thread {
        private Socket fInSocket;
        private Socket fOutSocket;

        public PlainForwarderThread(Socket inSocket, Socket outSocket) {
            fInSocket = inSocket;
            fOutSocket = outSocket;
        }

        public void run() {
            try {
                forwarder();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private void forwarder() throws IOException {
            InputStream in = fInSocket.getInputStream();
            OutputStream out = fOutSocket.getOutputStream();

            try {
                final int BufferSize = 10240;
                byte[] buffer = new byte[BufferSize];

                while (true) {
                    int len = in.read(buffer);
                    if (len < 0) break;

                    out.write(buffer, 0, len);
                }
            } catch (SocketException e) {
                System.err.println(e.getMessage());
                fInSocket.close();
                fOutSocket.close();
            }
            finally {
                fInSocket.shutdownInput();
                fOutSocket.shutdownOutput();
            }
        }
    }
}

@@ -173,16 +173,49 @@
 ception;
+%0Aimport java.util.logging.Logger;
 %0A%0A/**%0A *
@@ -362,24 +362,88 @@
 ProxyPort;%0A%0A
+    private static Logger logger = Logger.getLogger(%22Server%22);%0A%0A
     private 
@@ -881,24 +881,74 @@
 er.accept();
+%0A%0A                logger.info(%22Client connected%22);
 %0A           
@@ -1568,69 +1568,22 @@
     
-//e.printStackTrace();%0A                    System.err.println
+logger.warning
 (%22Un
@@ -1588,16 +1588,22 @@
 Unknown 
+proxy 
 host: %22 
@@ -1604,28 +1604,30 @@
 st: %22 + 
-fProxyServer
+e.getMessage()
 );%0A     
@@ -1711,35 +1711,65 @@
         
-System.err.println(
+logger.warning(%22Connect Proxy Server failed: %22 + 
 e.getMes
@@ -2432,24 +2432,67 @@
 eArray());%0A%0A
+                logger.info(requestLine);%0A%0A
             
@@ -2762,34 +2762,30 @@
         
-System.err.println
+logger.warning
 (e.getMe
@@ -5564,35 +5564,46 @@
         
-e.printStackTrac
+logger.warning(e.getMessag
 e()
+)
 ;%0A      
@@ -6159,26 +6159,22 @@
     
-System.err.println
+logger.warning
 (e.g

use logger