package dareka.processor.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import dareka.common.CloseUtil;
import dareka.common.Logger;
import dareka.common.TextUtil;
import dareka.processor.HttpHeader;
import dareka.processor.Resource;
import dareka.processor.StringResource;
import dareka.processor.TemporaryFileResource;
import dareka.processor.impl.Cache;

public class AudioExtractor extends Resource {
	Cache cache;
	File audioFile;
	String requestName, userAgent;
	String cachePath, audioPath, contentType;
	
	public AudioExtractor(Cache cache, String requestName, String userAgent) {
		this.cache = cache;
		this.requestName = requestName;
		this.userAgent = userAgent;
	}
	
	private void info(String message) {
		Logger.info("AudioExtractor: " + message);
	}
	
	public Resource extract() {
		if (cache.exists()) {
			String requestExt = getExt(requestName);
			String cacheExt = cache.getPostfix();
			info(cache.getId() + " " + cache.getTitle() + requestExt);
			if (useFFmpeg(cacheExt)) { // FFmpegがあれば優先
				return ffmpeg(requestExt);
			}
			if (requestExt.equals(".mp3")) {
				if (cacheExt.equals(Cache.FLV)) {
					return new Flv2mp3(cache);
				} else if (cacheExt.equals(Cache.SWF)) {
					return swfextract();
				}
			} else if (requestExt.equals(".m4a")) {
				if (cacheExt.equals(Cache.MP4)) {
					return mp4box();
				}
			}
			info("Cannot extract from '"+requestExt+"' to '"+cacheExt+"'.");
		}
		return StringResource.getNotFound();
	}
	
	private void setupExecCommand(String ext, String contentType) {
		String tmpdir = System.getProperty("java.io.tmpdir");
		int i = 0;
		do {
			audioFile = new File(tmpdir, "nlAudioExtractor" + (++i) + ext);
		} while (audioFile.exists());
		this.cachePath = cache.getCacheFile().getAbsolutePath();
		this.audioPath = audioFile.getAbsolutePath();
		this.contentType = contentType;
	}
	
	private boolean useFFmpeg(String cacheExt) {
		String useFFmpeg = System.getProperty("useFFmpeg");
		if (useFFmpeg == null) {
			String[] args = { "ffmpeg", "-version" };
			useFFmpeg = execCommand(args, false) == null ? "false" : "true";
			System.setProperty("useFFmpeg", useFFmpeg); 
		}
		// ID3コメントの扱いに非互換があるのでオプション指定でFLVを除外
		if (cacheExt.equals(Cache.FLV) && useFFmpeg.equalsIgnoreCase("noflv")) {
			return false;
		}
		return !useFFmpeg.equalsIgnoreCase("false");
	}
	
	private Resource ffmpeg(String requestExt) {
		setupExecCommand(requestExt, "audio/" + requestExt.substring(1));
		String[] args = {
				"ffmpeg", "-i", cachePath, "-vn", "-acodec", "copy",
				"-id3v2_version", "3", // ID3v2.3
				"-metadata", "title=" + cache.getTitle(),
				"-metadata", "comment=" + cache.getId(), // ID3v2:COMMタグ非対応
				audioPath };
		execCommand(args);
		return createAudioResource();
	}
	
	private static final Pattern MP4BOX_TRACK_ID_PATTERN = Pattern.compile(
			"Track #\\s*(\\d+) Info - TrackID\\s*(\\d+)");
	
	private Resource mp4box() {
		// MP4Boxでは出力ファイルに拡張子不要
		setupExecCommand("", "audio/m4a");
		
		// 先にAudioトラックを特定する
		String[] preArgs = { "MP4Box", cachePath, "-info" };
		BufferedReader preInputReader = execCommand(preArgs);
		if (preInputReader == null)
			return StringResource.getNotFound();
		String tmpTrackNum = "2";
		String trackNum = "2";
		String infoLine = "";
		try {
			while((infoLine = preInputReader.readLine()) != null) {
				Matcher m = MP4BOX_TRACK_ID_PATTERN.matcher(infoLine);
				if (m.lookingAt()) {
					tmpTrackNum = m.group(2);
				} else if (infoLine.indexOf("Audio Stream") != -1) {
					trackNum = tmpTrackNum;
				}
			}
		} catch (IOException e) {}
		String[] args = { "MP4Box", cachePath, "-single", trackNum, "-out", audioPath };
		execCommand(args);
		
		// [GUI] 少し古いMP4Boxは出力ファイル名が違うため、そのファイルも確認する
		audioFile = new File(audioPath + ".mp4");
		if (!audioFile.exists()) {
			audioFile = new File(audioPath + "_track" + trackNum + ".mp4");
		}
		audioPath = audioFile.getAbsolutePath();
		
		return createAudioResource();
	}
	
	private Resource swfextract() {
		setupExecCommand(".mp3", "audio/mp3");
		String[] args = { "swfextract", "-m", cachePath, "-o", audioPath };
		execCommand(args);
		return createAudioResource();
	}
	
	private BufferedReader execCommand(String[] args) {
		return execCommand(args, true);
	}
	
	private BufferedReader execCommand(String[] args, boolean showError) {
		StringBuilder sb = new StringBuilder();
//		for (String s : args) {
//			if (sb.length() > 0) sb.append(" ");
//			if (s.contains(" ")) sb.append('"');
//			sb.append(s);
//			if (s.contains(" ")) sb.append('"');
//		}
//		String commandLine = sb.toString();
//		sb.delete(0, sb.length());
// TODO ソース整理
		String commandLine = TextUtil.join(args, " ");
		Process proc = null;
		BufferedReader in = null;
		try {
//			proc = Runtime.getRuntime().exec(args);
			ProcessBuilder pb = new ProcessBuilder(args);
			pb.redirectErrorStream(true);
			proc = pb.start();
			in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
			String line;
			while((line = in.readLine()) != null) {
				sb.append(line);
				sb.append('\n');
			}
			proc.waitFor();
		} catch (IOException e) {
			Logger.debug(commandLine + " [FAILED]");
			if (showError) {
				info("Needs `" + args[0] + "' command to extract audio.");
			}
			return null;
		} catch (InterruptedException e) {
			Logger.error(e);
		} finally {
			if (proc != null) {
				CloseUtil.close(in);
				CloseUtil.close(proc.getOutputStream());
				CloseUtil.close(proc.getErrorStream());
				proc.destroy();
			}
		}
		Logger.debug(commandLine);
		String commandOut = sb.toString();
		Logger.debug(commandOut);
		return new BufferedReader(new StringReader(commandOut));
	}
	
	private Resource createAudioResource() {
		if (audioFile.length() > 0) {
			try {
				Resource r = new TemporaryFileResource(audioFile);
				r.setResponseHeader(HttpHeader.CONTENT_TYPE, contentType);
				setContentDisposition(r, cache, userAgent, requestName);
				return r; // SUCCESS
			} catch (IOException e) {
				Logger.error(e);
			}
		} else {
			info("Cannot extract audio.");
		}
		audioFile.delete();
		return StringResource.getNotFound();
	}
	
	public static String getExt(String path) {
		int pos = path.lastIndexOf('.');
		return (pos != -1) ? path.substring(pos).toLowerCase() : "";
	}
	
	public static void setContentDisposition(Resource r, Cache cache, String userAgent, String downloadName) {
		String ext = getExt(downloadName);
		// ダウンロード時に動画タイトルをつける
		String charset = null;
		if (Boolean.getBoolean("useDownloadCacheName") && userAgent != null) {
			if (userAgent.contains("MSIE") || userAgent.contains("Chrome")) {
				charset = "MS932";
				if (ext.equals(".mp3")) {
//Chromeだとmp3は勝手に拡張子つけるので、なしにしないとダブる
					downloadName = cache.getTitle();
					if (userAgent.contains("MSIE")) {
						downloadName += ext;
					}
				} else {
					downloadName = cache.getTitle() + ext;
				}
			} else if (!userAgent.contains("Safari")) {
				charset = "UTF-8";
				downloadName = cache.getTitle() + ext;
			}
		}
		r.setResponseHeaderOnCharset("Content-Disposition",
							"attachment; filename=\"" + downloadName + "\"", charset);
	}
}