/*
 * @(#)StreamParser
 *
 * Copyright (c) 2005 by dvb.matt, All rights reserved.
 * 
 * This file is part of ProjectX, a free Java based demux utility.
 * By the authors, ProjectX is intended for educational purposes only, 
 * as a non-commercial test project.
 * 
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package net.sourceforge.dvb.projectx.parser;

import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.PushbackInputStream;
import java.io.FileOutputStream;

import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.Hashtable;

import net.sourceforge.dvb.projectx.common.Common;
import net.sourceforge.dvb.projectx.common.Resource;
import net.sourceforge.dvb.projectx.common.Keys;
import net.sourceforge.dvb.projectx.common.JobCollection;
import net.sourceforge.dvb.projectx.common.JobProcessing;

import net.sourceforge.dvb.projectx.io.StandardBuffer;

import net.sourceforge.dvb.projectx.xinput.XInputFile;
import net.sourceforge.dvb.projectx.xinput.StreamInfo;

import net.sourceforge.dvb.projectx.parser.CommonParsing;
import net.sourceforge.dvb.projectx.parser.StreamConverter;
import net.sourceforge.dvb.projectx.parser.StreamDemultiplexer;
import net.sourceforge.dvb.projectx.parser.StreamParserBase;
import net.sourceforge.dvb.projectx.parser.StreamProcess;


/**
 * main thread
 */
public class StreamParserPESSecondary extends StreamParserBase {

	/**
	 * 
	 */
	public StreamParserPESSecondary()
	{
		super();
	}

	/**
	 * secondary PES Parser
	 */
	public String parseStream(JobCollection collection, XInputFile aXInputFile, int _pes_streamtype, int action, String vptslog)
	{
		String fchild = collection.getOutputName(aXInputFile.getName());
		String fparent = collection.getOutputNameParent(fchild);

		if (Common.getSettings().getBooleanProperty(Keys.KEY_ExternPanel_appendExtension))
			fparent = collection.getOutputDirectory() + collection.getFileSeparator() + fchild;

		JobProcessing job_processing = collection.getJobProcessing();

		/**
		 * split part 
		 */
		fparent += job_processing.getSplitSize() > 0 ? "(" + job_processing.getSplitPart() + ")" : "" ;

		String paname = fparent + ".ma1";

		List tempfiles = job_processing.getTemporaryFileList();

		if (!tempfiles.isEmpty())
		{
			for (int i = 0; i < tempfiles.size(); i += 4 )
			{
				if ( tempfiles.get(i + 1).toString().equals(aXInputFile.toString()) )
				{
					Common.renameTo(tempfiles.get(i).toString(), paname);

					tempfiles.set(i, paname);

					Common.setMessage(Resource.getString("parseSecondaryPES.continue") + " " + aXInputFile);

					String str = tempfiles.get(i + 3).toString();

					if (str.equals("tt")) //vtx
						new StreamProcess(CommonParsing.TELETEXT, collection, tempfiles.get(i).toString(), tempfiles.get(i + 2).toString(), tempfiles.get(i + 3).toString(), vptslog);

					else if (str.equals("sp")) //subpics
						new StreamProcess(CommonParsing.SUBPICTURE, collection, tempfiles.get(i).toString(), tempfiles.get(i + 2).toString(), tempfiles.get(i + 3).toString(), vptslog);

					else if (str.equals("pc")) //lpcm
						new StreamProcess(CommonParsing.LPCM_AUDIO, collection, tempfiles.get(i).toString(), tempfiles.get(i + 2).toString(), tempfiles.get(i + 3).toString(), vptslog);

					else //other audio
						new StreamProcess(CommonParsing.MPEG_AUDIO, collection, tempfiles.get(i).toString(), tempfiles.get(i + 2).toString(), tempfiles.get(i + 3).toString(), vptslog);

					return vptslog;
				}
			}
		}


		boolean Message_2 = Common.getSettings().getBooleanProperty(Keys.KEY_MessagePanel_Msg2);
		boolean Debug = collection.DebugMode();
		boolean SimpleMPG = Common.getSettings().getBooleanProperty(Keys.KEY_simpleMPG);
		boolean GetEnclosedPackets = Common.getSettings().getBooleanProperty(Keys.KEY_Input_getEnclosedPackets);
		boolean IgnoreScrambledPackets = Common.getSettings().getBooleanProperty(Keys.KEY_TS_ignoreScrambled);

		boolean isTeletext = false;
		boolean missing_startcode = false;
		boolean scrambling_messaged = false;
		boolean pes_isMpeg2;
		boolean pes_alignment;
		boolean pes_scrambled;
		boolean containsPts = false;
		boolean foundObject;

		int pes_streamtype;
		int pes_payloadlength;
		int pes_packetlength;
		int pes_extensionlength;
		int pes_headerlength = 9;
		int pes_packetoffset = 6;
		int pes_extension2_id;
		int pesID;
		int subID;
		int offset;
		int returncode = 0;

		byte[] pes_packet = new byte[0x10006];
		byte[] buffered_data;

		long count = 0;
		long size;
		long qexit;

		job_processing.clearStatusVariables();
		int[] clv = job_processing.getStatusVariables();

		job_processing.setMinBitrate(CommonParsing.MAX_BITRATE_VALUE);
		job_processing.setMaxBitrate(0);
		job_processing.setExportedVideoFrameNumber(0);
		job_processing.setEndPtsOfGop(-10000);
		job_processing.setSequenceHeader(true);
		job_processing.setAllMediaFilesExportLength(0);
		job_processing.setProjectFileExportLength(0);

		Hashtable substreams = new Hashtable();
		StandardBuffer sb;

		List demuxList = job_processing.getSecondaryPESDemuxList();

		demuxList.clear();

		StreamDemultiplexer streamdemultiplexer = null;

		try {

			PushbackInputStream in = new PushbackInputStream(aXInputFile.getInputStream(), pes_packet.length);

			size = aXInputFile.length();

			Common.updateProgressBar(Resource.getString("parseSecondaryPES.demux.pes") + " " + aXInputFile.getName(), 0, 0);

			qexit = count + (0x100000L * Integer.parseInt(Common.getSettings().getProperty(Keys.KEY_ExportPanel_Infoscan_Value)));

			bigloop:
			while (true)
			{
				loop:
				while (count < size)
				{
					pes_streamtype = _pes_streamtype;  // reset to original type

					Common.updateProgressBar(count, size);

					//yield();

					while (pause())
					{}

					if (CommonParsing.isProcessCancelled() || (CommonParsing.isInfoScan() && count > qexit))
					{
						CommonParsing.setProcessCancelled(false);
						job_processing.setSplitSize(0); 

						break bigloop; 
					}

					in.read(pes_packet, 0, pes_packetoffset);

					pesID = CommonParsing.getPES_IdField(pes_packet, 0);

					if ((returncode = CommonParsing.validateStartcode(pes_packet, 0)) < 0 || pesID < CommonParsing.SYSTEM_END_CODE)
					{
						returncode = returncode < 0 ? -returncode : 4;

						if (Message_2 && !missing_startcode)
							Common.setMessage(Resource.getString("parseSecondaryPES.missing.startcode") + " " + count);

						in.read(pes_packet, pes_packetoffset, pes_packet.length - pes_packetoffset);

						int i = returncode;

						for (; i < pes_packet.length - 3; )
						{
							returncode = CommonParsing.validateStartcode(pes_packet, i);

							if (returncode < 0)
							{
								i += -returncode;
								continue;
							}

							else
							{
								in.unread(pes_packet, i, pes_packet.length - i);

								count += i;
								missing_startcode = true;

								continue loop;
							}
						}

						in.unread(pes_packet, i, pes_packet.length - i);

						count += i;
						missing_startcode = true;

						continue loop;
					}

					if (Message_2 && missing_startcode)
						Common.setMessage(Resource.getString("parseSecondaryPES.found.startcode") + " " + count);

					missing_startcode = false;

					if (pes_streamtype == CommonParsing.MPEG1PS_TYPE || pes_streamtype == CommonParsing.MPEG2PS_TYPE || SimpleMPG)
					{
						switch (pesID)
						{
						case CommonParsing.SYSTEM_END_CODE: 
							in.unread(pes_packet, 4, 2);
							Common.setMessage("-> skip system_end_code @ " + count);
							count += 4;
							continue loop;

						case CommonParsing.PACK_START_CODE: 
							if ((0xC0 & pes_packet[4]) == 0)  //mpg1
							{
								in.skip(6);
								count += 12;
								continue loop;
							}

							else if ((0xC0 & pes_packet[4]) == 0x40) //mpg2
							{
								in.read(pes_packet, 6, 8);

								offset = 7 & pes_packet[13];

								count += 14;

								in.read(pes_packet, 14, offset);

								if (offset > 0)
								{
									for (int i = 0; i < offset; i++)
										if (pes_packet[14 + i] != -1)
										{
											in.unread(pes_packet, 14, offset);

											Common.setMessage("!> wrong pack header stuffing @ " + count);
											missing_startcode = true;
											continue loop;
										}
								}

								count += offset;

								continue loop;
							}

							else
							{
								in.unread(pes_packet, 4, 2);
								count += 4;
								continue loop;
							}

						case CommonParsing.SYSTEM_START_CODE:
						case CommonParsing.PROGRAM_STREAM_MAP_CODE:
						case CommonParsing.PADDING_STREAM_CODE:
						case CommonParsing.PRIVATE_STREAM_2_CODE:
						case CommonParsing.ECM_STREAM_CODE:
						case CommonParsing.EMM_STREAM_CODE:
						case CommonParsing.DSM_CC_STREAM_CODE:
						case 0xF3:
						case 0xF4:
						case 0xF5:
						case 0xF6:
						case 0xF7:
						case 0xF8:
						case 0xF9:
						case 0xFA:
						case 0xFB:
						case 0xFC:
						case 0xFD:
						case 0xFE:
						case 0xFF:
							pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, 0);

							in.skip(pes_payloadlength);

							count += (pes_packetoffset + pes_payloadlength);
							continue loop;
						}
					}

					if ( (0xF0 & pesID) != 0xE0 && (0xE0 & pesID) != 0xC0 && pesID != CommonParsing.PRIVATE_STREAM_1_CODE)
					{
						in.unread(pes_packet, 3, pes_packetoffset - 3);
						count += 3;
						continue loop;
					}

					pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, 0);

					if (pes_payloadlength == 0)
					{ 
						Common.setMessage(Resource.getString("parseSecondaryPES.packet.length") + " " + count);

						count += pes_packetoffset;
						continue loop;
					}

					in.read(pes_packet, pes_packetoffset, pes_payloadlength + 4);

					pes_packetlength = pes_packetoffset + pes_payloadlength;

					if (GetEnclosedPackets && CommonParsing.validateStartcode(pes_packet, pes_packetlength) < 0)
					{
						if (count + pes_packetlength < size)
						{
							if (Message_2 && !missing_startcode)
								Common.setMessage(Resource.getString("parseSecondaryPES.miss.next.startcode", String.valueOf(count + pes_packetlength), String.valueOf(count), Integer.toHexString(pesID).toUpperCase()));

							missing_startcode = true;

							in.unread(pes_packet, pes_packetoffset, pes_payloadlength + 4);
							count += pes_packetoffset;

							continue loop;
						}
					}

					else 
						in.unread(pes_packet, pes_packetlength, 4);


					clv[5]++;

					if (Debug) 
						System.out.print("\r"+Resource.getString("parseSecondaryPES.packs", String.valueOf(clv[5]), String.valueOf((count * 100 / size)), String.valueOf(count)));

					pes_extensionlength = CommonParsing.getPES_ExtensionLengthField(pes_packet, 0);

					pes_isMpeg2 = (0xC0 & pes_packet[6]) == 0x80;
					pes_alignment = pes_isMpeg2 && (4 & pes_packet[6]) != 0;
					pes_scrambled = pes_isMpeg2 && (0x30 & pes_packet[6]) != 0;

					count += pes_packetlength;

					/**
					 * check scrambling
					 */
					if (IgnoreScrambledPackets)
					{
						// cannot work with scrambled data
						if (pes_scrambled)
						{
							if (!scrambling_messaged)
							{
								scrambling_messaged = true;

								Common.setMessage(Resource.getString("parseTS.scrambled", Integer.toHexString(pesID).toUpperCase(), String.valueOf(clv[5]), String.valueOf(count - pes_packetlength)));
							}

							continue loop;
						}

						else if (scrambling_messaged)
						{
							Common.setMessage(Resource.getString("parseTS.clear", Integer.toHexString(pesID).toUpperCase(), String.valueOf(clv[5]), String.valueOf(count - pes_packetlength)));
							scrambling_messaged = false;
						}
					}

					/**
					 * vdr_dvbsub determination
					 */
					pes_extension2_id = CommonParsing.getExtension2_Id(pes_packet, pes_headerlength, pes_payloadlength, pesID, pes_isMpeg2);

					isTeletext = false;
					subID = 0;

					if (pesID == CommonParsing.PRIVATE_STREAM_1_CODE && pes_payloadlength > 2)
					{
						offset = pes_headerlength + pes_extensionlength;

						if (offset < pes_packetlength)
						{
							subID = 0xFF & pes_packet[offset];
							isTeletext = pes_extensionlength == 0x24 && subID>>>4 == 1;

							//subpic in vdr_pes
							if (pes_alignment && !isTeletext && (subID>>>4 == 2 || subID>>>4 == 3))
								pes_streamtype = CommonParsing.MPEG2PS_TYPE;  //will be resetted before next packet

							if (pes_streamtype != CommonParsing.MPEG1PS_TYPE && pes_streamtype != CommonParsing.MPEG2PS_TYPE && !isTeletext) 
								subID = 0; //disables LPCM too 
						}

						else if (pes_streamtype != CommonParsing.MPEG1PS_TYPE) //?
						{
							pes_extensionlength = pes_payloadlength - 3;
							pes_packet[8] = (byte)(pes_extensionlength);
						}


						/**
						 * packet buffering esp. of subpics from vdr or other pes
						 */
						if (pes_extension2_id != -1)
						{
							String str = String.valueOf(pes_extension2_id);
							offset = pes_headerlength + pes_extensionlength;

							if ( !substreams.containsKey(str))
								substreams.put(str, new StandardBuffer());

							sb = (StandardBuffer) substreams.get(str);

							// buffer raw packet data
							if (!pes_alignment)
							{
								sb.write(pes_packet, offset, pes_packetlength - offset);
								continue loop;
							}

							// start packet, buffer this and get last completed packet
							else
							{
								buffered_data = sb.getData();

								sb.reset();
								sb.write(pes_packet, 0, pes_packetlength);

								if (buffered_data == null || buffered_data.length < 10)
									continue loop;

								pes_packetlength = buffered_data.length;

								if (pes_packetlength > 0x10005)
								{
									Common.setMessage("!> sub packet too long: 0x" + Integer.toHexString(pesID).toUpperCase() + " /ext2_id " + pes_extension2_id);
									pes_packetlength = 0x10005;
								}

								pes_payloadlength = pes_packetlength - pes_packetoffset;

								System.arraycopy(buffered_data, 0, pes_packet, 0, pes_packetlength);

								CommonParsing.setPES_LengthField(pes_packet, 0, pes_payloadlength);

								buffered_data = null;
							}
						}
					}

					foundObject = false;

					/**
					 * find ID object
					 */
					for (int i = 0; i < demuxList.size(); i++)
					{      
						streamdemultiplexer = (StreamDemultiplexer) demuxList.get(i);

						foundObject = pesID == streamdemultiplexer.getID() && subID == streamdemultiplexer.subID() && isTeletext == streamdemultiplexer.isTTX();

						if (foundObject)
							break; 
					}

					/**
					 * create new ID object
					 */
					if (!foundObject)
					{
						String IDtype = "";

						switch (0xF0 & pesID)
						{
						case 0xE0: 
							IDtype = Resource.getString("idtype.mpeg.video.ignored");

							streamdemultiplexer = new StreamDemultiplexer();
							streamdemultiplexer.setID(pesID);
							streamdemultiplexer.setsubID(0);
							streamdemultiplexer.setType(CommonParsing.MPEG_VIDEO);
							streamdemultiplexer.setStreamType(pes_streamtype);

							demuxList.add(streamdemultiplexer);
							break; 

						case 0xC0:
						case 0xD0:
							IDtype = Resource.getString("idtype.mpeg.audio"); 

							streamdemultiplexer = new StreamDemultiplexer();
							streamdemultiplexer.setID(pesID);
							streamdemultiplexer.setsubID(0);
							streamdemultiplexer.setType(CommonParsing.MPEG_AUDIO);
							streamdemultiplexer.setStreamType(pes_streamtype);

							demuxList.add(streamdemultiplexer);
							streamdemultiplexer.init(fparent, MainBufferSize / demuxList.size(), demuxList.size(), CommonParsing.SECONDARY_PES_PARSER);

							break; 
						}

						switch (pesID)
						{
						case CommonParsing.PRIVATE_STREAM_1_CODE:
							IDtype = Resource.getString("idtype.private.stream");
							IDtype += (isTeletext ? " TTX ": "") + (subID != 0 ? " (SubID 0x" + Integer.toHexString(subID).toUpperCase() + ")" : ""); 

							streamdemultiplexer = new StreamDemultiplexer();
							streamdemultiplexer.setID(pesID);
							streamdemultiplexer.setsubID(subID);

							switch (subID>>>4)
							{
							case 1:
								streamdemultiplexer.setType(CommonParsing.TELETEXT);
								break;

							case 2:
							case 3:
								streamdemultiplexer.setType(CommonParsing.SUBPICTURE);
								break;

							case 8:
								streamdemultiplexer.setType(CommonParsing.AC3_AUDIO);
								break;

							case 0xA:
								streamdemultiplexer.setType(CommonParsing.LPCM_AUDIO);
							}

							streamdemultiplexer.setTTX(isTeletext);
							streamdemultiplexer.setStreamType(pes_streamtype);

							demuxList.add(streamdemultiplexer);
							streamdemultiplexer.init(fparent, MainBufferSize / demuxList.size(), demuxList.size(), CommonParsing.SECONDARY_PES_PARSER);

							break; 
						}

						Common.setMessage(Resource.getString("parseSecondaryPES.found.pesid", Integer.toHexString(pesID).toUpperCase(), IDtype, "" + (count - 6 - pes_payloadlength))); 
					}

					if (!streamdemultiplexer.StreamEnabled())
						continue loop;

					if (streamdemultiplexer.getType() == CommonParsing.MPEG_VIDEO) 
						continue loop;

					else 
						streamdemultiplexer.write(job_processing, pes_packet, 0, pes_packetlength, true);
				}

				/**
				 * loop not yet used
				 */
				break bigloop;
			}

			Common.setMessage(Resource.getString("parseSecondaryPES.packs", String.valueOf(clv[5]), String.valueOf(count * 100 / size), String.valueOf(count)));


			in.close(); 

			int[] stream_number = new int[10];

			for (int i = 0, es_streamtype; i < demuxList.size(); i++)
			{
				streamdemultiplexer = (StreamDemultiplexer) demuxList.get(i);
				es_streamtype = streamdemultiplexer.getType();

				if (es_streamtype == CommonParsing.MPEG_VIDEO) 
					continue;

				String[] values = streamdemultiplexer.close(job_processing, vptslog);

				if (values[0].equals("")) 
				{
					Common.setMessage(Resource.getString("parseSecondaryPES.msg.noexport") + Integer.toHexString(streamdemultiplexer.getID()).toUpperCase() + ")");
					continue;
				}

				String newfile = values[3] + (stream_number[es_streamtype] > 0 ? ("[" + stream_number[es_streamtype] + "]") : "") + "." + values[2];

				Common.renameTo(values[0], newfile);

				values[0] = newfile;
				values[3] = vptslog;

				switch (es_streamtype)
				{
				case CommonParsing.AC3_AUDIO:
				case CommonParsing.DTS_AUDIO:
					if ( streamdemultiplexer.subID() != 0 && (0xF0 & streamdemultiplexer.subID()) != 0x80 ) 
						break;

					Common.setMessage("");
					Common.setMessage(Resource.getString("parseSecondaryPES.ac3.audio") + ((streamdemultiplexer.subID() != 0) ? ("(SubID 0x" + Integer.toHexString(streamdemultiplexer.subID()).toUpperCase()+")") : ""));

					new StreamProcess(es_streamtype, collection, values[0], values[1], values[2], values[3]);
					break;

				case CommonParsing.TELETEXT: 
					Common.setMessage("");
					Common.setMessage(Resource.getString("parseSecondaryPES.teletext") + " (SubID 0x" + Integer.toHexString(streamdemultiplexer.subID()).toUpperCase() + ")");

					new StreamProcess(es_streamtype, collection, values[0], values[1], values[2], values[3]);
					break;

				case CommonParsing.MPEG_AUDIO: 
					Common.setMessage("");
					Common.setMessage(Resource.getString("parseSecondaryPES.mpeg.audio") + " (0x" + Integer.toHexString(streamdemultiplexer.getID()).toUpperCase() + ")");

					new StreamProcess(es_streamtype, collection, values[0], values[1], values[2], values[3]);
					break;

				case CommonParsing.LPCM_AUDIO:
					Common.setMessage("");
					Common.setMessage(Resource.getString("parseSecondaryPES.lpcm.audio") + " (SubID 0x" + Integer.toHexString(streamdemultiplexer.subID()).toUpperCase() + ")");

					new StreamProcess(es_streamtype, collection, values[0], values[1], values[2], values[3]);
					break;

				case CommonParsing.SUBPICTURE:
					Common.setMessage("");
					Common.setMessage(Resource.getString("parseSecondaryPES.subpic") + " (SubID 0x" + Integer.toHexString(streamdemultiplexer.subID()).toUpperCase() + ")");

					new StreamProcess(es_streamtype, collection, values[0], values[1], values[2], values[3]);
					break;
				}

				stream_number[es_streamtype]++;

				// save infos for output segmentation
				tempfiles.add(values[0]);
				tempfiles.add(aXInputFile);
				tempfiles.add(values[1]);
				tempfiles.add(values[2]);

				if (job_processing.getSplitSize() == 0)
				{
					new File(newfile).delete();
					new File(values[1]).delete();
				}
			}

		} catch (IOException e2) { 

			Common.setExceptionMessage(e2);
		}

		return vptslog;
	}
}
