/*
 * MediathekView
 * Copyright (C) 2014 W. Xaver
 * W.Xaver[at]googlemail.com
 * http://zdfmediathk.sourceforge.net/
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package mediathek.controller.starter;

import mSearch.tool.Listener;
import mSearch.tool.Log;
import mSearch.tool.SysMsg;
import mediathek.config.Daten;
import mediathek.config.MVConfig;
import mediathek.controller.MVBandwidthTokenBucket;
import mediathek.controller.MVInputStream;
import mediathek.daten.DatenDownload;
import mediathek.gui.dialog.DialogContinueDownload;
import mediathek.gui.dialog.MeldungDownloadfehler;
import mediathek.tool.MVInfoFile;
import mediathek.tool.MVSubtitle;

import javax.swing.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;

import static mediathek.controller.starter.StarterClass.*;

public class DirectHttpDownload extends Thread {

    private final Daten daten;
    private final DatenDownload datenDownload;
    private final Start start;
    private HttpURLConnection conn = null;
    private HttpDownloadState state = HttpDownloadState.DOWNLOAD;
    private long downloaded = 0;
    private File file = null;
    private String responseCode;
    private String exMessage;

    private FileOutputStream fos = null;

    private final java.util.Timer bandwidthCalculationTimer;
    private boolean retAbbrechen;
    private boolean dialogAbbrechenIsVis;

    enum HttpDownloadState {

        CANCEL, ERROR, DOWNLOAD
    }

    public DirectHttpDownload(Daten daten, DatenDownload d, java.util.Timer bandwidthCalculationTimer) {
        super();
        this.daten = daten;
        this.bandwidthCalculationTimer = bandwidthCalculationTimer;
        datenDownload = d;
        start = datenDownload.start;
        setName("DIRECT DL THREAD_" + d.arr[DatenDownload.DOWNLOAD_TITEL]);
        start.status = Start.STATUS_RUN;
        notifyStartEvent(datenDownload);
    }

    /**
     * Return the content length of the requested Url.
     *
     * @param url {@link java.net.URL} to the specified content.
     * @return Length in bytes or -1 on error.
     */
    private long getContentLength(final URL url) {
        final int TIMEOUT_LENGTH = 5000; //ms, beim Start eines Downloads
        long ret = -1;
        HttpURLConnection connection = null;
        try {
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestProperty("User-Agent", Daten.getUserAgent());
            connection.setReadTimeout(TIMEOUT_LENGTH);
            connection.setConnectTimeout(TIMEOUT_LENGTH);
            if (connection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST) {
                ret = connection.getContentLengthLong();
            }
            // alles unter 300k sind Playlisten, ...
            if (ret < 300 * 1000) {
                ret = -1;
            }
        } catch (Exception ex) {
            ret = -1;
            Log.errorLog(643298301, ex);
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
        return ret;
    }

    /**
     * Setup the HTTP connection common settings
     *
     * @param conn The active connection.
     */
    private void setupHttpConnection(HttpURLConnection conn) {
        conn.setRequestProperty("Range", "bytes=" + downloaded + '-');
        conn.setRequestProperty("User-Agent", Daten.getUserAgent());
        conn.setDoInput(true);
        conn.setDoOutput(true);
    }

    /**
     * Start the actual download process here.
     *
     * @throws Exception
     */
    private void downloadContent() throws Exception {
        if (Boolean.parseBoolean(datenDownload.arr[DatenDownload.DOWNLOAD_INFODATEI])) {
            MVInfoFile.writeInfoFile(datenDownload);
        }
        if (Boolean.parseBoolean(datenDownload.arr[DatenDownload.DOWNLOAD_SUBTITLE])) {
            MVSubtitle.writeSubtitle(datenDownload);
        }
        datenDownload.interruptRestart();

        start.mVInputStream = new MVInputStream(conn.getInputStream(), bandwidthCalculationTimer);

        fos = new FileOutputStream(file, (downloaded != 0));

        datenDownload.mVFilmSize.addAktSize(downloaded);
        final byte[] buffer = new byte[MVBandwidthTokenBucket.DEFAULT_BUFFER_SIZE];
        long p, pp = 0, startProzent = -1;
        int len;
        long aktBandwidth, aktSize = 0;
        boolean melden = false;

        while ((len = start.mVInputStream.read(buffer)) != -1 && (!start.stoppen)) {
            downloaded += len;
            fos.write(buffer, 0, len);
            datenDownload.mVFilmSize.addAktSize(len);

            //für die Anzeige prüfen ob sich was geändert hat
            if (aktSize != datenDownload.mVFilmSize.getAktSize()) {
                aktSize = datenDownload.mVFilmSize.getAktSize();
                melden = true;
            }
            if (datenDownload.mVFilmSize.getSize() > 0) {
                p = (aktSize * (long) 1000) / datenDownload.mVFilmSize.getSize();
                if (startProzent == -1) {
                    startProzent = p;
                }
                // p muss zwischen 1 und 999 liegen
                if (p == 0) {
                    p = Start.PROGRESS_GESTARTET;
                } else if (p >= 1000) {
                    p = 999;
                }
                start.percent = (int) p;
                if (p != pp) {
                    pp = p;
                    // Restzeit ermitteln
                    if (p > 2 && p > startProzent) {
                        // sonst macht es noch keinen Sinn
                        int diffZeit = start.startZeit.diffInSekunden();
                        int restProzent = 1000 - (int) p;
                        start.restSekunden = (diffZeit * restProzent / (p - startProzent));
                        // anfangen zum Schauen kann man, wenn die Restzeit kürzer ist
                        // als die bereits geladene Speilzeit des Films
                        bereitsAnschauen(datenDownload);
                    }
                    melden = true;
                }
            }
            aktBandwidth = start.mVInputStream.getBandwidth(); // bytes per second
            if (aktBandwidth != start.bandbreite) {
                start.bandbreite = aktBandwidth;
                melden = true;
            }
            if (melden) {
                Listener.notify(Listener.EREIGNIS_ART_DOWNLOAD_PROZENT, StarterClass.class.getName());
                melden = false;
            }
        }
        start.bandbreite = start.mVInputStream.getSumBandwidth();
        if (!start.stoppen) {
            if (datenDownload.quelle == DatenDownload.QUELLE_BUTTON) {
                // direkter Start mit dem Button
                start.status = Start.STATUS_FERTIG;
            } else if (pruefen(daten, datenDownload, start)) {
                //Anzeige ändern - fertig
                start.status = Start.STATUS_FERTIG;
            } else {
                //Anzeige ändern - bei Fehler fehlt der Eintrag
                start.status = Start.STATUS_ERR;
            }
        }
    }

    @Override
    public synchronized void run() {
        startmeldung(datenDownload, start);
        try {
            Files.createDirectories(Paths.get(datenDownload.arr[DatenDownload.DOWNLOAD_ZIEL_PFAD]));
        } catch (IOException ignored) {
        }

        int restartCount = 0;
        boolean restart = true;
        while (restart) {
            restart = false;
            try {
                final URL url = new URL(datenDownload.arr[DatenDownload.DOWNLOAD_URL]);
                file = new File(datenDownload.arr[DatenDownload.DOWNLOAD_ZIEL_PFAD_DATEINAME]);

                if (!cancelDownload()) {

                    datenDownload.mVFilmSize.setSize(getContentLength(url));
                    datenDownload.mVFilmSize.setAktSize(0);
                    conn = (HttpURLConnection) url.openConnection();
                    conn.setConnectTimeout(1000 * MVConfig.getInt(MVConfig.Configs.SYSTEM_PARAMETER_DOWNLOAD_TIMEOUT_SEKUNDEN));
                    conn.setReadTimeout(1000 * MVConfig.getInt(MVConfig.Configs.SYSTEM_PARAMETER_DOWNLOAD_TIMEOUT_SEKUNDEN));

                    setupHttpConnection(conn);
                    conn.connect();
                    final int httpResponseCode = conn.getResponseCode();
                    if (httpResponseCode >= HttpURLConnection.HTTP_BAD_REQUEST) {
                        //Range passt nicht, also neue Verbindung versuchen...
                        if (httpResponseCode == 416) {
                            conn.disconnect();
                            //Get a new connection and reset download param...
                            conn = (HttpURLConnection) url.openConnection();
                            downloaded = 0;
                            setupHttpConnection(conn);
                            conn.connect();
                            //hier war es dann nun wirklich...
                            state = conn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST ? HttpDownloadState.ERROR : HttpDownloadState.DOWNLOAD;
                        } else {
                            // ==================================
                            // dann wars das
                            responseCode = "Responsecode: " + conn.getResponseCode() + '\n' + conn.getResponseMessage();
                            Log.errorLog(915236798, "HTTP-Fehler: " + conn.getResponseCode() + ' ' + conn.getResponseMessage());
                            SwingUtilities.invokeLater(() -> {
                                if (!Daten.isAuto()) {
                                    new MeldungDownloadfehler(Daten.getInstance().getMediathekGui(), "URL des Films:\n"
                                            + datenDownload.arr[DatenDownload.DOWNLOAD_URL] + "\n\n"
                                            + responseCode + '\n', datenDownload).setVisible(true);
                                }
                            });
                            state = HttpDownloadState.ERROR;
                        }
                    }
                }
                switch (state) {
                    case DOWNLOAD:
                        downloadContent();
                        break;
                    case CANCEL:
//                        start.status = Start.STATUS_RUN; // bei "init" wird er sonst nochmal gestartet
                        break;
                    case ERROR:
                        start.status = Start.STATUS_ERR;
                        break;
                }
            } catch (Exception ex) {
                if ((ex instanceof java.io.IOException || ex instanceof java.net.SocketTimeoutException)
                        && restartCount < MVConfig.getInt(MVConfig.Configs.SYSTEM_PARAMETER_DOWNLOAD_MAX_RESTART_HTTP)) {

                    if (ex instanceof java.net.SocketTimeoutException) {
                        //Timeout Fehlermeldung für zxd :)
                        ArrayList<String> text = new ArrayList<>();
                        text.add("Timeout, Download Restarts: " + restartCount);
                        text.add("Ziel: " + datenDownload.arr[DatenDownload.DOWNLOAD_ZIEL_PFAD_DATEINAME]);
                        text.add("URL: " + datenDownload.arr[DatenDownload.DOWNLOAD_URL]);
                        SysMsg.sysMsg(text.toArray(new String[text.size()]));
                    }

                    restartCount++;
                    restart = true;
                } else {
                    // dann weiß der Geier!
                    exMessage = ex.getLocalizedMessage();
                    Log.errorLog(316598941, ex, "Fehler");
                    start.status = Start.STATUS_ERR;
                    SwingUtilities.invokeLater(() -> {
                        if (!Daten.isAuto()) {
                            new MeldungDownloadfehler(Daten.getInstance().getMediathekGui(), exMessage, datenDownload).setVisible(true);
                        }
                    });
                }
            }
        }

        try {
            if (start.mVInputStream != null) {
                start.mVInputStream.close();
            }
            if (fos != null) {
                fos.close();
            }
            if (conn != null) {
                conn.disconnect();
            }
        } catch (Exception ignored) {
        }

        StarterClass.finalizeDownload(datenDownload, start, state);
    }

    private boolean cancelDownload() {
        if (!file.exists()) {
            // dann ist alles OK
            return false;
        }
        if (Daten.isAuto()) {
            return false; // immer überschreiben, keine GUI!!!
        }

        dialogAbbrechenIsVis = true;
        retAbbrechen = true;
        if (SwingUtilities.isEventDispatchThread()) {
            retAbbrechen = abbrechen_();
        } else {
            SwingUtilities.invokeLater(() -> {
                retAbbrechen = abbrechen_();
                dialogAbbrechenIsVis = false;
            });
        }
        while (dialogAbbrechenIsVis) {
            try {
                wait(100);
            } catch (Exception ignored) {

            }
        }
        return retAbbrechen;
    }

    private boolean abbrechen_() {
        boolean result = false;
        if (file.exists()) {
            DialogContinueDownload dialogContinueDownload = new DialogContinueDownload(Daten.getInstance().getMediathekGui(), datenDownload, true /*weiterführen*/);
            dialogContinueDownload.setVisible(true);

            switch (dialogContinueDownload.getResult()) {
                case CANCELLED:
                    // dann wars das
                    state = HttpDownloadState.CANCEL;
                    result = true;
                    break;

                case CONTINUE:
                    downloaded = file.length();
                    break;

                case RESTART_WITH_NEW_NAME:
                    if (dialogContinueDownload.isNewName()) {
                        Listener.notify(Listener.EREIGNIS_LISTE_DOWNLOADS, this.getClass().getSimpleName());
                        try {
                            Files.createDirectories(Paths.get(datenDownload.arr[DatenDownload.DOWNLOAD_ZIEL_PFAD]));
                        } catch (IOException ignored) {
                        }
                        file = new File(datenDownload.arr[DatenDownload.DOWNLOAD_ZIEL_PFAD_DATEINAME]);
                    }
                    break;
            }
        }
        return result;
    }

    private void bereitsAnschauen(DatenDownload datenDownload) {
        if (datenDownload.film != null && datenDownload.start != null) {
            if (datenDownload.film.dauerL > 0
                    && datenDownload.start.restSekunden > 0
                    && datenDownload.mVFilmSize.getAktSize() > 0
                    && datenDownload.mVFilmSize.getSize() > 0) {
                // macht nur dann Sinn
                final long zeitGeladen = datenDownload.film.dauerL * datenDownload.mVFilmSize.getAktSize() / datenDownload.mVFilmSize.getSize();
                if (zeitGeladen > (datenDownload.start.restSekunden * 1.1 /* plus 10% zur Sicherheit*/)) {
                    datenDownload.start.beginnAnschauen = true;
                }
            }
        }
    }
}
