package de.duehl.html.download.logic;

/*
 * Copyright 2024 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.io.IOException;

import org.apache.http.impl.client.CloseableHttpClient;

import de.duehl.basics.logging.Logger;
import de.duehl.basics.retry.Retry;
import de.duehl.html.download.data.DownloadInfo;
import de.duehl.html.download.data.DownloadParameters;
import de.duehl.html.download.data.DownloadStatus;
import de.duehl.html.download.proxy.RotatingProxies;
import de.duehl.threads.timed.TimedThreadRunner;
import de.duehl.threads.timed.TimedThreadRunner.TaskState;

/**
 * Diese Klasse lädt Seiten aus dem WWW herunter. Dabei kann eine laxe Strategie im Umgang mit
 * Redirects gesetzt werden sowie angegeben werden, welcher Proxy gesetzt werden soll oder ob
 * eine rotierende Liste von Proxies verwendet werden soll.
 *
 * Verwendet wird hier der HttpClient von Apache.
 *
 * http://hc.apache.org/downloads.cgi
 *
 * -> httpcomponents-client-4.5.3-bin.zip
 * -> httpclient-4.5.3.jar, httpcore-4.4.6.jar und commons-logging-1.2.jar
 *
 *
 * https://hc.apache.org/httpcomponents-client-ga/examples.html
 *
 * Redirects:
 * https://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html#d5e334
 * http://www.baeldung.com/httpclient-stop-follow-redirect
 * http://blog.liguoliang.com/2014/httpclient-disable-automatic-redirect-in-httpget/
 *
 * Proxy:
 * http://stackoverflow.com/questions/5571744/java-convert-a-string-representing-an-ip-to-inetaddress
 *
 * Encoding:
 * http://stackoverflow.com/questions/5769717/how-can-i-get-an-http-response-body-as-a-string-in-java
 *
 * Get last url:
 * http://stackoverflow.com/questions/1456987/httpclient-4-how-to-capture-last-redirect-url
 *
 * @version 1.01     2024-08-20
 * @author Christian Dühl
 */

public class InternalDownloader {

    /** Information über den Download (und Inhalt der Seite), der nachher zurückgegeben wird. */
    private final DownloadInfo downloadInfo;

    /** Die Parameter für den Download. */
    private final DownloadParameters parameters;

    private int outerLoopStepNumber;
    private int innerLoopStepNumber;

    /**
     * Konstruktor.
     *
     * @param parameters
     *            Die Parameter für den Download.
     */
    public InternalDownloader(DownloadParameters parameters) {
        this.parameters = parameters;
        downloadInfo = new DownloadInfo(parameters.getWebsite());
    }

    public DownloadInfo download() {
        log("website = " + parameters.getWebsite());
        outerLoop();
        return downloadInfo;
    }

    /**
     * Wir verwenden ein Retry-Objekt, im Normalfall versucht dieses halt einmal die Seite zu laden.
     *
     * Bei einem rotierenden Proxy versucht es einmal mit allen Proxies die Seite zu laden.
     * Nicht direkt im Retry, sondern in der downloadInternal-Methode.
     *
     * Wünscht der Benutzer mehrere Versuche mit Wartezeit dazwischen, wird auch dies gemacht.
     *
     */
    private void outerLoop() {
        log("Start");
        outerLoopStepNumber = 0;

        int maximumNumberOfTries = parameters.getMaximumNumberOfTries();
        long secondsToSleep = parameters.getSecondsToSleep();
        Retry retry = new Retry(this::outerLoopStep, maximumNumberOfTries, secondsToSleep * 1000L);
        retry.tryAndTry();
        if (!retry.isSucessfullyDone()) {
            DownloadStatus status = downloadInfo.getStatus();
            if (status == DownloadStatus.OK) {
                throw new RuntimeException("Logikfeler!");
            }
            log("Ganz und gar kein Erfolg in allen Versuchen.");
        }

        log("Ende");
    }

    private void outerLoopStep() {
        ++outerLoopStepNumber;
        log("Start outerLoopStepNumber = " + outerLoopStepNumber);

        if (parameters.useRotatingProxies()) {
            innerLoop();
        }
        else {
            innerLoopStep();
        }

        log("Ende outerLoopStepNumber = " + outerLoopStepNumber);
    }

    /** Der innere Loop probiert bei Misserfolg alle Proxies einmal durch. */
    private void innerLoop() {
        log("Start outerLoopStepNumber = " + outerLoopStepNumber);
        innerLoopStepNumber = 0;

        RotatingProxies rotatingProxies = parameters.getRotatingProxies();
        int numberOfProxies = rotatingProxies.getNumberOfProxies();
        log("numberOfProxies = " + numberOfProxies);
        Retry retry = new Retry(this::innerLoopStep, numberOfProxies, 0L);
        retry.tryAndTry();
        if (!retry.isSucessfullyDone()) {
            throw new RuntimeException("inner loop not successfull");
        }
        log("Ende outerLoopStepNumber = " + outerLoopStepNumber);
    }

    private void innerLoopStep() {
        ++innerLoopStepNumber;
        log("Start outerLoopStepNumber = " + outerLoopStepNumber
                + ", innerLoopStepNumber = " + innerLoopStepNumber);

        if (parameters.useRotatingProxies()) {
            String proxy = parameters.getRotatingProxies().getProxy().toString();
            log("rotating-proxies - proxy = " + proxy);
        }

        if (parameters.useTimedThreadRunner()) {
            log("TimedThread download");
            int secondsBeforeKill = parameters.getSecondsBeforeKill();
            long millisecondsBetweenWatching = parameters.getMillisecondsBetweenWatching();
            TimedThreadRunner runner = new TimedThreadRunner(secondsBeforeKill,
                    millisecondsBetweenWatching, () -> singleDownload());
            runner.runTask();
            TaskState state = runner.getTaskState();
            if (state == TaskState.FINISHED_IN_TIME) {
                log("Download finished in time!");
                // ... nichts zu tun
            }
            else {
                log("Download wurde abgebrochen!");
                downloadInfo.downloadCanceled();
            }
        }
        else {
            log("direct download");
            singleDownload();
        }

        log("Ende outerLoopStepNumber = " + outerLoopStepNumber
                + ", innerLoopStepNumber = " + innerLoopStepNumber);
    }

    /** Versucht einmal die Seite herunterzuladen. */
    private void singleDownload() {
        log("Start");

        HttpClientCreator creator = new HttpClientCreator(parameters);
        try (CloseableHttpClient httpClient = creator.createClosableHttpClient()) {
            HttpGetter httpGetter = new HttpGetter(parameters, downloadInfo);
            httpGetter.loadHttpGet(httpClient);

            log("Ende");
        }
        catch (IOException exception) {
            log("Fehler beim Download: " + exception.getMessage());
        }
    }

    /** Loggt die übergebene Nachricht. */
    private void log(String message) {
        if (parameters.weHaveALogger()) {
            Logger logger = parameters.getLogger();
            logger.log(message, 1);
        }
    }

}
