/*
 * @(#)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 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.xinput.XInputFile;

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.StreamProcess;

import java.util.List;
import java.util.Hashtable;

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

	public int ERRORCODE = 0;
	public int MainBufferSize = 8192000;

//
	public static StreamDemultiplexer streamdemultiplexer;
	public static StreamConverter streamconverter;

	public static List demuxList;
	public static Hashtable streamobjects;

	public static boolean CreateD2vIndex;
	public static boolean SplitProjectFile;

	public static String fchild;
	public static String fparent;

	public static long OverheadSize;


//

	/**
	 * 
	 */
	public StreamParserBase()
	{
		init();
	}

	/**
	 * 
	 */
	public void init()
	{
		MainBufferSize = Integer.parseInt(Common.getSettings().getProperty(Keys.KEY_MainBuffer));

		if (MainBufferSize <= 0)
			MainBufferSize = 4096000;


		streamdemultiplexer = null;
		streamconverter = null;
		demuxList = null;
		streamobjects = null;

		CreateD2vIndex = false;
		SplitProjectFile = false;

		OverheadSize = 2048000;

		fchild = "";
		fparent = "";
	}

	/**
	 * 
	 */
	public String parseStream(JobCollection collection, XInputFile aXInputFile, int pes_streamtype, int action, String vptslog)
	{
		return null;
	}

	/**
	 * 
	 */
	public void setFileName(JobCollection collection, JobProcessing job_processing, XInputFile aXInputFile)
	{
		setFileName(collection, job_processing, aXInputFile, "");
	}

	/**
	 * 
	 */
	public void setFileName(JobCollection collection, JobProcessing job_processing, XInputFile aXInputFile, String extension)
	{
		String str = aXInputFile.getName();
		str = str.replace('?', '_');
		str = str.replace('*', '_');
		str = str.replace(':', '_');
		str = str.replace('/', '_');
		str = str.replace('"', '_');
		str = str.replace('<', '_');
		str = str.replace('>', '_');
		str = str.replace('|', '_');

		fchild = collection.getOutputName(str);

//		fchild = collection.getOutputName(aXInputFile.getName());
		fparent = collection.getOutputNameParent(fchild);

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

	/**
	 * 
	 */
	public boolean pause()
	{
		return Common.waitingMainProcess();
	}

	/**
	 * nextfile PTS check
	 * 
	 * returns new pts offset to append
	 */
	public long nextFilePTS(JobCollection collection, int parser_type, int pes_streamtype, long lastpts, int file_number)
	{
		return nextFilePTS(collection, parser_type, pes_streamtype, lastpts, file_number, 0L);
	}

	/**
	 * nextfile PTS check
	 * 
	 * returns new pts offset to append
	 */
	public long nextFilePTS(JobCollection collection, int parser_type, int pes_streamtype, long lastpts, int file_number, long startPoint)
	{
		return nextFilePTS(collection, parser_type, pes_streamtype, lastpts, 0L, file_number, 0L);
	}

	/**
	 * nextfile PTS check
	 * 
	 * returns new pts offset to append
	 */
	public long nextFilePTS(JobCollection collection, int parser_type, int pes_streamtype, long lastpts, long ptsoffset, int file_number, long startPoint)
	{
		JobProcessing job_processing = collection.getJobProcessing();

		byte[] data;

		long pts = 0;

		int position = 0;
		int buffersize = collection.getSettings().getIntProperty(Keys.KEY_ScanBuffer);
		int pes_ID;

		boolean PVA_Audio = collection.getSettings().getBooleanProperty(Keys.KEY_PVA_Audio);
		boolean containsPts;

		lastpts &= 0xFFFFFFFFL; //ignore bit33 of lastpts

		if (collection.getPrimaryInputFileSegments() > file_number)
		{
			try {

				XInputFile aXinputFile = ((XInputFile) collection.getInputFile(file_number)).getNewInstance();

				if (aXinputFile == null)
					return -1L;

				data = new byte[buffersize];

				aXinputFile.randomAccessSingleRead(data, startPoint);

				switch (parser_type)
				{
				case CommonParsing.PVA_PARSER:    // pva

					int pva_pid;
					int ptsflag;
					int pva_payloadlength;

					loop:
					while (position < buffersize - 20)
					{
						if (data[position] != 0x41 || data[position + 1] != 0x56 || data[position + 4] != 0x55)
						{
							position++; 
							continue loop;
						}

						pva_pid = 0xFF & data[position + 2];
						ptsflag = 0xFF & data[position + 5];
						pva_payloadlength = (0xFF & data[position + 6])<<8 | (0xFF & data[position + 7]);
						containsPts = (0x10 & ptsflag) != 0;

		 				if (pva_pid == 1) //video
						{
							if (containsPts)
							{
								pts = CommonParsing.readPTS(data, position + 8, 4, !CommonParsing.BYTEREORDERING, true);
								break loop;
							} 
						}

						else if (pva_pid != 0) //mainaudio mpa and other pids
						{
							ptsloop:
							for (int i = position + 8, j = (PVA_Audio && !containsPts) ? position + 8: position + 11; i < j; i++)
							{
								if (CommonParsing.validateStartcode(data, i) < 0)
									continue ptsloop;

								pes_ID = CommonParsing.getPES_IdField(data, i);

								if (pes_ID != 0xBD && (0xF0 & pes_ID) != 0xC0) 
									continue ptsloop;

								if ((0x80 & data[i + 7]) == 0) 
									break ptsloop;

								pts = CommonParsing.getPTSfromBytes(data, i + 9); //returns 32bit

								break loop;
							}
						}

						position += 8 + pva_payloadlength;
					}

					break;

				case CommonParsing.PRIMARY_PES_PARSER:    // mpg

					int pes_payloadlength = 0;
					int ptslength = 0;
					int returncode;
					int pes_offset = 0;

					boolean nullpacket = false;

					loop:
					while (position < buffersize - 20)
					{
						if ((returncode = CommonParsing.validateStartcode(data, position)) < 0)
						{ 
							position += -returncode;
							continue loop; 
						}

						pes_ID = CommonParsing.getPES_IdField(data, position);

						if (pes_ID < 0xBA)
						{
							position += 3;
							continue loop; 
						}

						if (pes_ID == 0xBA)
						{ 
							position += 12;
							continue loop; 
						}

						pes_payloadlength = CommonParsing.getPES_LengthField(data, position);

						if (pes_payloadlength == 0)
							nullpacket = true;

						if ((0xF0 & pes_ID) != 0xE0 && (0xF0 & pes_ID) != 0xC0 && pes_ID != 0xBD )
						{ 
							position += 6 + pes_payloadlength;
							continue loop; 
						}

						if (pes_streamtype == CommonParsing.MPEG1PS_TYPE)
						{
							pes_offset = 6;

							skiploop:
							while(true)
							{
								switch (0xC0 & data[position + pes_offset])
								{
								case 0x40: 
									pes_offset += 2; 
									continue skiploop; 

								case 0x80: 
									pes_offset += 3; 
									continue skiploop; 

								case 0xC0: 
									pes_offset++; 
									continue skiploop; 

								case 0:
									break; 
								}

								switch (0x30 & data[position + pes_offset])
								{
								case 0x20:
									containsPts = true; 
									break skiploop; 

								case 0x30:  
									containsPts = true; 
									break skiploop; 

								case 0x10: 
									containsPts = false; 
									break skiploop; 

								case 0:  
									containsPts = false; 
									pes_offset++; 
									break skiploop; 
								}
							}
						}

						else
						{
							containsPts = (0x80 & data[position + 7]) != 0;
							pes_offset = 9;
						}

						if (containsPts)
							pts = CommonParsing.getPTSfromBytes(data, position + pes_offset);

						position += 6 + pes_payloadlength;

						if (nullpacket)
							break;
					}

					break;
				} // end switch

			} catch (IOException e) { 

				Common.setExceptionMessage(e);
			}
		}

	//Common.setMessage("AA " + pts + " / " + ptsoffset + " / " + (pts + ptsoffset) + " / " + lastpts);
	//re-use given ptsoffset (chapterpoints of vob)
	//	pts += ptsoffset;

		if (file_number == 0 && startPoint == 0)
		{  // need global offset?
			pts &= 0xFFFFFFFFL;

			String str = collection.getSettings().getProperty(Keys.KEY_PtsShift_Value);

			if (str.equals("auto"))
			{ 
				long newpts = ((pts / 324000000L) - 1L) * 324000000L;
				Common.setMessage(Resource.getString("nextfile.shift.auto", "" + (newpts / 324000000L)));

				return newpts;
			}

			else if (!str.equals("0"))
			{ 
				Common.setMessage(Resource.getString("nextfile.shift.manual", collection.getSettings().getProperty(Keys.KEY_PtsShift_Value)));

				return ((long)(Double.parseDouble(collection.getSettings().getProperty(Keys.KEY_PtsShift_Value)) * 324000000L));
			}

			else 
				return 0L;
		}
		else
		{
			pts -= job_processing.getNextFileStartPts();
			pts &= 0xFFFFFFFFL;

			long ret = 0L;

			if (Math.abs(pts - lastpts) < 900000)
				ret = -1L;

			else if (pts > lastpts)  
				ret = 0L;

			else 
				ret = ((lastpts + 1728000L) - pts); // offset is multiple of 40,24,32,33.1,8ms

			if (ret >= 0)
				Common.setMessage(Resource.getString("nextfile.next.file.start", Common.formatTime_1(pts / 90L), Common.formatTime_1(lastpts / 90L)));

			if (ret > 0)
				Common.setMessage(Resource.getString("nextfile.next.file.start.adaption", Common.formatTime_1(ret / 90L)));

			return ret;
		}
	}

	/**
	 * 
	 */
	public Hashtable getStreamObjects(JobProcessing job_processing)
	{
		return job_processing.getStreamObjects();
	}

	/**
	 * 
	 */
	public String processElementaryStreams(String vptslog, int action, int[] clv, JobCollection collection, JobProcessing job_processing)
	{
		if (action != CommonParsing.ACTION_DEMUX)
		{
			streamconverter.close(job_processing, CommonParsing.isInfoScan());
			return vptslog;
		}

		//finish video stream
		for (int i = 0, NumberOfVideostreams = 0; i < demuxList.size(); i++)
		{
			streamdemultiplexer = (StreamDemultiplexer) demuxList.get(i);

			if (streamdemultiplexer.getType() == CommonParsing.MPEG_VIDEO)
			{ 
				// accept only first video
				if (NumberOfVideostreams > 0)
				{
					Common.setMessage("!> further videostream found (PID 0x" + Integer.toHexString(streamdemultiplexer.getPID()).toUpperCase() + " / ID 0x" + Integer.toHexString(streamdemultiplexer.getID()).toUpperCase() + ") -> ignored");
					continue;
				}

				// d2v project 
				if (CreateD2vIndex || SplitProjectFile)
					job_processing.getProjectFileD2V().write(job_processing.getProjectFileExportLength(), job_processing.getExportedVideoFrameNumber());

				Common.setMessage("");
				Common.setMessage(formatIDString(Resource.getString("ExportPanel.Streamtype.MpgVideo"), streamdemultiplexer.getPID(), streamdemultiplexer.getID(), streamdemultiplexer.subID()));
				Common.setMessage(Resource.getString("video.msg.summary") + " " + job_processing.getExportedVideoFrameNumber() + "-" + clv[0] + "-" + clv[1] + "-" + clv[2] + "-" + clv[3] + "-" + clv[4]);

				vptslog = streamdemultiplexer.closeVideo(job_processing, collection.getOutputDirectory() + collection.getFileSeparator());

				NumberOfVideostreams++;
			}
		} 

		processNonVideoElementaryStreams(vptslog, action, clv, collection, job_processing);

		return vptslog;
	}

	/**
	 * 
	 */
	public void processNonVideoElementaryStreams(String vptslog, int action, int[] clv, JobCollection collection, JobProcessing job_processing)
	{
		processNonVideoElementaryStreams(vptslog, action, clv, collection, job_processing, null, null);
	}

	/**
	 * 
	 */
	public void processNonVideoElementaryStreams(String vptslog, int action, int[] clv, JobCollection collection, JobProcessing job_processing, List tempfiles, XInputFile aXInputFile)
	{
		//finish other streams
		int[] stream_number = job_processing.getStreamNumbers(); 

		String[] key_values = { 
			Keys.KEY_Streamtype_Ac3Audio[0],
			Keys.KEY_Streamtype_Teletext[0],
			Keys.KEY_Streamtype_MpgAudio[0],
			"",
			Keys.KEY_Streamtype_PcmAudio[0],
			Keys.KEY_Streamtype_Subpicture[0],
			Keys.KEY_Streamtype_Ac3Audio[0],
			Keys.KEY_Streamtype_PcmAudio[0]
		};

		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;

			// from pva - test with others
			if (streamdemultiplexer.getID() == 0) 
				continue;

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

			if (values[0].equals("")) 
			{
				Common.setMessage(formatIDString(Resource.getString("StreamParser.NoExport"), streamdemultiplexer.getPID(), 0xFF & streamdemultiplexer.getID(), streamdemultiplexer.subID()));
				continue;
			}

			if (streamdemultiplexer.getStreamNumber() < 0)
			{
				streamdemultiplexer.setStreamNumber(stream_number[es_streamtype]);
				stream_number[es_streamtype]++;
			}

			String newfile = values[3] + (streamdemultiplexer.getStreamNumber() > 0 ? ("-" + Common.adaptString(stream_number[es_streamtype], 2)) : "") + "." + 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)
				{
					Common.setMessage(formatIDString(Resource.getString("StreamParser.NoExport"), streamdemultiplexer.getPID(), 0xFF & streamdemultiplexer.getID(), streamdemultiplexer.subID()));
					break;
				}

			case CommonParsing.TELETEXT: 
			case CommonParsing.MPEG_AUDIO: 
			case CommonParsing.LPCM_AUDIO:
			case CommonParsing.SUBPICTURE:
				createStreamProcess(es_streamtype, collection, values, key_values[es_streamtype]);
				break;
			}

			// save infos for output segmentation
			if (tempfiles != null)
			{
/**/				tempfiles.add(values[0]);
				tempfiles.add(aXInputFile);
				tempfiles.add(values[1]);
				tempfiles.add(values[2]);
/**/
Common.setMessage("tmpfiles " + tempfiles.size());
				if (job_processing.getSplitSize() == 0)
				{
					new File(newfile).delete();
					new File(values[1]).delete();
				}
			}

			else
			{
				new File(newfile).delete();
				new File(values[1]).delete();
			}

		}
	}

	/**
	 * 
	 */
	public String formatIDString(String str1, int pid, int id, int subid)
	{
		String str = "++> " + str1;

		str += ": PID 0x" + Common.adaptString(Integer.toHexString(pid).toUpperCase(), 4);
		str += " / PesID 0x" + Common.adaptString(Integer.toHexString(id).toUpperCase(), 2);
		str += " / SubID 0x" + Common.adaptString(Integer.toHexString(subid).toUpperCase(), 2);
		str += " :";

		return str;
	}

	/**
	 * 
	 */
	public void createStreamProcess(int es_streamtype, JobCollection collection, String[] values, String key_value)
	{
		Common.setMessage("");
		Common.setMessage(formatIDString(Resource.getString(key_value), streamdemultiplexer.getPID(), streamdemultiplexer.getID(), streamdemultiplexer.subID()));

		new StreamProcess(es_streamtype, collection, values);
	}

	/**
	 *
	 */
	public void addCellTimeFromFileSegment(JobProcessing job_processing)
	{
		//addCellTime(job_processing);
	}

	/**	 *

	 */
	public void addCellTime(JobProcessing job_processing)
	{
		job_processing.addCellTime(job_processing.getExportedVideoFrameNumber());
	}

	/**
	 * init conversions 
	 */
	public void initConversion(JobCollection collection, String parent, int action, int source, int splitpart)
	{
		if (action <= CommonParsing.ACTION_DEMUX)
			return;

		String[] ext = { "", ".vdr", ".mpg", ".pva", ".ts" };

		switch (action)
		{
		case CommonParsing.ACTION_TO_VDR:
		case CommonParsing.ACTION_TO_M2P:
		case CommonParsing.ACTION_TO_PVA:
		case CommonParsing.ACTION_TO_TS:
			streamconverter.init(collection, parent + "[remux]" + ext[action], MainBufferSize, action, splitpart);
			break;

		case CommonParsing.ACTION_FILTER:
			streamconverter.init(collection, parent + "[filter]" + ext[source], MainBufferSize, action, splitpart);
			break;

		case CommonParsing.ACTION_COPY:
		}
	}

	/**
	 * getOverhead to collect more samples
	 */
	public void setOverheadSize(JobCollection collection)
	{
		OverheadSize = collection.getSettings().getBooleanProperty(Keys.KEY_Input_useReadOverhead) ? 2048000 : 0;
	}
}
