package ctbrec.recorder.download.hls;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.Instant;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.regex.Pattern;

import javax.xml.bind.JAXBException;

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

import com.iheartradio.m3u8.ParseException;
import com.iheartradio.m3u8.PlaylistException;

import ctbrec.Config;
import ctbrec.Model;
import ctbrec.OS;
import ctbrec.Recording;
import ctbrec.io.HttpClient;
import ctbrec.io.StreamRedirector;
import ctbrec.recorder.download.ProcessExitedUncleanException;

/**
 * Does the whole HLS download with FFmpeg. Not used at the moment, because FFMpeg can't
 * handle the HLS encryption of Flirt4Free correctly
 */
public class FFmpegDownload extends AbstractHlsDownload {
    private static final transient Logger LOG = LoggerFactory.getLogger(FFmpegDownload.class);

    private transient Process ffmpeg;
    private File targetFile;

    public FFmpegDownload(HttpClient client) {
        super(client);
    }

    @Override
    public void init(Config config, Model model, Instant startTime, ExecutorService executorService) throws IOException {
        super.init(config, model, startTime, executorService);
        String suffix = config.getSettings().ffmpegFileSuffix;
        targetFile = config.getFileForRecording(model, suffix, startTime);
    }

    @Override
    public FFmpegDownload call() throws IOException {
        try {
            Files.createDirectories(targetFile.getParentFile().toPath());
            String chunkPlaylist = getSegmentPlaylistUrl(model);

            String[] args = config.getSettings().ffmpegMergedDownloadArgs.split(" ");
            String[] argsPlusFile = new String[args.length + 5];
            int i = 0;
            argsPlusFile[i++] = "-headers";
            argsPlusFile[i++] = "User-Agent: " + config.getSettings().httpUserAgent;
            argsPlusFile[i++] = "-i";
            argsPlusFile[i++] = chunkPlaylist;
            System.arraycopy(args, 0, argsPlusFile, i, args.length);
            argsPlusFile[argsPlusFile.length-1] = targetFile.getAbsolutePath();
            String[] cmdline = OS.getFFmpegCommand(argsPlusFile);
            LOG.debug("Command line: {}", Arrays.toString(cmdline));
            ffmpeg = Runtime.getRuntime().exec(cmdline, new String[0], targetFile.getParentFile());
            int exitCode = 1;
            File ffmpegLog = File.createTempFile(targetFile.getName(), ".log");
            ffmpegLog.deleteOnExit();
            try (FileOutputStream mergeLogStream = new FileOutputStream(ffmpegLog)) {
                Thread stdout = new Thread(new StreamRedirector(ffmpeg.getInputStream(), mergeLogStream));
                Thread stderr = new Thread(new StreamRedirector(ffmpeg.getErrorStream(), mergeLogStream));
                stdout.start();
                stderr.start();
                exitCode = ffmpeg.waitFor();
                stdout.join();
                stderr.join();
                mergeLogStream.flush();
            }
            if (exitCode == 0) {
                if (ffmpegLog.exists()) {
                    Files.delete(ffmpegLog.toPath());
                }
            } else {
                LOG.info("FFmpeg exit code was {}. Logfile: {}", exitCode, ffmpegLog.getAbsolutePath());
                throw new ProcessExitedUncleanException("FFmpeg exit code was " + exitCode);
            }
        } catch (InterruptedException e) {
            LOG.error("Thread interrupted while waiting for FFmpeg to terminate");
            Thread.currentThread().interrupt();
        } catch (ExecutionException | ParseException | PlaylistException | JAXBException e) {
            LOG.error("Couldn't start FFmpeg process for stream download", e);
        }
        return this;
    }

    @Override
    public void stop() {
        if (ffmpeg != null && ffmpeg.isAlive()) {
            OutputStream ffmpegStdIn = ffmpeg.getOutputStream();
            try {
                ffmpegStdIn.write("q".getBytes(StandardCharsets.UTF_8));
                ffmpegStdIn.flush();
                ffmpegStdIn.close();
            } catch (IOException e) {
                LOG.error("Couldn't terminate FFmpeg by sending 'q'", e);
                ffmpeg.destroy();
            }
        }
    }

    @Override
    public Model getModel() {
        return model;
    }

    @Override
    public void postprocess(Recording recording) {
        // nothing to here for now
    }

    @Override
    public File getTarget() {
        return targetFile;
    }

    @Override
    public String getPath(Model model) {
        String absolutePath = getTarget().getAbsolutePath();
        String recordingsDir = Config.getInstance().getSettings().recordingsDir;
        String relativePath = absolutePath.replaceFirst(Pattern.quote(recordingsDir), "");
        return relativePath;
    }

    @Override
    protected void internalStop() {
        stop();
    }

    @Override
    public boolean isSingleFile() {
        return true;
    }

    @Override
    public long getSizeInByte() {
        return getTarget().length();
    }

    protected void createTargetDirectory() throws IOException {
        // TODO Auto-generated method stub

    }

    @Override
    protected void execute(SegmentDownload segmentDownload) {
        // TODO Auto-generated method stub

    }

    @Override
    protected OutputStream getSegmentOutputStream(String prefix, String fileName) throws IOException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void finalizeDownload() {
        // TODO Auto-generated method stub

    }

}
