
package com.googlecode.android_scripting.facade.wifi;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;

import com.googlecode.android_scripting.Log;
import com.googlecode.android_scripting.facade.FacadeManager;
import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
import com.googlecode.android_scripting.rpc.Rpc;
import com.googlecode.android_scripting.rpc.RpcParameter;
import com.googlecode.android_scripting.rpc.RpcOptional;

/**
 * Basic http operations.
 */
public class HttpFacade extends RpcReceiver {

    private ServerSocket mServerSocket = null;
    private int mServerTimeout = -1;
    private HashMap<Integer, Socket> mSockets = null;
    private int socketCnt = 0;

    public HttpFacade(FacadeManager manager) throws IOException {
        super(manager);
        mSockets = new HashMap<Integer, Socket>();
    }

    private void inputStreamToOutputStream(InputStream in, OutputStream out) throws IOException {
        if (in == null) {
            Log.e("InputStream is null.");
            return;
        }
        if (out == null) {
            Log.e("OutputStream is null.");
            return;
        }
        try {
            int read = 0;
            byte[] bytes = new byte[1024];
            while ((read = in.read(bytes)) != -1) {
                out.write(bytes, 0, read);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            in.close();
            out.close();
        }

    }

    private String inputStreamToString(InputStream in) throws IOException {
        BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        String str = null;
        while ((str = r.readLine()) != null) {
            sb.append(str);
        }
        return sb.toString();
    }

    /**
     * Send an http request and get the response.
     *
     * @param url The url to send request to.
     * @return The HttpURLConnection object.
     */
    private HttpURLConnection httpRequest(String url) throws IOException {
        URL targetURL = new URL(url);
        HttpURLConnection urlConnection = null;
        try {
            urlConnection = (HttpURLConnection) targetURL.openConnection();
            urlConnection.connect();
            int respCode = urlConnection.getResponseCode();
            String respMsg = urlConnection.getResponseMessage();
            Log.d("Got response code: " + respCode + " and response msg: " + respMsg);
        } catch (IOException e) {
            Log.e("Failed to open a connection to " + url);
            Log.e(e.toString());
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }
        return urlConnection;
    }

    @Rpc(description = "Start waiting for a connection request on a specified port.",
            returns = "The index of the connection.")
    public Integer httpAcceptConnection(Integer port) throws IOException {
        mServerSocket = new ServerSocket(port);
        if (mServerTimeout > 0) {
            mServerSocket.setSoTimeout(mServerTimeout);
        }
        Socket sock = mServerSocket.accept();
        socketCnt += 1;
        mSockets.put(socketCnt, sock);
        return socketCnt;
    }

    @Rpc(description = "Download a file from specified url.")
    public void httpDownloadFile(String url) throws IOException {
        HttpURLConnection urlConnection = httpRequest(url);
        String filename = null;
        String contentDisposition = urlConnection.getHeaderField("Content-Disposition");
        // Try to figure out the name of the file being downloaded.
        // If the server returned a filename, use it.
        if (contentDisposition != null) {
            int idx = contentDisposition.toLowerCase().indexOf("filename");
            if (idx != -1) {
                filename = contentDisposition.substring(idx + 9);
                Log.d("Using name returned by server: " + filename);
            }
        }
        // If the server did not provide a filename to us, use the last part of url.
        if (filename == null) {
            int lastIdx = url.lastIndexOf('/');
            filename = url.substring(lastIdx + 1);
            Log.d("Using name from url: " + filename);
        }
        InputStream in = new BufferedInputStream(urlConnection.getInputStream());
        String outPath = "/sdcard/Download/" + filename;
        OutputStream output = new FileOutputStream(new File(outPath));
        inputStreamToOutputStream(in, output);
        Log.d("Downloaded file at " + outPath);
    }

    @Rpc(description = "Make an http request and return the response message.")
    public HttpURLConnection httpPing(@RpcParameter(name = "url") String url) throws IOException {
        try {
            HttpURLConnection urlConnection = null;
            urlConnection = httpRequest(url);
            return urlConnection;
        } catch (UnknownHostException e) {
            return null;
        }
    }

    @Rpc(description = "Make an http request and return the response content as a string.")
    public String httpRequestString(@RpcParameter(name = "url") String url) throws IOException {
        HttpURLConnection urlConnection = httpRequest(url);
        InputStream in = new BufferedInputStream(urlConnection.getInputStream());
        String result = inputStreamToString(in);
        Log.d("Fetched: " + result);
        return result;
    }

    @Rpc(description = "Set how many milliseconds to wait for an incoming connection.")
    public void httpSetServerTimeout(@RpcParameter(name = "timeout") Integer timeout)
            throws SocketException {
        mServerSocket.setSoTimeout(timeout);
        mServerTimeout = timeout;
    }

    @Rpc(description = "Ping to host(URL or IP), return success (true) or fail (false).")
    // The optional timeout parameter is in unit of second.
    public Boolean pingHost(@RpcParameter(name = "host") String hostString,
            @RpcParameter(name = "timeout") @RpcOptional Integer timeout) {
        try {
            String host;
            try {
                URL url = new URL(hostString);
                host = url.getHost();
            } catch (java.net.MalformedURLException e) {
                Log.d("hostString is not URL, it may be IP address.");
                host = hostString;
            }

            Log.d("Host:" + host);
            String pingCmdString = "ping -c 1 ";
            if (timeout != null) {
                pingCmdString = pingCmdString + "-W " + timeout + " ";
            }
            pingCmdString = pingCmdString + host;
            Log.d("Execute command: " + pingCmdString);
            Process p1 = java.lang.Runtime.getRuntime().exec(pingCmdString);
            int returnVal = p1.waitFor();
            boolean reachable = (returnVal == 0);
            Log.d("Ping return Value:" + returnVal);
            return reachable;
        } catch (Exception e){
            e.printStackTrace();
            return false;
        }
        /*TODO see b/18899134 for more information.
        */
    }

    @Override
    public void shutdown() {
        for (int key : mSockets.keySet()) {
            Socket sock = mSockets.get(key);
            try {
                sock.close();
            } catch (IOException e) {
                Log.e("Failed to close socket " + key + " on port " + sock.getLocalPort());
                e.printStackTrace();
            }
        }
    }
}
