package net.pms.dlna;

import java.io.File;
import java.util.StringTokenizer;

import net.pms.configuration.FormatConfiguration;
import net.pms.PMS;
import net.pms.formats.Format;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MediaInfoParser {
	private static final Logger logger = LoggerFactory.getLogger(MediaInfoParser.class);
	private static Base64 base64;
	private static MediaInfo gMI;
	private static boolean mthread=PMS.rz_minfo_multi;

	static {
		if(!mthread) {
			gMI = new MediaInfo();
			if (gMI.isValid()) {
				gMI.Option("Complete", "1");
				gMI.Option("Language", "raw");
			}
		}
		base64 = new Base64();
	}

	public static boolean isValid() {
		if(mthread) return true;
		return gMI.isValid();
	}

	public static void close() {
		if(mthread) return;
		try {
			gMI.finalize();
		} catch (Throwable e) {
		}
	}

	public static void parse(DLNAMediaInfo media, InputFile file, int type) {
		if(mthread) {
			//use work instance of Mediainfo.dll: can be executed on multi-threading
			parse_in(media, file, type);
		}
		else {
			//use global instanse of Mediainfo.dll : must be executed serially
			parse_sync(media, file, type);
		}
	}
	
	public synchronized static void parse_sync(DLNAMediaInfo media, InputFile file, int type) {
		parse_in(media, file, type);
	}
		
	public static void parse_in(DLNAMediaInfo media, InputFile file, int type) {
		
		//regzam, for multi-threading
		MediaInfo MI;
		File filename = file.getFile();
		
		if(mthread) {
			MI = new MediaInfo();
			if (MI.isValid()) {
				MI.Option("Complete", "1");
				MI.Option("Language", "raw");
			}
		}
		else {
			
			MI=gMI;
		}
		//regzam, for multi-threading

		if(PMS.rz_debug>1) PMS.dbg("MediainfoParser.parse_in: Start file="+filename.getName()+", mthread="+mthread);
		if (!media.isMediaparsed() && filename != null && MI.isValid() && MI.Open(filename.getAbsolutePath()) > 0) {
			media.setThumbready(false); //regzam
			try {
				String info = MI.Inform();
				MediaInfo.StreamKind step = MediaInfo.StreamKind.General;
				DLNAMediaAudio currentAudioTrack = new DLNAMediaAudio();
				boolean audioPrepped = false;
				DLNAMediaSubtitle currentSubTrack = new DLNAMediaSubtitle();
				boolean subPrepped = false;
				if(PMS.rz_debug>1) {
					PMS.dbg("==== MediaInfoParser: Dump info_list, filename="+filename);
				}
				if (StringUtils.isNotBlank(info)) {
					media.setSize(filename.length());
					StringTokenizer st = new StringTokenizer(info, "\n\r");
					int cnt=0;
					while (st.hasMoreTokens()) {
						String line = st.nextToken().trim();
						if(PMS.rz_debug>1) {
							cnt++;
							PMS.dbg("["+cnt+"]"+line);	//regamod
						}
						if (line.equals("Video") || line.startsWith("Video #")) {
							step = MediaInfo.StreamKind.Video;
						} else if (line.equals("Audio") || line.startsWith("Audio #")) {
							if (audioPrepped) {
								addAudio(currentAudioTrack, media);
								currentAudioTrack = new DLNAMediaAudio();
							}
							audioPrepped = true;
							step = MediaInfo.StreamKind.Audio;
						} else if (line.equals("Text") || line.startsWith("Text #")) {
							if (subPrepped) {
								addSub(currentSubTrack, media);
								currentSubTrack = new DLNAMediaSubtitle();
							}
							subPrepped = true;
							step = MediaInfo.StreamKind.Text;
						} else if (line.equals("Menu") || line.startsWith("Menu #")) {
							step = MediaInfo.StreamKind.Menu;
						} else if (line.equals("Chapters")) {
							step = MediaInfo.StreamKind.Chapters;
						}
						int point = line.indexOf(":");
						if (point > -1) {
							String key = line.substring(0, point).trim();
							String ovalue = line.substring(point + 1).trim();
							String value = ovalue.toLowerCase();
							if (key.equals("Format") || key.startsWith("Format_Version") || key.startsWith("Format_Profile")) {
								getFormat(step, media, currentAudioTrack, value,1,key);
							} else if (key.equals("Duration/String1") && step == MediaInfo.StreamKind.General) {
								media.setDuration(getDuration(value));
							} else if (key.equals("Codec_Settings_QPel") && step == MediaInfo.StreamKind.Video) {
								media.putExtra(FormatConfiguration.MI_QPEL, value);
							} else if (key.equals("Codec_Settings_GMC") && step == MediaInfo.StreamKind.Video) {
								media.putExtra(FormatConfiguration.MI_GMC, value);
							} else if (key.equals("MuxingMode") && step == MediaInfo.StreamKind.Video) {
								media.setMuxingMode(ovalue);
							} else if (key.equals("Codec")) {
								getFormat(step, media, currentAudioTrack, value,2,key);
							} else if (key.equals("CodecID")) {
								if (step == MediaInfo.StreamKind.Text) {
									getSubCodec(currentSubTrack, value);
								} 
								else {
									getFormat(step, media, currentAudioTrack, value,3,key);
								}
							} else if (key.equals("Language/String")) {
								if (step == MediaInfo.StreamKind.Audio) {
									currentAudioTrack.setLang(getLang(value));
								} else if (step == MediaInfo.StreamKind.Text) {
									currentSubTrack.setLang(getLang(value));
								}
							} else if (key.equals("Title")) {
								if (step == MediaInfo.StreamKind.Audio) {
									currentAudioTrack.setFlavor(getFlavor(value));
								} else if (step == MediaInfo.StreamKind.Text) {
									currentSubTrack.setFlavor(getFlavor(value));
								}
							} else if (key.equals("Width")) {
								media.setWidth(getPixelValue(value));
							} else if (key.equals("Encryption") && !media.isEncrypted()) {
								media.setEncrypted("encrypted".equals(value));
							} else if (key.equals("Height")) {
								media.setHeight(getPixelValue(value));
							} else if (key.equals("FrameRate")) {
								media.setFrameRate(getFPSValue(value));
							
							} else if (key.equals("DisplayAspectRatio")) {	//regzamod, add
								String asp_str=getAspectValue(value);
								media.setAspect(asp_str);
								media.setAspect_f(Float.parseFloat(asp_str));
								
							} else if (key.equals("OverallBitRate")) {
								if (step == MediaInfo.StreamKind.General) {
									media.setBitrate(getBitrate(value));
									//PMS.dbg("MediainfoParesr: setBitrate="+media.getBitrate());
								}
							} else if (key.equals("Channel(s)")) {
								if (step == MediaInfo.StreamKind.Audio) {
									currentAudioTrack.setNrAudioChannels(getNbChannels(value));
								}
							} else if (key.equals("SamplingRate")) {
								if (step == MediaInfo.StreamKind.Audio) {
									currentAudioTrack.setSampleFrequency(getSampleFrequency(value));
								}
							} else if (key.equals("ID/String")) {
								// Special check for OGM: MediaInfo reports specific Audio/Subs IDs (0xn) while mencoder does not
								if (value.contains("(0x") && !FormatConfiguration.OGG.equals(media.getContainer())) {
									if (step == MediaInfo.StreamKind.Audio) {
										currentAudioTrack.setId(getSpecificID(value));
									} else if (step == MediaInfo.StreamKind.Text) {
										currentSubTrack.setId(getSpecificID(value));
									}
								} else {
									if (step == MediaInfo.StreamKind.Audio) {
										currentAudioTrack.setId(media.getAudioCodes().size());
										if(false) {	//why , regzamod
											if (media.getContainer() != null && (media.getContainer().equals(FormatConfiguration.AVI) || media.getContainer().equals(FormatConfiguration.FLV) || media.getContainer().equals(FormatConfiguration.MOV) || media.getContainer().equals(FormatConfiguration.MP4))) {
												currentAudioTrack.setId(currentAudioTrack.getId() + 1);
											}
										}
									} else if (step == MediaInfo.StreamKind.Text) {
										currentSubTrack.setId(media.getSubtitlesCodes().size());
									}
								}
							} else if (key.equals("Cover_Data") && step == MediaInfo.StreamKind.General) {
								media.setThumb(getCover(ovalue));
							} else if (key.equals("Track") && step == MediaInfo.StreamKind.General) {
								currentAudioTrack.setSongname(ovalue);
							} else if (key.equals("Album") && step == MediaInfo.StreamKind.General) {
								currentAudioTrack.setAlbum(ovalue);
							} else if (key.equals("Performer") && step == MediaInfo.StreamKind.General) {
								currentAudioTrack.setArtist(ovalue);
							} else if (key.equals("Genre") && step == MediaInfo.StreamKind.General) {
								currentAudioTrack.setGenre(ovalue);
							} else if (key.equals("Recorded_Date") && step == MediaInfo.StreamKind.General) {
								try {
									currentAudioTrack.setYear(Integer.parseInt(value));
								} catch (NumberFormatException nfe) {
								}
							} else if (key.equals("Track/Position") && step == MediaInfo.StreamKind.General) {
								try {
									currentAudioTrack.setTrack(Integer.parseInt(value));
								} catch (NumberFormatException nfe) {
								}
							} else if (key.equals("Resolution") && step == MediaInfo.StreamKind.Audio) {
								try {
									currentAudioTrack.setBitsperSample(Integer.parseInt(value));
								} catch (NumberFormatException nfe) {
								}
							} else if (key.equals("Video_Delay") && step == MediaInfo.StreamKind.Audio) {
								try {
									currentAudioTrack.setDelay(Integer.parseInt(value));
								} catch (NumberFormatException nfe) {
								}
							}
						}
					}
				}
				if (audioPrepped) {
					addAudio(currentAudioTrack, media);
				}
				if (subPrepped) {
					addSub(currentSubTrack, media);
				}
				media.finalize(type, file);
			} catch (Exception e) {
				logger.error("file="+filename+", Error in MediaInfo parsing:", e);
			} finally {
				if(mthread) {
					MI.Close();
					try {
						MI.finalize();
					} catch (Throwable e) {
					}
				}
				if (media.getContainer() == null) {
					media.setContainer(DLNAMediaLang.UND);
				}
				if (media.getCodecV() == null) {
					//media.setCodecV(DLNAMediaLang.UND); //regzam, shouldn't set UND
				}
				media.setMediaparsed(true);
			}
			if(type== Format.VIDEO && media.getDurationInSeconds() == 0) {  //regzamod
				if(media.getBitrate()>0) {
					if(PMS.rz_debug>2) PMS.dbg("seems failed to get duration normally, let's 2nd way");
					media.setDuration(((double)media.getSize())/media.getBitrate()*8.0);
				}
			}

		}
		else {
			PMS.dbg("MediainfoParser.parse: Error, file="+filename.getName());
			//PMS.dbg("  media.isMediaparsed()="+media.isMediaparsed());
			//PMS.dbg("  MI.isValid()="+MI.isValid());
			//PMS.dbg("  MI.Open(filename.getAbsolutePath())="+MI.Open(filename.getAbsolutePath()));
		}
		//PMS.dbg("MediainfoParser.parse: End file="+filename.getName());
	}

	public static void addAudio(DLNAMediaAudio currentAudioTrack, DLNAMediaInfo media) {
		if (currentAudioTrack.getLang() == null) {
			currentAudioTrack.setLang(DLNAMediaLang.UND);
		}
		if (currentAudioTrack.getCodecA() == null) {
			//currentAudioTrack.setCodecA(DLNAMediaLang.UND);  //regzam, shouldn't set UND
		}
		if (currentAudioTrack.getNrAudioChannels() == 0) {
			currentAudioTrack.setNrAudioChannels(2); //stereo by default
		}
		media.getAudioCodes().add(currentAudioTrack);
	}

	public static void addSub(DLNAMediaSubtitle currentSubTrack, DLNAMediaInfo media) {
		if (currentSubTrack.getType() == -1) {
			return;
		}
		if (currentSubTrack.getLang() == null) {
			currentSubTrack.setLang(DLNAMediaLang.UND);
		}
		if (currentSubTrack.getType() == 0) {
			currentSubTrack.setType(DLNAMediaSubtitle.EMBEDDED);
		}
		media.getSubtitlesCodes().add(currentSubTrack);
	}

	public static void getFormat (MediaInfo.StreamKind current, DLNAMediaInfo media, DLNAMediaAudio audio, 
		String value, int tagknd, String tag) {
		//PMS.dbg("getFormat: StreamKind="+current+", tagKind="+tagknd+", tag="+tag+", value="+value);
		
		//pre check
		if (current == MediaInfo.StreamKind.General) {
			if(media.getContainer()!=null) return;
		} else if (current == MediaInfo.StreamKind.Video) {
			String cv=media.getCodecV();
			if(cv!=null && tag.equals("Format_Profile")) {
				cv=cv+","+value;
				media.setCodecV(cv);
				//PMS.dbg("MediaInfoParser: added Format_Profile_value, vcodec="+cv);
				return;
			}
			if(media.getCodecV()!=null) return;
		} else if (current == MediaInfo.StreamKind.Audio) {
			//if(audio.getCodecA()!=null) return;
			//--> can't classify mpeg audios subtypes? (mp1,mp2,mp3 etc)
		}
		
		String format = null;
		if (value.equals("matroska")) {
			format = FormatConfiguration.MATROSKA;
		} else if (value.equals("avi") || value.equals("opendml")) {
			format = FormatConfiguration.AVI;
		} else if (value.startsWith("flash")) {
			format = FormatConfiguration.FLV;
		} else if (value.equals("qt") || value.equals("quicktime")) {
			format = FormatConfiguration.MOV;
		} else if (value.equals("isom") || value.startsWith("mp4") || value.equals("20") || value.equals("m4v") || value.startsWith("mpeg-4")) {
			format = FormatConfiguration.MP4;
		} else if (value.contains("mpeg-ps")) {
			format = FormatConfiguration.MPEGPS;
		} else if (value.contains("mpeg-ts") || value.equals("bdav")) {
			format = FormatConfiguration.MPEGTS;
		} else if (value.contains("aiff")) {
			format = FormatConfiguration.AIFF;
		} else if (value.contains("ogg")) {
			format = FormatConfiguration.OGG;
		} else if (value.contains("realmedia") || value.startsWith("rv") || value.startsWith("cook")) {
			format = FormatConfiguration.RM;
		} else if (value.contains("windows media") || value.equals("wmv1") || value.equals("wmv2") || value.equals("wmv7") || value.equals("wmv8")) {
			format = FormatConfiguration.WMV;
		} else if (value.contains("mjpg") || value.contains("m-jpeg")) {
			format = FormatConfiguration.MJPEG;
		} else if (value.startsWith("avc") || value.contains("h264")) {
			format = FormatConfiguration.H264;
		} else if (value.contains("xvid")) {
			format = FormatConfiguration.MP4;
		} else if (value.contains("mjpg") || value.contains("m-jpeg")) {
			format = FormatConfiguration.MJPEG;
		} else if (value.contains("div") || value.contains("dx")) {
			format = FormatConfiguration.DIVX;
		} else if (value.contains("dv") && !value.equals("dvr")) {
			format = FormatConfiguration.DV;
		} else if (value.contains("mpeg video")) {
			format = FormatConfiguration.MPEG2;
		} else if (value.equals("vc-1") || value.equals("vc1") || value.equals("wvc1") || value.equals("wmv3") || value.equals("wmv9") || value.equals("wmva")) {
			format = FormatConfiguration.VC1;
		} else if (value.equals("version 1")) {
			if (media.getCodecV() != null && media.getCodecV().equals(FormatConfiguration.MPEG2) && audio.getCodecA() == null) {
				format = FormatConfiguration.MPEG1;
			}
		} else if (value.equals("layer 3")) {
			if (audio.getCodecA() != null && audio.getCodecA().equals(FormatConfiguration.MPA)) {
				format = FormatConfiguration.MP3;
				// special case:
				if (media.getContainer() != null && media.getContainer().equals(FormatConfiguration.MPA)) {
					media.setContainer(FormatConfiguration.MP3);
				}
			}
		} else if (value.equals("ma")) {
			if (audio.getCodecA() != null && audio.getCodecA().equals(FormatConfiguration.DTS)) {
				format = FormatConfiguration.DTSHD;
			}
		} else if (value.equals("vorbis") || value.equals("a_vorbis")) {
			//format = FormatConfiguration.OGG;   //origin
			format = FormatConfiguration.VORBIS;  //regzam
		} else if (value.equals("ac-3") || value.equals("a_ac3") || value.equals("2000")) {
			format = FormatConfiguration.AC3;
		} else if (value.equals("e-ac-3")) {
			format = FormatConfiguration.EAC3;
		} else if (value.contains("truehd")) {
			format = FormatConfiguration.TRUEHD;
		} else if (value.equals("55") || value.equals("a_mpeg/l3")||value.equals("mpa1l3")) {
			format = FormatConfiguration.MP3;
		} else if (value.equals("m4a") || value.equals("40") || value.equals("a_aac") || value.equals("aac")) {
			format = FormatConfiguration.AAC;
		} else if (value.equals("pcm") || (value.equals("1") && (audio.getCodecA() == null || !audio.getCodecA().equals(FormatConfiguration.DTS)))) {
			format = FormatConfiguration.LPCM;
		} else if (value.equals("alac")) {
			format = FormatConfiguration.ALAC;
		} else if (value.equals("wave")) {
			format = FormatConfiguration.WAV;
		} else if (value.equals("shorten")) {
			format = FormatConfiguration.SHORTEN;
		} else if (value.equals("dts") || value.equals("a_dts") || value.equals("8")) {
			format = FormatConfiguration.DTS;
		} else if (value.equals("mpeg audio")) {
			format = FormatConfiguration.MPA;
		} else if (value.equals("161") || value.startsWith("wma")) {
			format = FormatConfiguration.WMA;
			if (media.getCodecV() == null) {
				media.setContainer(FormatConfiguration.WMA);
			}
		} else if (value.equals("flac")) {
			format = FormatConfiguration.FLAC;
		} else if (value.equals("monkey's audio")) {
			format = FormatConfiguration.APE;
		} else if (value.contains("musepack")) {
			format = FormatConfiguration.MPC;
		} else if (value.contains("wavpack")) {
			format = FormatConfiguration.WAVPACK;
		} else if (value.contains("mlp")) {
			format = FormatConfiguration.MLP;
		} else if (value.contains("atrac3")) {
			format = FormatConfiguration.ATRAC;
			if (media.getCodecV() == null) {
				media.setContainer(FormatConfiguration.ATRAC);
			}
		} else if (value.equals("jpeg")) {
			format = FormatConfiguration.JPG;
		} else if (value.equals("png")) {
			format = FormatConfiguration.PNG;
		} else if (value.equals("gif")) {
			format = FormatConfiguration.GIF;
		} else if (value.equals("bitmap")) {
			format = FormatConfiguration.BMP;
		} else if (value.equals("tiff")) {
			format = FormatConfiguration.TIFF;
		}
		
		if(format==null) {
			//value is not pre-supposed above, but is better than null
			format=value;
		}
		
		if (format != null) { 
			if (current == MediaInfo.StreamKind.General) {
				//PMS.dbg("==> getFormat: setContainer="+format);
				media.setContainer(format);
			} else if (current == MediaInfo.StreamKind.Video) {
				//PMS.dbg("==> getFormat: setCodecV="+format);
				media.setCodecV(format);
			} else if (current == MediaInfo.StreamKind.Audio) {
				//PMS.dbg("==> getFormat: setCodecA="+format);
				audio.setCodecA(format);
			}
		}
	}

	public static void getSubCodec(DLNAMediaSubtitle subt, String value) {
		if (value.equals("s_text/ass") || value.equals("s_text/ssa")) {
			subt.setType(DLNAMediaSubtitle.ASS);
		} else if (value.equals("pgs")) {
			subt.setType(-1); // PGS not yet supported
		} else if (value.equals("s_text/utf8")) {
			subt.setType(DLNAMediaSubtitle.EMBEDDED);
			subt.setFileUtf8(true);
		}
	}

	public static int getPixelValue(String value) {
		if (value.indexOf("pixel") > -1) {
			value = value.substring(0, value.indexOf("pixel"));
		}
		value = value.trim();

		// Value can look like "512 / 512" at this point
		if (value.contains("/")) {
			value = value.substring(0, value.indexOf("/")).trim();
		}

		int pixels = Integer.parseInt(value);
		return pixels;
	}

	public static int getBitrate(String value) {
		if (value.contains("/")) {
			value = value.substring(0, value.indexOf("/")).trim();
		}
		try {
			int br=Integer.parseInt(value);
			return br;
		} catch(NumberFormatException e) {
			logger.warn("Illegal Bitrate: may be VBR");
			return 0;
		}
		//return Integer.parseInt(value);
	}

	public static int getNbChannels(String value) {
		if (value.indexOf("channel") > -1) {
			value = value.substring(0, value.indexOf("channel"));
		}
		value = value.trim();

		// Audio is DTS-ES (6.1 channels) but MEncoder only supports either 2, 4, 6 or 8 for channel values so we use 8
		if (value.equals("7 / 6")) {
			value = "8";
		}

		try {
			int channels = Integer.parseInt(value);
			return channels;
		} catch(NumberFormatException e) {
			logger.warn("Unknown number of audio channels detected. Using 6.");
			return 6;
		}
	}

	public static int getSpecificID(String value) {
		if (value.indexOf("(0x") > -1) {
			value = value.substring(0, value.indexOf("(0x"));
		}
		value = value.trim();
		int id = Integer.parseInt(value);
		return id;
	}

	public static String getSampleFrequency(String value) {
		if (value.indexOf("khz") > -1) {
			value = value.substring(0, value.indexOf("khz"));
		}
		value = value.trim();
		return value;
	}

	public static String getFPSValue(String value) {
		if (value.indexOf("fps") > -1) {
			value = value.substring(0, value.indexOf("fps"));
		}
		value = value.trim();
		return value;
	}
	public static String getAspectValue(String value) {
		if (value.indexOf(": ") > -1) {
			value = value.substring(value.indexOf(": "));
		}
		value = value.trim();
		return value;
	}

	public static String getLang(String value) {
		if (value.indexOf("(") > -1) {
			value = value.substring(0, value.indexOf("("));
		}
		if (value.indexOf("/") > -1) {
			value = value.substring(0, value.indexOf("/"));
		}
		value = value.trim();
		return value;
	}

	public static String getFlavor(String value) {
		value = value.trim();
		return value;
	}

	private static double getDuration(String value) {
		int h = 0, m = 0, s = 0;
		StringTokenizer st = new StringTokenizer(value, " ");
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			int hl = token.indexOf("h");
			if (hl > -1) {
				h = Integer.parseInt(token.substring(0, hl).trim());
			}
			int mnl = token.indexOf("mn");
			if (mnl > -1) {
				m = Integer.parseInt(token.substring(0, mnl).trim());
			}
			int msl = token.indexOf("ms");
			if (msl > -1) {
				// no need
			} else {
				int sl = token.indexOf("s");
				if (sl > -1) {
					s = Integer.parseInt(token.substring(0, sl).trim());
				}
			}
		}
		return (h * 3600) + (m * 60) + s;
	}

	public static byte[] getCover(String based64Value) {
		try {
			if (base64 != null) {
				return base64.decode(based64Value.getBytes());
			}
		} catch (Exception e) {
			logger.error("Error in decoding thumbnail data", e);
		}
		return null;
	}
}
