/*
 * @(#)StreamParser
 *
 * Copyright (c) 2005-2009 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
 *
 */

/*
 * multicolor subtitling patch by Duncan (Shannock9) UK
 * 2008-12
 */

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.RandomAccessFile;
import java.io.ByteArrayOutputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.PrintStream;

import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Date;
import java.util.TimeZone;
import java.util.Enumeration;
import java.util.StringTokenizer;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

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

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

import net.sourceforge.dvb.projectx.subtitle.Subpicture;
import net.sourceforge.dvb.projectx.subtitle.BMP;
import net.sourceforge.dvb.projectx.subtitle.Bitmap;
import net.sourceforge.dvb.projectx.subtitle.Sup2VobSub;
import net.sourceforge.dvb.projectx.subtitle.ColorAreas;                                         //S9

import net.sourceforge.dvb.projectx.thirdparty.Ifo;


/**
 * main thread
 */
public class StreamProcessSubpicture extends StreamProcessBase {

	private final String subdecode_errors[] = {
		"",
		"", // -1 = correct decoded dvb-subpicture segments, can export
		"", // -2 = correct decoded dvb-subpicture segments, w/o export
		Resource.getString("subpicture.msg.error3"), // -3 = error while decoding dvb-subpicture
		Resource.getString("subpicture.msg.error4"),
		Resource.getString("subpicture.msg.error5"),
		Resource.getString("subpicture.msg.error6"),
		Resource.getString("subpicture.msg.error7"),
		Resource.getString("subpicture.msg.error8"),
		Resource.getString("subpicture.msg.error9")
	};

	private String Extension = ".new";

	private boolean debug;
	private boolean KeepOriginalTimecode;
	private boolean ExportAsVobSub;
	private boolean UseAdditionalOffset;
	private boolean ShowSubpictureWindow;
	private boolean Message_2;

	private int AdditionalOffset_Value;

	private int X_Offset = 0;
	private int Y_Offset = 0;
	private int DisplayMode = 0;
	private int ExportType = 0;
	private int Pictures = 0;
	private int LastPGCSet = 0;

	private String SubpictureColorModel;
	private String PageId_Value;
	private String SubtitleExportFormat;
	private String FileParent;

	/**
	 * 
	 */
	public StreamProcessSubpicture(String extension)
	{
		super();

		Extension = extension;
	}

	/**
	 * 
	 */
	public StreamProcessSubpicture(JobCollection collection, XInputFile xInputFile, String filename_pts, String filename_type, String videofile_pts, int isElementaryStream)
	{
		super();

		get_XY_Offset(collection, isElementaryStream);
		getDisplayMode(collection, isElementaryStream);

		String SubtitleExportFormat = collection.getSettings().getProperty(Keys.KEY_SubtitleExportFormat);

		processStream(collection, xInputFile, filename_pts, filename_type, videofile_pts, isElementaryStream, SubtitleExportFormat);

		// 2nd export format, new run
		SubtitleExportFormat = collection.getSettings().getProperty(Keys.KEY_SubtitleExportFormat_2);

		if (!SubtitleExportFormat.equalsIgnoreCase("null"))
			processStream(collection, xInputFile, filename_pts, filename_type, videofile_pts, isElementaryStream, SubtitleExportFormat);
	}

	/**
	 * set new X with or without offset
	 */
	public void set_XY_Offset(int x_value, int y_value)
	{
		X_Offset = x_value;
		Y_Offset = y_value;
	}

	/**
	 * set new X with or without offset
	 */
	private void get_XY_Offset(JobCollection collection, int isElementaryStream)
	{
		//if (isElementaryStream != CommonParsing.ES_TYPE)
		//	return;

		StringTokenizer st = new StringTokenizer(collection.getSettings().getProperty(Keys.KEY_SubtitleMovePosition_Value), ",");
		int a = 0;
		int[] values = new int[2];

		while (st.hasMoreTokens() && a < values.length)
		{
			try {
				values[a] = Integer.parseInt(st.nextToken());

			} catch (Exception e) {}

			a++;
		}

		X_Offset = values[0];
		Y_Offset = values[1];
	}

	/**
	 * set new display to be forced or not
	 */
	private void getDisplayMode(JobCollection collection, int isElementaryStream)
	{
		//if (isElementaryStream != CommonParsing.ES_TYPE)
		//	return;

		DisplayMode = collection.getSettings().getIntProperty(Keys.KEY_SubtitleChangeDisplay);
	}

	/**
	 * decoding subpicture stream, forces SUP, simple method for modifying a SUP stream from teletext process
	 */
	public void processStream(JobCollection collection, XInputFile xInputFile, int isElementaryStream)
	{
		processStream(collection, xInputFile, "-1", "sp", "-1", isElementaryStream, Keys.ITEMS_SubtitleExportFormat[7].toString());
	}

	/**
	 * decoding subpicture stream
	 */
	private void processStream(JobCollection collection, XInputFile xInputFile, String filename_pts, String filename_type, String videofile_pts, int isElementaryStream, String SubtitleExportFormat)
	{
		Subpicture subpicture = Common.getSubpictureClass();

		JobProcessing job_processing = collection.getJobProcessing();

		String fchild = isElementaryStream == CommonParsing.ES_TYPE ? collection.getOutputName(xInputFile.getName()) : xInputFile.getName();
		FileParent = collection.getOutputNameParent(fchild);

		FileParent += isElementaryStream == CommonParsing.ES_TYPE ? Extension : "";

		String subfile = FileParent + ".sup";

		long size = xInputFile.length();

		byte[] parse12 = new byte[12];
		byte[] packet = new byte[0];

		long count = 0;
		long startPoint = 0;
		long time_difference = 0;
		long display_time = 0;
		long source_pts = 0;
		long new_pts = 0;
		long first_pts = -1;
		long last_pts = 0;

		int x = 0;
		int v = 0;
		int packetlength = 0;
		ExportType = 0;
		Pictures = 0;
		LastPGCSet = 0;

		boolean vptsdata = false;
		boolean ptsdata = false;
		boolean write = false;
		boolean missing_syncword = false;
		boolean DVBpicture = false;

		debug = collection.getSettings().getBooleanProperty(Keys.KEY_DebugLog);
		KeepOriginalTimecode = isElementaryStream == CommonParsing.ES_TYPE ? true : collection.getSettings().getBooleanProperty(Keys.KEY_SubtitlePanel_keepOriginalTimecode);
		ExportAsVobSub = collection.getSettings().getBooleanProperty(Keys.KEY_SubtitlePanel_exportAsVobSub);
		UseAdditionalOffset = collection.getSettings().getBooleanProperty(Keys.KEY_additionalOffset);
		ShowSubpictureWindow = collection.getSettings().getBooleanProperty(Keys.KEY_showSubpictureWindow);
		Message_2 = collection.getSettings().getBooleanProperty(Keys.KEY_MessagePanel_Msg2);

		AdditionalOffset_Value = collection.getSettings().getIntProperty(Keys.KEY_ExportPanel_additionalOffset_Value);

		SubpictureColorModel = collection.getSettings().getProperty(Keys.KEY_SubpictureColorModel);
		PageId_Value = collection.getSettings().getProperty(Keys.KEY_SubtitlePanel_PageId_Value);
	//	SubtitleExportFormat = collection.getSettings().getProperty(Keys.KEY_SubtitleExportFormat);

		try {
			if (ShowSubpictureWindow)
				Common.getGuiInterface().showSubpicture();

			Hashtable user_table = new Hashtable();

			ArrayList subpicture_colormodel = Common.getColorModelsList();

			if (subpicture_colormodel.indexOf(SubpictureColorModel) > 2) // 0,1,2 internal
				user_table = Common.getUserColourTable(SubpictureColorModel);

			subpicture.reset();

			subpicture.dvb.setIRD(2<<subpicture_colormodel.indexOf(SubpictureColorModel), user_table, debug, PageId_Value);

			Common.setMessage(Resource.getString("subpicture.msg.model", SubpictureColorModel) + " " + PageId_Value);

			if (SubtitleExportFormat.equalsIgnoreCase(Keys.ITEMS_SubtitleExportFormat[6].toString()))
			{
				subfile = FileParent + ".son";
				ExportType = 1;
			}

		//	Common.setMessage("");
			Common.setMessage(Resource.getString("subpicture.msg.output") + " " + subfile.substring(subfile.length() - 3));

			PushbackInputStream in = new PushbackInputStream(xInputFile.getInputStream(), 65536);

			IDDBufferedOutputStream out = new IDDBufferedOutputStream(new FileOutputStream(subfile),65536);

			PrintStream print_out = new PrintStream(out);

			Common.setMessage(Resource.getString("subpicture.msg.tmpfile", xInputFile.getName(), "" + size));

			// SUP with changed settings
			if (ExportType == 0)
			{
				subpicture.set_XY_Offset(X_Offset, Y_Offset);
				subpicture.setDisplayMode(DisplayMode);
			}

			if (X_Offset != 0 || Y_Offset != 0)
				Common.setMessage("-> move source picture position: X " + X_Offset + ", Y " + Y_Offset);

			if (DisplayMode != 0)
				Common.setMessage("-> set new picture display mode: " + Keys.ITEMS_SubtitleChangeDisplay[DisplayMode]);

			Common.updateProgressBar(Resource.getString("subpicture.progress") + " " + xInputFile.getName(), 0, 0);


			long[] ptsval = {0};
			long[] ptspos = {0};
			long[] vptsval = {0};

			long pts_offset = UseAdditionalOffset ? 90L * AdditionalOffset_Value : 0;

			//System.gc();

			long[][] obj = loadTempOtherPts(filename_pts, "subpicture.msg.discard", "audio.msg.pts.firstonly", "subpicture.msg.pts.start_end", "", CommonParsing.SUBPICTURE, false, debug);

			if (obj != null)
			{
				ptsval = obj[0];
				ptspos = obj[1];
				ptsdata = true;
				obj = null;
			}

			ptsdata = true;

			obj = loadTempVideoPts(videofile_pts, debug);

			if (obj != null)
			{
				vptsval = obj[0];
				vptsdata = true;
				obj = null;
			}

			//System.gc();


			if (vptsdata && ptsdata)
			{
				int jump = checkPTSMatch(vptsval, ptsval);

				if (jump < 0)
				{
					Common.setMessage(Resource.getString("subpicture.msg.pts.mismatch"));  

					vptsdata = false; 
					x = 0; 
				}

				else
					x = jump;

			}

			if (vptsdata && ptsdata)
			{
				Common.setMessage(Resource.getString("subpicture.msg.adjust.at.video"));

				time_difference = vptsval[0];
			}

			if (!vptsdata && ptsdata)
			{
				Common.setMessage(Resource.getString("subpicture.msg.adjust.at.own"));

				time_difference = 0;
			}

			if (ptsdata)
			{ 
				source_pts = ptsval[x]; 
				startPoint = ptspos[x]; 
			}

			//don't need it anymore
			ptsval = null;
			ptspos = null;

			while (count < startPoint)
				count += in.skip(startPoint-count);

			//yield();


			readloop:
			while ( count < size )
			{ 
				Common.updateProgressBar(count, size);

				//yield();

				while (pause())
				{}

				if (CommonParsing.isProcessCancelled())
				{ 
					CommonParsing.setProcessCancelled(false);
					job_processing.setSplitSize(0); 

					break readloop; 
				}

				in.read(parse12, 0, 12);

				if (parse12[0] != 0x53 || parse12[1] != 0x50) // find "SP"
				{
					if (Message_2 && !missing_syncword)
						Common.setMessage(Resource.getString("subpicture.msg.syncword.lost") + " " + count);

					missing_syncword = true;
					count++;

					in.unread(parse12, 1, 11);

					continue readloop;
				}

				if (Message_2 && missing_syncword)
					Common.setMessage(Resource.getString("subpicture.msg.syncword.found") + " " + count);

				in.unread(parse12, 0, 12);

				missing_syncword = false;

				packetlength = ((0xFF & parse12[10])<<8 | (0xFF & parse12[11])) + 10;
				packet = new byte[packetlength];

				in.read(packet);
				count += packetlength;

				source_pts = 0;

				for (int a = 0; a < 5; a++) // 5bytes for pts, maybe wrong
					source_pts |= (0xFFL & packet[2+a])<<(a*8);

				//DM15072004 081.7 int06 add, use add. time offset if not applied by class-piddemux (e.g. ES)
				if (filename_pts.equals("-1"))
					source_pts += pts_offset;

			//	if (source_pts == pts_offset) // first pts would be 0, ever
				if (source_pts == pts_offset && first_pts != -1)
					source_pts = last_pts;

				else
					last_pts = source_pts;

				//remember pts of 1st packet
				if (first_pts == -1)
					first_pts = source_pts;


				if (debug)
					System.out.println(" " + (count - packetlength) + "/ " + packetlength + "/ " + source_pts);

				if (ptsdata)
				{
					write = !vptsdata;

					rangeloop:
					while (vptsdata && v < vptsval.length)  //pic_start_pts must be in range ATM
					{ 
						if (source_pts < vptsval[v])
							break rangeloop;

						else if (source_pts == vptsval[v] || source_pts < vptsval[v + 1])
						{
							write = true;
							break rangeloop;
						}

						v += 2;

						if (v < vptsval.length)
							time_difference += (vptsval[v] - vptsval[v-1]);
					}
				}
				else
					write = true;

				if (!vptsdata && time_difference == 0 && !KeepOriginalTimecode)
					time_difference = source_pts;

				new_pts = source_pts - time_difference;

				if ((display_time = subpicture.decode_picture(packet, 10, Common.getGuiInterface().isSubpictureVisible(), job_processing.getStatusStrings(), new_pts, write, Common.getGuiInterface().isSubpictureVisible())) < -2)
					Common.setMessage(Resource.getString("subpicture.msg.error", subdecode_errors[Math.abs((int)display_time)], String.valueOf(count - packetlength)));

				if (debug)
					System.out.println("PTS: source " + Common.formatTime_1(source_pts / 90) + "(" + source_pts + ")" + " /new " + Common.formatTime_1(new_pts / 90) + "(" + new_pts + ")" + " / write: " + write + " / dec.state: " + display_time);

				if (display_time < 0)  //dvb_subpic
				{
					if (!DVBpicture)
						Common.setMessage(Resource.getString("subpicture.msg.dvbsource"));

					DVBpicture = true;

					if (display_time == -1) // -1 full data, -2 forced end_time
						process_dvbsubpicture(job_processing, print_out, out, subpicture, subfile, new_pts);
				}

				else if (write) //dvd_subpic
				{
					for (int a = 0; a < 8; a++)
						packet[2 + a] = (byte)(0xFFL & new_pts>>>(a * 8));

					//later, to allow overlapping on cut boundaries 
					//if (display_time > 0)
					//	packet = subpicture.setTime(packet,display_time);

					out.write(packet);

					Common.getGuiInterface().showExportStatus(Resource.getString("subpicture.status"), ++Pictures);
					Common.getGuiInterface().setSubpictureTitle(" " + Resource.getString("subpicture.preview.title.dvdexport", "" + Pictures, Common.formatTime_1(new_pts / 90)) + " " + Common.formatTime_1(display_time / 90));

					String str = subpicture.isForced_Msg();

					if (str != null)
						Common.setMessage(str + " " + Resource.getString("subpicture.msg.forced") + " " + Pictures);
				}

				else
					Common.getGuiInterface().setSubpictureTitle(" " + Resource.getString("subpicture.preview.title.noexport"));

				if (debug)
				{
					System.out.println("-> wr " + write + " /v " + v + " /npts " + new_pts + " /tdif " + time_difference + " /pic " + Pictures + " /dtim " + display_time);
					System.out.println("");
				}
			}

			// check whether there is still a pic waiting from dvb subpic , assume max 10sec display time
			if (display_time == -2)
			{
			//	if ((display_time = subpicture.decode_picture(packet, 10, Common.getGuiInterface().isSubpictureVisible(), job_processing.getStatusStrings(), new_pts + 1024000, write, Common.getGuiInterface().isSubpictureVisible())) < -2)
				if ((display_time = subpicture.decode_picture(packet, 10, Common.getGuiInterface().isSubpictureVisible(), job_processing.getStatusStrings(), -1, write, Common.getGuiInterface().isSubpictureVisible())) < -2)
					Common.setMessage(Resource.getString("subpicture.msg.error", subdecode_errors[Math.abs((int)display_time)], String.valueOf(count - packetlength)));

				if (debug)
					System.out.println("last picture in memory PTS: source " + Common.formatTime_1(source_pts / 90) + "(" + source_pts + ")" + " /new " + Common.formatTime_1((new_pts) / 90) + "(" + new_pts + ")" + " / write: " + write + " / dec.state: " + display_time);

				if (display_time == -1)
					process_dvbsubpicture(job_processing, print_out, out, subpicture, subfile, new_pts);
			}

			in.close();

			print_out.flush();
			print_out.close();

			out.flush(); 
			out.close();

			if (filename_pts.equals("-1"))
				Common.setMessage(Resource.getString("subpicture.msg.pts.start_end", Common.formatTime_1(first_pts / 90)) + " " + Common.formatTime_1(source_pts / 90));

			Common.setMessage(Resource.getString("subpicture.msg.summary", "" + Pictures));

			if (!DVBpicture && ExportType == 1)
			{
				String renamed_file = subfile.substring(0, subfile.length() - 3) + "sup";
				Common.renameTo(subfile, renamed_file);
				subfile = renamed_file;
			}

			File subfile1 = new File(subfile); 

			if (Pictures == 0) 
				subfile1.delete();

			else
			{ 
				if (DVBpicture && ExportType == 0)
				{
				  if (ColorAreas.active)  			       //multicolor DVB to SUP active                                       //S9
					job_processing.countMediaFilesExportLength(Ifo.createIfo(subfile, ColorAreas.clut_pgc));                    //S9
				  else
					job_processing.countMediaFilesExportLength(Ifo.createIfo(subfile, subpicture.getUserColorTableArray()));    //S9
				}
				else if (DVBpicture && ExportType == 1)
					job_processing.countMediaFilesExportLength(new File( BMP.write_ColorTable(FileParent, subpicture.getUserColorTable(), 256)).length());

				Common.setMessage(Resource.getString("msg.newfile") + " " + subfile);
				job_processing.countMediaFilesExportLength(subfile1.length());
				job_processing.addSummaryInfo(Resource.getString("subpicture.summary", Common.adaptString(job_processing.countPictureStream(), 2), "" + Pictures, infoPTSMatch(filename_pts, videofile_pts, vptsdata, ptsdata)) + "'" + subfile1 + "'");

				//vobsub
				if (ExportType == 0 && ExportAsVobSub)
					new Sup2VobSub(subfile, subpicture.getUserColorTableArray());
			}

			Common.updateProgressBar(size, size);

		} catch (IOException e2) { 

			Common.setExceptionMessage(e2);
		}

		if (ShowSubpictureWindow)
			Common.getGuiInterface().hideSubpicture();

		ColorAreas.active = false;                                     //S9
	}


	private void process_dvbsubpicture(JobProcessing job_processing, PrintStream print_out, IDDBufferedOutputStream out, Subpicture subpicture, String subfile, long new_pts)
	{
		try {
			String num = "00000" + Pictures;
			String outfile_base = FileParent + "_st" + num.substring(num.length() - 5);

			String key, object_id_str, outfile;
			int object_id;

			for (Enumeration e = BMP.getKeys(); e.hasMoreElements() ; )
			{
				key = e.nextElement().toString();

				object_id = Integer.parseInt(key);
				object_id_str = Integer.toHexString(object_id).toUpperCase();
				outfile = outfile_base + "p" + object_id_str;

				Bitmap bitmap = BMP.getBitmap(object_id);

				if (ExportType == 0)  //.sup
					out.write( subpicture.writeRLE(bitmap));

				else    //.son + .bmp
				{
					if (Pictures == 0)
					{
						String[] SONhead = Common.getTeletextClass().getSONHead(new File(subfile).getParent(), (long)CommonParsing.getVideoFramerate());

						for (int a=0; a < SONhead.length; a++) 
							print_out.println(SONhead[a]);
					}

					subpicture.updateUserColorTable(bitmap);
					outfile = BMP.buildBMP_palettized(outfile, bitmap, subpicture.getUserColorTable(), 256);

					job_processing.countMediaFilesExportLength(new File(outfile).length());

					int pgc_values = subpicture.setPGClinks();

					// a change in color_links
					if ((0xFFFF & pgc_values) != (0xFFFF & LastPGCSet))
					{
						String pgc_colors = "";

						for (int a = 0; a < 4; a++)
							pgc_colors += "" + (0xF & pgc_values>>>(a * 4)) + " ";

						print_out.println("Color\t\t(" + pgc_colors.trim() + ")");
					}

					// a change in alpha_links
					if ((0xFFFF0000 & pgc_values) != (0xFFFF0000 & LastPGCSet))
					{
						String pgc_alphas = "";

						for (int a = 0; a < 4; a++)
							pgc_alphas += "" + (0xF & pgc_values>>>((4 + a) * 4)) + " ";

						print_out.println("Contrast\t(" + pgc_alphas.trim() + ")");
					}

					LastPGCSet = pgc_values;

					print_out.println("Display_Area\t(" + Common.adaptString(bitmap.getX(), 3) + " " + Common.adaptString(bitmap.getY(), 3) + " " + Common.adaptString(bitmap.getMaxX(), 3) + " " + Common.adaptString(bitmap.getMaxY(), 3) + ")");
					print_out.println(outfile_base.substring(outfile_base.length() - 4) + "\t\t" + Common.formatTime_2(bitmap.getInTime() / 90, (long)CommonParsing.getVideoFramerate()) + "\t" + Common.formatTime_2((bitmap.getInTime() / 90) + (bitmap.getPlayTime() * 10), (long)CommonParsing.getVideoFramerate()) + "\t" + new File(outfile).getName());

					if (debug)
						System.out.println("-> " + outfile);
				}

				Common.getGuiInterface().setSubpictureTitle(" " + Resource.getString("subpicture.preview.title.dvbexport", "" + bitmap.getPageId(), "" + Pictures, Common.formatTime_1(new_pts / 90)) + " " + Common.formatTime_1(bitmap.getPlayTime() * 10));
			}

			if (!BMP.isEmpty())
				Common.getGuiInterface().showExportStatus(Resource.getString("subpicture.status"), ++Pictures);

			BMP.clear();

		} catch (IOException e) { 

			Common.setExceptionMessage(e);
		}
	}
}
