/*
 * PS3 Media Server, for streaming any medias to your PS3.
 * Copyright (C) 2008  A.Brochard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License only.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package net.pms.encoders;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

import javax.swing.JComponent;

import net.pms.configuration.PmsConfiguration;
import net.pms.configuration.RendererConfiguration;
import net.pms.dlna.DLNAMediaAudio;
import net.pms.dlna.DLNAMediaInfo;
import net.pms.dlna.DLNAMediaSubtitle;
import net.pms.dlna.DLNAResource;
import net.pms.external.ExternalFactory;
import net.pms.external.ExternalListener;
import net.pms.external.FinalizeTranscoderArgsListener;
import net.pms.formats.Format;
import net.pms.io.OutputParams;
import net.pms.io.ProcessWrapper;
import net.pms.util.FileUtil;
import net.pms.util.Iso639;
import net.pms.PMS;

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

public abstract class Player {
	private static final Logger logger = LoggerFactory.getLogger(Player.class);

	public static final int VIDEO_SIMPLEFILE_PLAYER = 0;
	public static final int AUDIO_SIMPLEFILE_PLAYER = 1;
	public static final int VIDEO_WEBSTREAM_PLAYER = 2;
	public static final int AUDIO_WEBSTREAM_PLAYER = 3;
	public static final int MISC_PLAYER = 4;
	//public static final int IMAGE_SIMPLEFILE_PLAYER = 5;	//regzamod, cause out_of_bound on transcodingTab
	public static final String NATIVE = "NATIVE";

	public abstract int purpose();
	public abstract JComponent config();
	public abstract String id();
	public abstract String name();
	public abstract int type();
	public abstract String[] args();
	public abstract String mimeType();
	public abstract String executable();
	private static List<FinalizeTranscoderArgsListener> finalizeTranscodeArgsListeners =
		new ArrayList<FinalizeTranscoderArgsListener>();

	public static void initializeFinalizeTranscoderArgsListeners() {
		for (ExternalListener listener : ExternalFactory.getExternalListeners()) {
			if (listener instanceof FinalizeTranscoderArgsListener) {
				finalizeTranscodeArgsListeners.add((FinalizeTranscoderArgsListener) listener);
			}
		}
	}

	public boolean avisynth() {
		return false;
	}

	public boolean excludeFormat(Format extension) {
		return false;
	}

	public boolean isPlayerCompatible(RendererConfiguration renderer) {
		return true;
	}

	public boolean isInternalSubtitlesSupported() {
		return true;
	}

	public boolean isExternalSubtitlesSupported() {
		return true;
	}

	public boolean isTimeSeekable() {
		return false;
	}

	public abstract ProcessWrapper launchTranscode(
		String filename,
		DLNAResource dlna,
		DLNAMediaInfo media,
		OutputParams params
	) throws IOException;

	@Override
	public String toString() {
		return name();
	}

	protected String[] finalizeTranscoderArgs(
		Player player,
		String filename,
		DLNAResource dlna,
		DLNAMediaInfo media,
		OutputParams params,
		String[] cmdArgs) {
		
		if (finalizeTranscodeArgsListeners.isEmpty()) {
			return cmdArgs;
		} else {
			// make it mutable
			List<String> cmdList = new ArrayList<String>(Arrays.asList(cmdArgs));

			for (FinalizeTranscoderArgsListener listener : finalizeTranscodeArgsListeners) {
				try {
					cmdList = listener.finalizeTranscoderArgs(
							player,
							filename,
							dlna,
							media,
							params,
							cmdList);
				} catch (Throwable t) {
					logger.error(String.format("Failed to call finalizeTranscoderArgs on listener of type=%s", listener.getClass()), t);
				}
			}

			String[] cmdArray = new String[cmdList.size()];
			cmdList.toArray(cmdArray);
			return cmdArray;
		}
	}

	public void setAudioAndSubs(String fileName, DLNAResource dlna, DLNAMediaInfo media, OutputParams params, PmsConfiguration configuration) {
		
		//PMS.dbg("setAudioAndSubs: ---- Phase.1 search [audio lang] -------------");
		if (params.aid == null && media != null) {
			// check for preferred audio
			StringTokenizer st = new StringTokenizer(configuration.getMencoderAudioLanguages(), ",");
			while (st != null && st.hasMoreTokens()) {
				String lang = st.nextToken();
				lang = lang.trim();
				//PMS.dbg("Looking for an audio track with lang: " + lang);
				logger.trace("Looking for an audio track with lang: " + lang);
				for (DLNAMediaAudio audio : media.getAudioCodes()) {
					if (audio.matchCode(lang)) {
						params.aid = audio;
						logger.trace("Matched audio track: " + audio);
						st = null;
						break;
					}
				}
			}
		}
		//PMS.dbg("setAudioAndSubs: ---- Phase.1 result audio="+params.aid);

		if (params.aid == null && media != null && media.getAudioCodes().size() > 0) {
			//PMS.dbg("setAudioAndSubs: ---- Phase.2 (get DTS audio, if not desided on Phase1) -------------");
			// take a default audio track, dts first if possible
			for (DLNAMediaAudio audio : media.getAudioCodes()) {
				if (audio.isDTS()) {
					params.aid = audio;
					//PMS.dbg("Found priority audio track with DTS: " + audio);
					logger.trace("Found priority audio track with DTS: " + audio);
					break;
				}
			}

			if (params.aid == null) {
				//PMS.dbg("setAudioAndSubs: ---- Phase.2 (get first audio, if not found on Phase1 & not DTS) -------------");
				params.aid = media.getAudioCodes().get(0);
				//PMS.dbg("Choosed a default audio track: " + params.aid);
				logger.trace("Choosed a default audio track: " + params.aid);
			}
			//PMS.dbg("setAudioAndSubs: ---- Phase.2 result audio="+params.aid);
		}

		String currentLang = null;
		DLNAMediaSubtitle matchedSub = null;

		if (params.aid != null) {
			currentLang = params.aid.getLang();
		}

		if (params.sid != null && params.sid.getId() == -1) {
			//PMS.dbg("Don't want subtitles!");
			logger.trace("Don't want subtitles!");
			params.sid = null;
			return;
		}

		//PMS.dbg("setAudioAndSubs: ---- Phase.3 search [audio,subs] pair -------------");
		StringTokenizer st1 = new StringTokenizer(configuration.getMencoderAudioSubLanguages(), ";");

		while (st1.hasMoreTokens()) {
			String pair = st1.nextToken();
			if (pair.contains(",")) {
				String audio = pair.substring(0, pair.indexOf(","));
				String sub = pair.substring(pair.indexOf(",") + 1);
				audio = audio.trim();
				sub = sub.trim();
				//PMS.dbg("Search a match for: " + currentLang + " with " + audio + " and " + sub);
				logger.trace("Search a match for: " + currentLang + " with " + audio + " and " + sub);

				if (Iso639.isCodesMatching(audio, currentLang) || (currentLang != null && audio.equals("*"))) {
					if (sub.equals("off")) {
						matchedSub = new DLNAMediaSubtitle();
						matchedSub.setLang("off");
					} else {
						for (DLNAMediaSubtitle present_sub : media.getSubtitlesCodes()) {
							if (present_sub.matchCode(sub) || sub.equals("*")) {
								matchedSub = present_sub;
								//PMS.dbg(" Found a match: " + matchedSub);
								logger.trace(" Found a match: " + matchedSub);
								break;
							}
						}
					}

					if (matchedSub != null) {
						break;
					}
				}
			}
		}
		//PMS.dbg("setAudioAndSubs: ---- Phase.3 result matchedSub="+matchedSub);

		if (matchedSub != null && params.sid == null) {
			if (matchedSub.getLang() != null && matchedSub.getLang().equals("off")) {
				//PMS.dbg(" Disabled the subtitles: " + matchedSub);
				logger.trace(" Disabled the subtitles: " + matchedSub);
			} else {
				params.sid = matchedSub;
			}
		}

		if (!configuration.isMencoderDisableSubs() && params.sid == null && media != null) {
			//PMS.dbg("setAudioAndSubs: ---- Phase.5 params.sid not found yet -------------");
			// Check for subtitles again
			File video = new File(fileName);
			FileUtil.doesSubtitlesExists(video, media, false, dlna.getName0());

			if (configuration.getUseSubtitles()) {
				boolean forcedSubsFound = false;
				// Priority to external subtitles
				for (DLNAMediaSubtitle sub : media.getSubtitlesCodes()) {
					if (matchedSub !=null && matchedSub.getLang() !=null && matchedSub.getLang().equals("off")) {
						StringTokenizer st = new StringTokenizer(configuration.getMencoderForcedSubTags(), ",");
						while (st != null && sub.getFlavor() != null && st.hasMoreTokens()) {
							String forcedTags = st.nextToken();
							forcedTags = forcedTags.trim();
							if (sub.getFlavor().toLowerCase().indexOf(forcedTags) > -1) {
								if (Iso639.isCodesMatching(sub.getLang(),configuration.getMencoderForcedSubLanguage())) {
									//PMS.dbg("Forcing preferred subtitles : " + sub.getLang() + "/" + sub.getFlavor());
									logger.trace("Forcing preferred subtitles : " + sub.getLang() + "/" + sub.getFlavor());
									//PMS.dbg("Forced subtitles track : " + sub);
									logger.trace("Forced subtitles track : " + sub);
									if (sub.getFile() != null) {
										logger.trace("Found external forced file : " + sub.getFile().getAbsolutePath());
									}
									params.sid = sub;
									forcedSubsFound = true;
									break;
								}
							}
						}
						if (forcedSubsFound == true) break;
					} else {
							//PMS.dbg("Found subtitles track: " + sub);
							logger.trace("Found subtitles track: " + sub);
							if (sub.getFile() != null) {
								//PMS.dbg("Found external file: " + sub.getFile().getAbsolutePath());
								logger.trace("Found external file: " + sub.getFile().getAbsolutePath());
								params.sid = sub;
								break;
							}
					}
				}
			}
			if (
				matchedSub !=null && 
				matchedSub.getLang() !=null && 
				matchedSub.getLang().equals("off")
			) {
				return;
			}

			if (params.sid == null) {
				StringTokenizer st = new StringTokenizer(configuration.getMencoderSubLanguages(), ",");
				while (st != null && st.hasMoreTokens()) {
					String lang = st.nextToken();
					lang = lang.trim();
					//PMS.dbg("Looking for a subtitle track with lang: " + lang);
					logger.trace("Looking for a subtitle track with lang: " + lang);
					for (DLNAMediaSubtitle sub : media.getSubtitlesCodes()) {
						if (sub.matchCode(lang)) {
							params.sid = sub;
							//PMS.dbg("Matched sub track: " + params.sid);
							logger.trace("Matched sub track: " + params.sid);
							st = null;
							break;
						}
					}
				}
			}
		}
	}
}