/*
 * Copyright (c) 2004-2017, by Alex Leigh.
 * All rights reserved.
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE
 * The copyright notice above does not evidence any
 * actual or intended publication of such source code.
 */

package lc.mecha.http.client;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

/**
 * Client-side utility functions for interacting with remote HTTP servers.
 *
 * @author Alex Leigh
 */
// TODO: Determine if we really have to consume the responses here. May waste performance.
public class HttpUtil {
    private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);
    public static String HEADER_CONTENTTYPE = "Content-Type";
    public static String HEADER_SOAPACTION = "SOAPAction";

    public static String getUntrustedContent(String url)
            throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException, IOException {
        final CredentialsProvider credsProvider = new BasicCredentialsProvider();
        HttpClient client = makeOverlyTrustingClient(credsProvider);
        HttpGet getReq = new HttpGet(url);
        HttpResponse putResponse;
        putResponse = client.execute(getReq);
        return EntityUtils.toString(putResponse.getEntity());
    }

    public static HttpClient makeOverlyTrustingClient(CredentialsProvider credsProvider) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        HttpClientBuilder b = HttpClientBuilder.create();

        SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build();
        b.setSslcontext(sslContext);

        X509HostnameVerifier hostnameVerifier = SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;

        SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", sslSocketFactory)
                .build();

        /* Allows the client to be used in multi-threaded situations */
        PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        b.setConnectionManager(connMgr);

        HttpClient client = b.setDefaultCredentialsProvider(credsProvider).build();
        return client;
    }

    // TODO: dstPath be URL encoded

    /**
     * Put the given file to the remote server using the HTTP PUT command. Note that the source must be contained
     * within a file. The reason for this is that the client may have to retry the request (in situations where
     * authentication such as NTLM is used). An input stream would not be repeatable.
     *
     * @param target
     * @param srcFile
     * @param dstPath
     * @throws Exception
     */
    public static void putFile(HttpHost target, File srcFile, String dsthost, String dstPath, Credentials proxyUser)
            throws Exception {
        logger.debug("Sending file: {} {} {}", target, srcFile, dstPath);

        final CredentialsProvider credsProvider = new BasicCredentialsProvider();

        // FIXME: Must not be hard-coded!!
        credsProvider.setCredentials(AuthScope.ANY, proxyUser);

        HttpClient client = makeOverlyTrustingClient(credsProvider);

        HttpPut putRequest = new HttpPut(dstPath.replace(" ", "%20"));
        logger.debug("Request: {}", putRequest);

        putRequest.setHeader("host", dsthost);
        putRequest.setEntity(new FileEntity(srcFile));
        HttpResponse putResponse;

        putResponse = client.execute(target, putRequest);
        //EntityUtils.consume(putResponse.getEntity());
        logger.debug("put response: {}", EntityUtils.toString(putResponse.getEntity()));

    }

    public static void deleteFile(HttpHost target, String dstHost, String dstPath, Credentials proxyUser) throws
            Exception {
        logger.debug("Deleting file: {}  {}", target, dstPath);

        final CredentialsProvider credsProvider = new BasicCredentialsProvider();

        // FIXME: Must not be hard-coded!!
        credsProvider.setCredentials(AuthScope.ANY, proxyUser);

        HttpClient client = makeOverlyTrustingClient(credsProvider);

        HttpDelete putRequest = new HttpDelete(dstPath.replace(" ", "%20"));
        logger.debug("Request: {}", putRequest);

        putRequest.setHeader("host", dstHost);
        HttpResponse putResponse;

        putResponse = client.execute(target, putRequest);
        EntityUtils.consume(putResponse.getEntity());
    }

    /**
     * Returns <code>true</code> if the given file exists on the remote HTTP server, otherwise, returns false.
     *
     * @param target
     * @param dstHost
     * @param dstPath
     * @return
     * @throws Exception
     */
    public static boolean checkExists(HttpHost target, String dstHost, String dstPath, Credentials proxyUser) throws
            Exception {
        logger.info("HEADING'ing file. [target: {}] [dstPath: {}] [dstHost: {}]", target, dstPath, dstHost);

        final CredentialsProvider credsProvider = new BasicCredentialsProvider();

        // FIXME: Must not be hard-coded!!
        credsProvider.setCredentials(AuthScope.ANY, proxyUser);

        HttpClient client = makeOverlyTrustingClient(credsProvider);

        HttpHead req = new HttpHead(dstPath.replace(" ", "%20"));
        logger.debug("Request: {}", req);

        req.setHeader("host", dstHost);

        HttpResponse res = client.execute(target, req);
        EntityUtils.consume(res.getEntity());

        return res.getStatusLine().getStatusCode() == 200;

    }

    /**
     * Log a <code>HttpResponse</code> object to the default logger for <code>HttpUtil</code>. This method is
     * primarily intended for debugging and should be avoided in production code.
     *
     * @param response
     * @throws IOException
     */
    public static void printResponse(HttpResponse response) throws IOException {
        logger.info("printResponse() ---------------------------");
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            logger.info("{}", EntityUtils.toString(entity));
            EntityUtils.consume(entity);
        }
    }

    public static boolean isMultipartType(String s) {
        return s != null && s.toLowerCase().indexOf("multipart/form-data") > -1;
    }
}

