package ctbrec.recorder.download.hls;

import static ctbrec.recorder.download.hls.AbstractHlsDownload.*;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.URL;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.Callable;

import javax.crypto.NoSuchPaddingException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ctbrec.Model;
import ctbrec.io.BandwidthMeter;
import ctbrec.io.HttpClient;
import ctbrec.io.HttpException;
import ctbrec.recorder.download.HttpHeaderFactory;
import okhttp3.Request;
import okhttp3.Request.Builder;
import okhttp3.Response;

public class SegmentDownload implements Callable<SegmentDownload> {
    private static final Logger LOG = LoggerFactory.getLogger(SegmentDownload.class);

    private URL url;
    private HttpClient client;
    private SegmentPlaylist playlist;
    private Model model;
    private OutputStream out;

    public SegmentDownload(Model model, SegmentPlaylist playlist, URL url, HttpClient client, OutputStream out) {
        this.model = model;
        this.playlist = playlist;
        this.url = url;
        this.client = client;
        this.out = out;
    }

    @Override
    public SegmentDownload call() {
        for (int tries = 1; tries <= 3 && !Thread.currentThread().isInterrupted(); tries++) {
            Request request = createRequest();
            try (Response response = client.execute(request)) {
                handleResponse(response);
                break;
            } catch (FileNotFoundException e) {
                LOG.debug("Segment does not exist {}", url.getFile());
                break;
            } catch (InterruptedIOException e) {
                break;
            } catch (Exception e) {
                if (tries == 3) {
                    LOG.warn("Error while downloading segment for {}. Segment {} finally failed: {}", model, url.getFile(), e.getMessage());
                } else {
                    LOG.debug("Error while downloading segment for {} on try {} - {}", model, tries, e.getMessage());
                }
            }
        }
        return this;
    }

    private boolean handleResponse(Response response) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IOException {
        if (response.isSuccessful()) {
            InputStream in = response.body().byteStream();
            if (playlist.encrypted) {
                in = new Crypto(playlist.encryptionKeyUrl, client).wrap(in);
            }
            byte[] b = new byte[1024 * 100];
            int length = -1;
            while ((length = in.read(b)) >= 0 && !Thread.currentThread().isInterrupted()) {
                out.write(b, 0, length);
                BandwidthMeter.add(length);
            }
            out.flush();
            return true;
        } else {
            throw new HttpException(response.code(), response.message());
        }
    }

    private Request createRequest() {
        Builder builder = new Request.Builder().url(url);
        addHeaders(builder, Optional.ofNullable(model).map(Model::getHttpHeaderFactory).map(HttpHeaderFactory::createSegmentHeaders).orElse(new HashMap<>()), model);
        return builder.build();
    }

    public OutputStream getOutputStream() {
        return out;
    }

    public URL getUrl() {
        return url;
    }
}