package saccubus.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * ffmpegから動画情報を取得するためのユーティリティクラス.
 * マルチスレッドで使用されることは想定していません.
 * @author yuki
 */
public final class FfmpegUtil {

    private static final Logger logger = LoggerFactory.getLogger(FfmpegUtil.class);
    private static final Pattern PATTERN_DURATION = Pattern.compile("Duration: (\\d+):(\\d+):(\\d+)");
    private static final Pattern PATTERN_SIZE = Pattern.compile("Video: .+?(\\d+)x(\\d+)");
    private final File ffmpegPath;
    private final File targetPath;
    private String execResultCache;

    public FfmpegUtil(File ffmpeg, File target) {
        this.ffmpegPath = ffmpeg;
        this.targetPath = target;
    }

    public int getDuration() throws IOException {
        final String res = getExecResult();
        final Matcher m = PATTERN_DURATION.matcher(res);
        if (m.find()) {
            final int hour = Integer.parseInt(m.group(1));
            final int min = Integer.parseInt(m.group(2));
            final int sec = Integer.parseInt(m.group(3));
            return ((hour * 60) + min) * 60 + sec;
        } else {
            throw new IOException("Can Not Get Duration");
        }
    }

    /**
     * 動画ファイルのサイズを取得します.
     * ただし, ffmpegのバージョンに依存するため正常に動作しない場合があります.
     * @return 動画サイズ.
     * @throws IOException  取得失敗.
     */
    public saccubus.util.Size getSize() throws IOException {
        // TODO ffmpegのバージョンによってはうまく動かない.
        final String res = getExecResult();
        final Matcher m = PATTERN_SIZE.matcher(res);
        if (m.find()) {
            final int width = Integer.parseInt(m.group(1));
            final int height = Integer.parseInt(m.group(2));
            final Size size = new Size(width, height);
            logger.debug(size.toString());
            return size;
        } else {
            throw new IOException("Can Not Get Size");
        }
    }

    public saccubus.util.Size adjustSize(int maxWidth, int maxHeight) throws IOException {
        final saccubus.util.Size nowSize = getSize();

        if (nowSize.getWidth() <= maxWidth && nowSize.getHeight() <= maxHeight) {
            return nowSize;
        }

        double widthRatio = calcRatio(nowSize.getWidth(), maxWidth);
        double heightRatio = calcRatio(nowSize.getHeight(), maxHeight);

        if (widthRatio >= heightRatio) {
            int adjustedWidth = adjust(nowSize.getWidth(), widthRatio);
            int adjustedHeight = adjust(nowSize.getHeight(), widthRatio);
            return new Size(adjustedWidth, adjustedHeight);
        } else {
            int adjustedWidth = adjust(nowSize.getWidth(), heightRatio);
            int adjustedHeight = adjust(nowSize.getHeight(), heightRatio);
            return new Size(adjustedWidth, adjustedHeight);
        }
    }

    private static double calcRatio(int now, int max) {
        return (double) now / (double) max;
    }

    private static int adjust(int size, double ratio) {
        return (int) (size / ratio);
    }

    private String getExecResult() throws IOException {
        if (execResultCache != null) {
            return execResultCache;
        } else {
            final ProcessBuilder pb = new ProcessBuilder(ffmpegPath.getPath(), "-i", targetPath.getPath());
            final Process process = pb.start();
            final InputStream es = process.getErrorStream();
            final BufferedReader reader = new BufferedReader(new InputStreamReader(es));
            final StringBuilder sb = new StringBuilder();
            String str;
            while ((str = reader.readLine()) != null) {
                sb.append(str);
            }
            final String res = sb.toString();
            logger.debug(res);
            execResultCache = res;
            return res;
        }
    }

    private static class Size implements saccubus.util.Size {

        private final int width;
        private final int height;

        public Size(int width, int height) {
            this.width = width;
            this.height = height;
        }

        @Override
        public int getHeight() {
            return height;
        }

        @Override
        public int getWidth() {
            return width;
        }

        @Override
        public String toString() {
            return "Width:" + width + ", Height:" + height;
        }
    }
}
