package ctbrec.recorder.postprocessing;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Objects;

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

import ctbrec.Config;
import ctbrec.OS;
import ctbrec.Recording;
import ctbrec.io.IoUtils;
import ctbrec.io.StreamRedirectThread;
import ctbrec.recorder.RecordingManager;
import ctbrec.recorder.download.ProcessExitedUncleanException;

public class Remux extends AbstractPostProcessor {

    private static final Logger LOG = LoggerFactory.getLogger(Remux.class);

    public static final String FFMPEG_ARGS =  "ffmpeg.args";
    public static final String FILE_EXT =  "file.ext";

    @Override
    public String getName() {
        return "remux / transcode";
    }

    @Override
    public void postprocess(Recording rec, RecordingManager recordingManager, Config config) throws IOException, InterruptedException {
        String fileExt = getConfig().get(FILE_EXT);
        String[] args = getConfig().get(FFMPEG_ARGS).split(" ");
        String[] argsPlusFile = new String[args.length + 3];
        File inputFile = rec.getPostProcessedFile();
        if (inputFile.isDirectory()) {
            inputFile = new File(inputFile, "playlist.m3u8");
        }
        int i = 0;
        argsPlusFile[i++] = "-i";
        argsPlusFile[i++] = inputFile.getCanonicalPath();
        System.arraycopy(args, 0, argsPlusFile, i, args.length);
        File remuxedFile = new File(rec.getPostProcessedFile().getAbsolutePath() + '.' + fileExt);
        argsPlusFile[argsPlusFile.length - 1] = remuxedFile.getAbsolutePath();
        String[] cmdline = OS.getFFmpegCommand(argsPlusFile);
        File executionDir = rec.getPostProcessedFile().isDirectory() ? rec.getPostProcessedFile() : rec.getPostProcessedFile().getParentFile();
        LOG.info("Executing {} in working directory {}", Arrays.toString(cmdline), executionDir);
        Process ffmpeg = Runtime.getRuntime().exec(cmdline, new String[0], executionDir);
        setupLogging(ffmpeg, rec);
        rec.setPostProcessedFile(remuxedFile);
        if (inputFile.getName().equals("playlist.m3u8")) {
            IoUtils.deleteDirectory(inputFile.getParentFile());
            if (Objects.equals(inputFile.getParentFile(), rec.getAbsoluteFile())) {
                rec.setAbsoluteFile(remuxedFile);
            }
        } else {
            Files.deleteIfExists(inputFile.toPath());
            if (Objects.equals(inputFile, rec.getAbsoluteFile())) {
                rec.setAbsoluteFile(remuxedFile);
            }
        }
        rec.setSingleFile(true);
        rec.setSizeInByte(remuxedFile.length());
        IoUtils.deleteEmptyParents(inputFile.getParentFile());
        rec.getAssociatedFiles().remove(inputFile.getCanonicalPath());
        rec.getAssociatedFiles().add(remuxedFile.getCanonicalPath());
    }

    private void setupLogging(Process ffmpeg, Recording rec) throws IOException, InterruptedException {
        int exitCode = 1;
        File video = rec.getPostProcessedFile();
        File ffmpegLog = new File(video.getParentFile(), video.getName() + ".ffmpeg.log");
        rec.getAssociatedFiles().add(ffmpegLog.getCanonicalPath());
        try (FileOutputStream mergeLogStream = new FileOutputStream(ffmpegLog)) {
            Thread stdout = new Thread(new StreamRedirectThread(ffmpeg.getInputStream(), mergeLogStream));
            Thread stderr = new Thread(new StreamRedirectThread(ffmpeg.getErrorStream(), mergeLogStream));
            stdout.start();
            stderr.start();
            exitCode = ffmpeg.waitFor();
            LOG.debug("FFmpeg exited with code {}", exitCode);
            stdout.join();
            stderr.join();
            mergeLogStream.flush();
        }
        if (exitCode != 1) {
            if (ffmpegLog.exists()) {
                Files.delete(ffmpegLog.toPath());
                rec.getAssociatedFiles().remove(ffmpegLog.getCanonicalPath());
            }
        } else {
            rec.getAssociatedFiles().add(ffmpegLog.getAbsolutePath());
            LOG.info("FFmpeg exit code was {}. Logfile: {}", exitCode, ffmpegLog.getAbsolutePath());
            throw new ProcessExitedUncleanException("FFmpeg exit code was " + exitCode);
        }
    }

    @Override
    public String toString() {
        String s = getName();
        if(getConfig().containsKey(FFMPEG_ARGS)) {
            s += " [" + getConfig().get(FFMPEG_ARGS) + ']';
        }
        return s;
    }
}
