package com.workfusion.odf;

import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.ServerSocket;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import com.github.markusbernhardt.proxy.ProxySearch;
import io.webfolder.cdp.Launcher;
import io.webfolder.cdp.session.Session;
import io.webfolder.cdp.session.SessionFactory;
import io.webfolder.cdp.session.WaitUntil;

import static java.lang.String.format;
import static java.net.Proxy.Type.SOCKS;
import static java.nio.file.Files.write;


/**
 * @author Leanid_Piliptsevich
 **/
public class HtmlToPdfPrinter {
    public static final String COMPLETION_SUCCESS_MESSAGE = "Successfully printed PDF";



    /**
     * @param args
     *             Mandatory:
     *             1 Web link that will be printed to PDF
     *             2 Path of PDF file where link will be printed
     * */
    public static void main(String... args)  {

        if(args.length!=2) {
            throw new IllegalArgumentException("2 or 4 parameters expected, but got "+ args.length);
        }

        final String urlOfHtmlToPrintToPDF = getInputWebUrlToPrint(args[0]);

        final String pdfPath = getOutputPdfPath(args[1]);
        final HtmlToPdfPrinter htmlToPdfPrinter =  new HtmlToPdfPrinter();

        try {
            Retry.withRetry(10, 1, 50, (Retry.CallToRetry<Void>) () -> {
                htmlToPdfPrinter.generatePDF(urlOfHtmlToPrintToPDF, pdfPath);
                return null;
            });
        } catch (Exception e) {
            System.out.println("Failed with: " + e.getMessage());
            e.printStackTrace();
        }

    }

    private static String getInputWebUrlToPrint(String arg) {
        if(isBlank(arg)){
            throw new IllegalArgumentException("Empty or null url of html file");
        }
        return arg;
    }

    private static boolean isBlank(String arg) {
        return arg == null || arg.trim().isEmpty();
    }

    private static String getOutputPdfPath(String arg) {
        if (isBlank(arg)){
            throw new IllegalArgumentException("Empty or null path of pdf file");
        }
        return arg;
    }

    private void generatePDF(String url, String pdfPath) {
        int availablePort = findAvailablePort();
        System.out.println("Launching on available port: " + availablePort);
        Launcher launcher = null;
        try (SessionFactory factory = new SessionFactory(availablePort)) {
            ProxySelector proxySelector = getProxySelector();

            setUpCDP4JProxy(proxySelector, factory);

            final Proxy chromeProxy = getProxy(proxySelector, new URI(url), true);

            final List<String> arguments = getChromeArguments(chromeProxy);

            launcher = new Launcher(factory);
            launcher.launch(launcher.findChrome(), arguments, 20000);

            try (Session session = factory.create()) {
                session.navigateAndWait(url, WaitUntil.Load, 60000);
                for (int i = 0; i < 2; i++) {
                    session.evaluate("window.scrollTo(0, document.body.scrollHeight)");
                    session.wait(1000);
                }

                session.waitDocumentReady(60000);
                System.out.println("Generating PDF");

                final byte[] content = session
                        .getCommand()
                        .getPage()
                        .printToPDF(
                                false,
                                false,
                                true,
                                1.0,
                                8.19,
                                11.61,
                                0.4,
                                0.4,
                                0.4,
                                0.4,
                                "",
                                false,
                                "",
                                "",
                                false);

                final Path file = new File(pdfPath).toPath();
                write(file, content);


                final String canonicalPath = file.toFile().getCanonicalPath();
                System.out.println("Created pdf : " + canonicalPath);
                System.out.println(COMPLETION_SUCCESS_MESSAGE);

            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (launcher != null) {
                boolean isKilled = launcher.getProcessManager().kill();
                System.out.println("killing result: port:" + availablePort + ": " + isKilled);
            }
        }
    }

    private int findAvailablePort() {

        try (ServerSocket socket = new ServerSocket(0)) {
            return socket.getLocalPort();

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static List<String> getChromeArguments(Proxy chromeProxy) {
        List<String> arguments = new ArrayList<>();
        arguments.add("--headless");
        arguments.add("--disable-gpu");

        if (chromeProxy != null) {
            String protocol = SOCKS.equals(chromeProxy.type()) ? "socks5" : "http";
            InetSocketAddress chromeProxyAddress = (InetSocketAddress) chromeProxy.address();
            arguments.add(format("--proxy-server=%s://%s:%d",
                    protocol,
                    chromeProxyAddress.getHostName(),
                    chromeProxyAddress.getPort()));

        }
        return arguments;
    }

    private static ProxySelector getProxySelector() {
        ProxySearch proxySearch = ProxySearch.getDefaultProxySearch();
        return proxySearch.getProxySelector();
    }

    private static void setUpCDP4JProxy(ProxySelector proxySelector, SessionFactory factory)  {
        try {
            Proxy cdp4jProxy = getProxy(proxySelector,
                    new URI(format("http://%s:%d", factory.getHost(), factory.getPort())),
                    false);
            if (cdp4jProxy != null) {
                factory.setHttpClientProxy(cdp4jProxy);
                InetSocketAddress cdp4jProxyAddress = (InetSocketAddress) cdp4jProxy.address();
                factory.getWebSocketProxySettings().setHost(cdp4jProxyAddress.getHostName());
                factory.getWebSocketProxySettings().setPort(cdp4jProxyAddress.getPort());
            }

        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }


    }

    protected static Proxy getProxy(ProxySelector selector, URI uri, boolean supportSocks) {
        if (selector == null) {
            return null;
        }
        List<Proxy> proxies = selector.select(uri);
        Proxy found = null;
        for (Proxy proxy : proxies) {
            switch (proxy.type()) {
                case HTTP:
                    found = proxy;
                    break;
                case SOCKS:
                    if (supportSocks) {
                        found = proxy;
                    }
                    break;
                default:
                    found = null;
                    break;
            }
        }
        return found;
    }

}
