package net.pms.configuration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import net.pms.dlna.DLNAMediaAudio;
import net.pms.dlna.DLNAMediaInfo;
import net.pms.dlna.DLNAResource;
import net.pms.dlna.InputFile;
import net.pms.dlna.MediaInfoParser;
import net.pms.formats.Format;
import net.pms.PMS;

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

public class FormatConfiguration {
	private static final Logger logger = LoggerFactory.getLogger(FormatConfiguration.class);
	public static final String MPEGPS = "mpegps";
	public static final String MPEGTS = "mpegts";
	public static final String WMV = "wmv";
	public static final String AVI = "avi";
	public static final String MP4 = "mp4";
	public static final String MOV = "mov";
	public static final String FLV = "flv";
	public static final String RM = "rm";
	public static final String MATROSKA = "mkv";
	public static final String WAV = "wav";
	public static final String WAVPACK = "wavpack";
	public static final String LPCM = "lpcm";
	public static final String AAC = "aac";
	public static final String AC3 = "ac3";
	public static final String MP3 = "mp3";
	public static final String SHORTEN = "shn";
	public static final String MPA = "mpa";

	public static final String OGG = "ogg";        //regzam, precisely ogg is't codec but container
	public static final String VORBIS = "vorbis";  //regzam, added for (ogg's)vorbis audio codec 

	public static final String WMA = "wma";
	public static final String ALAC = "alac";
	public static final String DTS = "dts";
	public static final String DTSHD = "dtshd";
	public static final String TRUEHD = "truehd";
	public static final String EAC3 = "eac3";
	public static final String ATRAC = "atrac";
	public static final String FLAC = "flac";
	public static final String APE = "ape";
	public static final String RA = "ra";
	public static final String MPC = "mpc";
	public static final String AIFF = "aiff";
	public static final String DV = "dv";
	public static final String MPEG1 = "mpeg1";
	public static final String MPEG2 = "mpeg2";
	public static final String DIVX = "divx";
	public static final String H264 = "h264";
	public static final String MLP = "mlp";
	public static final String MJPEG = "mjpeg";
	public static final String VC1 = "vc1";
	public static final String JPG = "jpg";
	public static final String PNG = "png";
	public static final String GIF = "gif";
	public static final String TIFF = "tiff";
	public static final String BMP = "bmp";
	public static final String und = "und";
	public static final String MI_QPEL = "qpel";
	public static final String MI_GMC = "gmc";

	// Use old parser for Jpeg files (MediaInfo does not support EXIF)
	private static final String PARSER_V1_EXTENSIONS[] = new String[]{".jpg", ".jpe", ".jpeg"};

	public void parse(DLNAMediaInfo media, InputFile file, Format ext, int type, DLNAResource dlna) {
		boolean force_v1 = false;
		
		if(PMS.rz_debug>1) PMS.dbg("FormatConfiguration.parse: start, type="+type+"(IMAGE="+Format.IMAGE+"VIDEO="+Format.VIDEO+")" );
		if (file.getFile() != null) {
			String fName = file.getFile().getName().toLowerCase();
			for (String e : PARSER_V1_EXTENSIONS) {
				if (fName.endsWith(e)) {
					force_v1 = true;
					break;
				}
			}
			if (force_v1) {
				if(PMS.rz_debug>1) PMS.dbg("FormatConfiguration.parse: type1 exec DLNAMediaInfo.parse(), fname="+fName);
				media.parse(file, ext, type, false, dlna);
			} else {
				if(PMS.rz_debug>1) PMS.dbg("FormatConfiguration.parse: type2 exec MediaInfoParser.parse(), fname="+fName);
				MediaInfoParser.parse(media, file, type);
			}
		} else {
			if(PMS.rz_debug>1) PMS.dbg("FormatConfiguration.parse: type3 exec media.parse(), file="+file);
			media.parse(file, ext, type, false,dlna);
		}
	}

	public static final String MIMETYPE_AUTO = "MIMETYPE_AUTO";
	ArrayList<Supports> list;

	class Supports {
		String format;
		String videocodec;
		String audiocodec;
		String mimetype;
		String maxnbchannels;
		String maxfrequency;
		String maxbitrate;
		String maxvideowidth;
		String maxvideoheight;
		Map<String, Pattern> miExtras;
		Pattern pformat;
		Pattern pvideocodec;
		Pattern paudiocodec;
		int imaxnbchannels = Integer.MAX_VALUE;
		int imaxfrequency = Integer.MAX_VALUE;
		int imaxbitrate = Integer.MAX_VALUE;
		int imaxvideowidth = Integer.MAX_VALUE;
		int imaxvideoheight = Integer.MAX_VALUE;

		Supports() {
			mimetype = MIMETYPE_AUTO;
		}

		boolean isValid() {
			boolean v = format != null && format.length() > 0;
			if (v) {
				try {
					pformat = Pattern.compile(format);
				} catch (PatternSyntaxException pe) {
					logger.info("Couldn't resolve this pattern: " + format + " / " + pe.getMessage());
					v = false;
				}
				if (videocodec != null) {
					try {
						//pvideocodec = Pattern.compile(videocodec);
						pvideocodec = Pattern.compile(videocodec+".*"); //regzamod
					} catch (PatternSyntaxException pe) {
						logger.info("Couldn't resolve this pattern: " + videocodec + " / " + pe.getMessage());
						v = false;
					}
				}
				if (audiocodec != null) {
					try {
						//paudiocodec = Pattern.compile(audiocodec);
						paudiocodec = Pattern.compile(audiocodec+".*");  //regzamod
					} catch (PatternSyntaxException pe) {
						logger.info("Couldn't resolve this pattern: " + audiocodec + " / " + pe.getMessage());
						v = false;
					}
				}
				try {
					if (maxnbchannels != null) {
						imaxnbchannels = Integer.parseInt(maxnbchannels);
					}
				} catch (Exception e) {
					logger.info("Error in parsing number: " + maxnbchannels);
					v = false;
				}
				try {
					if (maxfrequency != null) {
						imaxfrequency = Integer.parseInt(maxfrequency);
					}
				} catch (Exception e) {
					logger.info("Error in parsing number: " + maxfrequency);
					v = false;
				}
				try {
					if (maxbitrate != null) {
						imaxbitrate = Integer.parseInt(maxbitrate);
					}
				} catch (Exception e) {
					logger.info("Error in parsing number: " + maxbitrate);
					v = false;
				}
				try {
					if (maxvideowidth != null) {
						imaxvideowidth = Integer.parseInt(maxvideowidth);
					}
				} catch (Exception e) {
					logger.info("Error in parsing number: " + maxvideowidth);
					v = false;
				}
				try {
					if (maxvideoheight != null) {
						imaxvideoheight = Integer.parseInt(maxvideoheight);
					}
				} catch (Exception e) {
					logger.info("Error in parsing number: " + maxvideoheight);
					v = false;
				}
			}
			return v;
		}

		public boolean match (String container, String videocodec, String audiocodec) {
			
			return match(container, videocodec, audiocodec, 0, 0, 0, 0, 0, null);
		}

		public boolean match (String format, String videocodec, String audiocodec, int nbAudioChannels, int frequency, int bitrate, int videowidth, int videoheight, Map<String, String> extras) {
			//PMS.dbg("FormatConf.match: target vcodec="+videocodec+", this.vcodec="+this.videocodec);

			boolean matched = false;
			
			if (format != null && !(matched = pformat.matcher(format).matches())) {
				//PMS.dbg("FormatConf.match: format unmatch, return false");
				return false;
			}

			//if (matched && videocodec != null && pvideocodec != null && !(matched = pvideocodec.matcher(videocodec).matches())) {
			//	return false;
			//}
			if (matched && videocodec != null && pvideocodec != null) {
				String[] vcodec=videocodec.split("\\s");
				//PMS.dbg("FormatConf.match: target vcodec="+videocodec+", vcodec.split="+vcodec[0]);
				if(!(matched = pvideocodec.matcher(vcodec[0]).matches())) {
					//PMS.dbg("FormatConf.match: vcodec unmatch, return false");
					return false;
				}
			}

			//if (matched && audiocodec != null && paudiocodec != null && !(matched = paudiocodec.matcher(audiocodec).matches())) {
			//	return false;
			//}
			if (matched && audiocodec != null && paudiocodec != null) {
				String[] acodec=audiocodec.split("\\s");
				//PMS.dbg("FormatConf.match: target acodec="+audiocodec+", acodec.split="+acodec[0]);
				if(!(matched = paudiocodec.matcher(acodec[0]).matches())) {
					//PMS.dbg("FormatConf.match: acodec unmatch, return false");
					return false;
				}
			}

			if (matched && nbAudioChannels > 0 && imaxnbchannels > 0 && nbAudioChannels > imaxnbchannels) {
				return false;
			}

			if (matched && frequency > 0 && imaxfrequency > 0 && frequency > imaxfrequency) {
				return false;
			}

			if (matched && bitrate > 0 && imaxbitrate > 0 && bitrate > imaxbitrate) {
				return false;
			}

			if (matched && videowidth > 0 && imaxvideowidth > 0 && videowidth > imaxvideowidth) {
				return false;
			}

			if (matched && videoheight > 0 && imaxvideoheight > 0 && videoheight > imaxvideoheight) {
				return false;
			}

			if (matched && extras != null && miExtras != null) {
				Iterator<String> keyIt = extras.keySet().iterator();
				while (keyIt.hasNext()) {
					String key = keyIt.next();
					String value = extras.get(key);
					if (matched && key.equals(MI_QPEL) && miExtras.get(MI_QPEL) != null) {
						matched = miExtras.get(MI_QPEL).matcher(value).matches();
					} else if (matched && key.equals(MI_GMC) && miExtras.get(MI_GMC) != null) {
						matched = miExtras.get(MI_GMC).matcher(value).matches();
					}
				}
			}

			return matched;
		}
	}

	public boolean isDVDVideoRemuxSupported() {
		return match(MPEGPS, MPEG2, null) != null;
	}

	public boolean isFormatSupported(String container) {
		return match(container, null, null) != null;
	}

	public boolean isDTSSupported() {
		return match(MPEGPS, null, DTS) != null || match(MPEGTS, null, DTS) != null;
	}

	public boolean isLPCMSupported() {
		return match(MPEGPS, null, LPCM) != null || match(MPEGTS, null, LPCM) != null;
	}

	public boolean isMpeg2Supported() {
		return match(MPEGPS, MPEG2, null) != null || match(MPEGTS, MPEG2, null) != null;
	}

	public boolean isHiFiMusicFileSupported() {
		return match(WAV, null, null, 0, 96000, 0, 0, 0, null) != null || match(MP3, null, null, 0, 96000, 0, 0, 0, null) != null;
	}

	public String getPrimaryVideoTranscoder() {
		for (Supports conf : list) {
			if (conf.match(MPEGPS, MPEG2, AC3)) {
				return MPEGPS;
			}
			if (conf.match(MPEGTS, MPEG2, AC3)) {
				return MPEGTS;
			}
			if (conf.match(WMV, WMV, WMA)) {
				return WMV;
			}
		}
		return null;
	}

	public String getPrimaryAudioTranscoder() {
		for (Supports conf : list) {
			if (conf.match(WAV, null, null)) {
				return WAV;
			}
			if (conf.match(MP3, null, null)) {
				return MP3;
			}
		}
		return null;
	}

	public FormatConfiguration(List<?> supported) {
		list = new ArrayList<Supports>();
		for (Object line : supported) {
			if (line != null) {
				Supports conf = parseSupportLine(line.toString());
				if (conf.isValid()) {
					list.add(conf);
				} else {
					logger.info("Invalid configuration line: " + line);
				}
			}
		}
	}
	public FormatConfiguration() {  //regzam
	}

	/**
	 * Match a media information to available audio codes for the renderer
	 * and return its mimetype if the match is successful. Returns null if
	 * the media is not natively supported by the renderer, which means it
	 * has to be transcoded.
	 * @param media The media information
	 * @return The mimetype or null if no match was found.
	 */
	public String match (DLNAMediaInfo media) {
		if(media==null) return null;
		if (media.getFirstAudioTrack() == null) {
			// no sound
			return match(media.getContainer(), media.getCodecV(), null, 0, 0, media.getBitrate(), media.getWidth(), media.getHeight(), media.getExtras());
		} else {
			String finalMimeType = null;
			String vc,ac;
			for (DLNAMediaAudio audio : media.getAudioCodes()) {
				//vc=media.getCodecV();   //return strict name
				//ac=audio.getCodecA();   //return strict name
				vc=media.getVideoCodec();	//return nominal name
				ac=audio.getAudioCodec();	//return nominal name
				
				String mimeType = match(media.getContainer(), vc, ac, audio.getNrAudioChannels(), audio.getSampleRate(), media.getBitrate(), media.getWidth(), media.getHeight(), media.getExtras());
				finalMimeType = mimeType;
				if (mimeType == null) // if at least one audio track is not compatible, the file must be transcoded.
				{
					return null;
				}
			}
			return finalMimeType;
		}
	}

	public String match (String container, String videocodec, String audiocodec) {
		return match(container, videocodec, audiocodec, 0, 0, 0, 0, 0, null);
	}

	public String match (String container, String videocodec, String audiocodec, int nbAudioChannels, int frequency, int bitrate, int videowidth, int videoheight, Map<String, String> extras) {
		String matchedMimeType = null;

		for (Supports conf : list) {
			if (conf.match(container, videocodec, audiocodec, nbAudioChannels, frequency, bitrate, videowidth, videoheight, extras)) {
				matchedMimeType = conf.mimetype;
				break;
			}
		}

		return matchedMimeType;
	}

	private Supports parseSupportLine(String line) {
		StringTokenizer st = new StringTokenizer(line, "\t ");
		Supports conf = new Supports();
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			if (token.startsWith("f:")) {
				conf.format = token.substring(2).trim();
			} else if (token.startsWith("v:")) {
				conf.videocodec = token.substring(2).trim();
			} else if (token.startsWith("a:")) {
				conf.audiocodec = token.substring(2).trim();
			} else if (token.startsWith("n:")) {
				conf.maxnbchannels = token.substring(2).trim();
			} else if (token.startsWith("s:")) {
				conf.maxfrequency = token.substring(2).trim();
			} else if (token.startsWith("w:")) {
				conf.maxvideowidth = token.substring(2).trim();
			} else if (token.startsWith("h:")) {
				conf.maxvideoheight = token.substring(2).trim();
			} else if (token.startsWith("m:")) {
				conf.mimetype = token.substring(2).trim();
			} else if (token.startsWith("b:")) {
				conf.maxbitrate = token.substring(2).trim();
			} else if (token.contains(":")) {
				// extra mediainfo stuff
				if (conf.miExtras == null) {
					conf.miExtras = new HashMap<String, Pattern>();
				}
				String key = token.substring(0, token.indexOf(":"));
				String value = token.substring(token.indexOf(":") + 1);
				conf.miExtras.put(key, Pattern.compile(value));
			}
		}
		return conf;
	}
}
