/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.android_scripting.facade.wifi;

import com.googlecode.android_scripting.FileUtils;
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.RpcOptional;
import com.googlecode.android_scripting.rpc.RpcParameter;

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;


/**
 * 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);
        }
        r.close();
        return sb.toString();
    }

    /**
     * Send an http request and get the response.
     *
     * @param url The url to send request to.
     * @param timeout Time to load the page
     * @return The HttpURLConnection object.
     */
    private HttpURLConnection httpRequest(String url, Integer timeout) throws IOException {
        if (timeout == null) {
            timeout = 50000;
        }
        URL targetURL = new URL(url);
        HttpURLConnection urlConnection;
        try {
            urlConnection = (HttpURLConnection) targetURL.openConnection();
            urlConnection.setConnectTimeout(9000);
            urlConnection.setReadTimeout(timeout);
            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());
            throw e;
        }
        return urlConnection;
    }

    @Rpc(description = "Start waiting for a connection request on a specified port.",
            returns = "The index of the connection.")
    public Integer httpAcceptConnection(@RpcParameter(name = "port") 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, to an (optionally) specified path.")
    public void httpDownloadFile(@RpcParameter(name = "url") String url,
            @RpcParameter(name="outPath") @RpcOptional String outPath) throws IOException {
        // Create the input stream
        HttpURLConnection urlConnection = httpRequest(url, null);
        // Parse destination path and create the output stream. The function assumes that the path
        // is specified relative to the system default Download dir.
        File outFile = FileUtils.getExternalDownload();
        if (outPath != null && outPath.trim().length() != 0) {
            // Check to see if the path is absolute.
            if (outPath.startsWith("/")) {
                outFile = new File(outPath);
            } else {
                outFile = new File(outFile, outPath);
            }
            // Check to see if specified path should be a dir.
            if (outPath.endsWith("/")) {
                if (!outFile.isDirectory() && !outFile.mkdirs()) {
                    throw new IOException("Failed to create the path: " + outPath);
                }
            }
        }
        // If no filename was specified, use the filename provided by the server.
        if (outFile.isDirectory()) {
            String filename = "";
            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 filename returned by server: " + filename);
                }
            }
            // If the server did not provide a filename to us, use the last part of url.
            if (filename.trim().length() == 0) {
               int lastIdx = url.lastIndexOf('/');
                filename = url.substring(lastIdx + 1);
                Log.d("Using name from url: " + filename);
            }
            outFile = new File(outFile, filename);
        }
        InputStream in = new BufferedInputStream(urlConnection.getInputStream());
        OutputStream output = new FileOutputStream(outFile);
        inputStreamToOutputStream(in, output);
        Log.d("Downloaded file from " + url + " to " + outPath);
        urlConnection.disconnect();
    }

    @Rpc(description = "Make an http request and return the response message.")
    public HttpURLConnection httpPing(
            @RpcParameter(name = "url") String url,
            @RpcParameter(name = "timeout") @RpcOptional Integer timeout) throws IOException {
        HttpURLConnection urlConnection = null;
        urlConnection = httpRequest(url, timeout);
        urlConnection.disconnect();
        return urlConnection;
    }

    @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, null);
        InputStream in = new BufferedInputStream(urlConnection.getInputStream());
        String result = inputStreamToString(in);
        Log.d("Fetched: " + result);
        urlConnection.disconnect();
        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,
            @RpcParameter(name = "ping") @RpcOptional String pingType) {
        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(pingType!=null && pingType.equals("ping6")) {
                pingCmdString = "ping6 -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();
            }
        }
    }
}
