package fr.android.pointspermis.web;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import org.apache.http.util.ByteArrayBuffer;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created by vins on 08/01/15.
 */
public class HTTPSConnect {

    private Certificate ca;
    private HttpsURLConnection urlConnection;
    private List<String> cookies;

    private static final int READ_TIMEOUT = 10000;
    private static final int CONNECT_TIMEOUT = 15000;
    private static final char PARAMETER_DELIMITER = '&';
    private static final char PARAMETER_EQUALS_CHAR = '=';

    public HTTPSConnect(InputStream certificate) throws Exception {

        // Init a list of cookies
        cookies = new ArrayList<String>();

        // Load certificate
        try {
            ca = loadCA(certificate);
        } catch (Exception e) {
            throw new Exception("Unable to load CA certificate: " + e.getMessage());
        }

        // Create and set custom SSLSocketFactory (which trust the ca) as the default one
        try {
            HttpsURLConnection.setDefaultSSLSocketFactory(createSSLSocketFactory(ca));
        } catch (Exception e) {
            throw new Exception("Unable to create custom SSLSocketFactory: " + e.getMessage());
        }
    }

    private HttpsURLConnection openConnection(String u) throws Exception {

        URL url;
        HttpsURLConnection urlConnection;

        url = new URL(u);
        urlConnection = (HttpsURLConnection) url.openConnection();

        // Get cookies
        if(cookies.isEmpty()) {
            cookies = urlConnection.getHeaderFields().get("Set-Cookie");
        }
        else {
            // Reuse all previous cookies
            for (String cookie : cookies) {
                urlConnection.addRequestProperty("Cookie", cookie.split(";", 2)[0]);
            }
        }

        // Set a custom SSL socket factory
        //urlConnection.setSSLSocketFactory(socketFactory);

        /* Specify a custom hostname verifier
        HostnameVerifier hostnameVerifier = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                HostnameVerifier hv =
                        HttpsURLConnection.getDefaultHostnameVerifier();
                return hv.verify("tele7.interieur.gouv.fr", session);
            }
        };
        urlConnection.setHostnameVerifier(hostnameVerifier);
        */

        // Set timeouts
        urlConnection.setReadTimeout(READ_TIMEOUT);
        urlConnection.setConnectTimeout(CONNECT_TIMEOUT);

        return urlConnection;
    }

    public void clear() {
        if (urlConnection != null) {
            urlConnection.disconnect();
        }
        cookies = new ArrayList<String>();
    }

    public Certificate getCertificate() {
        return ca;
    }

    private InputStream getPage(String u) throws Exception {

        InputStream in;
        urlConnection = openConnection(u);

        try {
            in = urlConnection.getInputStream();
        } catch (IOException e) {
            throw new Exception("Unable to get remote input stream from SSL connection: " + e.getMessage());
        }

        return in;
    }

    public Document getHTML(String u) throws Exception {

        Document doc = null;
        InputStream in = getPage(u);

        try {
            doc = Jsoup.parse(in, null, "");
        } catch (IOException e) {
            throw new Exception("Unable to parse InputStream html to create the JSoup Document: " + e.getMessage());
        }

        return doc;
    }

    public Bitmap getImage(String u) throws Exception {

        Bitmap img = null;
        InputStream in = getPage(u);

        BufferedInputStream bis = new BufferedInputStream(in, 8190);

        ByteArrayBuffer baf = new ByteArrayBuffer(50);
        int current = 0;
        while ((current = bis.read()) != -1) {
            baf.append((byte)current);
        }
        byte[] imageData = baf.toByteArray();

        img = BitmapFactory.decodeByteArray(imageData, 0, imageData.length);

        return img;
    }

    public Document postData(String u, Map<String, String> urlParams) throws Exception {

        Document doc = null;
        HttpsURLConnection urlConnection = openConnection(u);
        String urlParameters = createQueryStringForParameters(urlParams);
        //Log.d("DEBUG", "POST Request params: " + urlParameters);

        try {
            urlConnection.setRequestMethod("POST");
            urlConnection.setRequestProperty("Content-Type",
                    "application/x-www-form-urlencoded");

            urlConnection.setRequestProperty("Content-Length", "" +
                    Integer.toString(urlParameters.getBytes().length));
            urlConnection.setRequestProperty("Content-Language", "fr-FR");

            urlConnection.setUseCaches(false);
            urlConnection.setDoInput(true);
            urlConnection.setDoOutput(true);
        } catch (Exception e) {

            e.printStackTrace();
            return null;
        }

        //Send request
        try {
            DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
            wr.writeBytes(urlParameters);
            wr.flush();
            wr.close();
        } catch (Exception e) {
            throw new Exception("Unable to send the POST request: " + e.getMessage());
        }

        //Get Response
        InputStream in = urlConnection.getInputStream();
        try {
            doc = Jsoup.parse(in, null, "");
        } catch (IOException e) {
            throw new Exception("Unable to parse InputStream html to create the JSoup Document: " + e.getMessage());
        }

        return doc;
    }

    private Certificate loadCA(InputStream crt) throws Exception {

        Certificate ca;
        CertificateFactory cf;

        // Create certificateFactory instance
        try {
            cf = CertificateFactory.getInstance("X.509");
        } catch (CertificateException e) {
            throw new Exception("Can not get a X.509 certificate instance: " + e.getMessage());
        }

        // Read and load the certificate
        InputStream caInput = new BufferedInputStream(crt);
        try {
            ca = cf.generateCertificate(caInput);
            //Log.d("DEBUG", "ca=" + ((X509Certificate) ca).getSubjectDN());
        } catch (CertificateException e) {
            throw new Exception("Unable to read the certificate X509: " + e.getMessage());
        } finally {
            try {
                caInput.close();
            } catch (IOException e) {
                throw new Exception("Can not close the certificate file: " + e.getMessage());
            }
        }

        return ca;
    }

    private SSLSocketFactory createSSLSocketFactory(Certificate ca) throws Exception {

        SSLContext sslContext;
        CustomSSL.CustomSSLSocketFactory socketFactory;
        KeyStore keyStore;
        TrustManagerFactory tmf;

        // Create a KeyStore containing our trusted CAs
        String keyStoreType = KeyStore.getDefaultType();

        try {
            keyStore = KeyStore.getInstance(keyStoreType);
        } catch (KeyStoreException e) {
            throw new Exception("Can not get KeyStore instance: " + e.getMessage());
        }
        try {
            keyStore.load(null, null);
        } catch (Exception e) {
            throw new Exception("Error during keystore loading: " + e.getMessage());
        }
        try {
            keyStore.setCertificateEntry("ca", ca);
        } catch (KeyStoreException e) {
            throw new Exception("Unable to read certificate entry 'ca': " + e.getMessage());
        }

        // Create a TrustManager that trusts the CAs in our KeyStore
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        try {
            tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("Can not get TrustManager instance: " + e.getMessage());
        }
        try {
            tmf.init(keyStore);
        } catch (KeyStoreException e) {
            throw new Exception("Unable to openConnection keystore from trustmanager: " + e.getMessage());
        }

        // Init a new SSLContext (TLS)
        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);

        // Create the custom SSLSocketFactory
        socketFactory = new CustomSSL.CustomSSLSocketFactory(sslContext);

        return socketFactory;
    }

    private String createQueryStringForParameters(Map<String, String> parameters) throws Exception {

        StringBuilder parametersAsQueryString = new StringBuilder();

        if (parameters != null) {
            boolean firstParameter = true;

            for (String parameterName : parameters.keySet()) {

                if (!firstParameter) {
                    parametersAsQueryString.append(PARAMETER_DELIMITER);
                }

                try {
                    parametersAsQueryString
                            .append(URLEncoder.encode(parameterName, "UTF-8"))
                            .append(PARAMETER_EQUALS_CHAR)
                            .append(URLEncoder.encode(parameters.get(parameterName), "UTF-8"));
                } catch (Exception e) {
                    throw new Exception("Error during parameters encoding: " + e.getMessage());
                }

                firstParameter = false;
            }
        }

        return parametersAsQueryString.toString();
    }
}
