//----------------------------------------------------------------------
// RzImageAsVideoTC , by regzamod
// play(transcode) image as video
//----------------------------------------------------------------------
package net.pms.encoders;

import java.awt.Font;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.List;
import java.lang.Math;

import javax.swing.JComponent;
import javax.swing.JTextField;

import net.pms.Messages;
import net.pms.PMS;
import net.pms.dlna.DLNAMediaInfo;
import net.pms.dlna.DLNAMediaAudio;
import net.pms.dlna.DLNAMediaSubtitle;
import net.pms.dlna.DLNAResource;
import net.pms.formats.Format;
import net.pms.io.OutputParams;
//import net.pms.io.PipeIPCProcess;
import net.pms.io.PipeProcess;
import net.pms.io.ProcessWrapper;
import net.pms.io.ProcessWrapperImpl;
import net.pms.network.HTTPResource;
import net.pms.util.ProcessUtil;
import net.pms.configuration.PmsConfiguration;
import net.pms.configuration.RendererConfiguration;

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

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

public class RzImageAsVideoTC extends Player {
	private static final Logger logger = LoggerFactory.getLogger(RzImageAsVideoTC.class);
	public static final String ID = "rz_image_as_video_tc";
	private final PmsConfiguration configuration;
	private double clip_itvl;

	@Override
	public int purpose() {
		return VIDEO_SIMPLEFILE_PLAYER;
		//return IMAGE_SIMPLEFILE_PLAYER;
	}
	
	@Override
	public String id() {
		return ID;
	}
	
	@Override
	public boolean isTimeSeekable() {
		return true;
	}

	@Override
	public boolean avisynth() {
		return false;
	}

	private String overriddenArgs[];
	private String aav_params[];
	
	public RzImageAsVideoTC(PmsConfiguration configuration) {
		this.configuration = configuration;
		this.clip_itvl=configuration.getRZ_iav_clip_interval();
	}
	
	@Override
	public String name() {
		return "RzImageAsVideoTC";
	}

	@Override
	public int type() {
		return Format.VIDEO;
	}
	
	protected String[] getDefaultArgs() {
		return new String[]{
			"dummy",
		};
	}
	
	@Override
	public String[] args() {
		return getDefaultArgs();
	}

	public boolean mplayer() {
		return false;
	}

	@Override
	public String mimeType() {
		return HTTPResource.VIDEO_TRANSCODE;
	}

	@Override
	public String executable() {
		return PMS.getConfiguration().getFfmpegPath();
	}

	@Override
	public ProcessWrapper launchTranscode(
		String image_in,
		DLNAResource dlna,
		DLNAMediaInfo media,
		OutputParams params) throws IOException {
		
		RendererConfiguration renderer=params.mediaRenderer;
		//---- tuning params for start playing 
		//params.manageFastStart();
		params.manageStart(renderer.getRZ_IavStartWait());  //set default
		
		String s;
		//---- create output pipe
		String sfx=".mpg";
		if(renderer.getRZ_TransVFormat()==PMS.TRS_M2TS) {
			sfx=".m2ts";
		}
		PipeProcess pipe = new PipeProcess("iav_trans_out" + System.currentTimeMillis() + sfx);
		params.input_pipes[0] = pipe;
		
		//---- setup params for the transcoder
		// RzIAVplayer.py {image_in} {video_out} [options]
		
		String path=configuration.getRZ_iav_player_path();
		String[] paths=path.split(",");
		List<String> plist = new ArrayList<String>();
		
		//---- set main args (progpath)----------
		if(paths.length>1) {
			for (int i=0;i<paths.length;i++) {
				if(i==0 && paths[i].trim().equals("$python_path$")) {
					paths[i]=configuration.getRZ_python_path();
					//PMS.dbg("RzImageAsVideoTC: got python_path="+paths[i]);
				}
				if(paths[i].length()>0) plist.add(paths[i].trim());
			}
		}
		else {
			plist.add(path);
		}

		//---- image in ---------
		int	img_pos=0;
		int auto_play=0;
		String play_type=PMS.get().getFrame().getRza().getPlayTypeIAV();
		//double dur=configuration.getRZ_iav_clip_interval();
		double dur_ttl=0;  //total play time in list

		if(play_type.equalsIgnoreCase("slideshow")) {	//slideshow
			//get image list
			auto_play=1; //slideshow
			File f = new File(configuration.getTempFolder(), "imagelist("+params.sess.id+").dat");
			//PrintWriter pw = new PrintWriter(f,configuration.getRZ_MetafileEncode());
			PrintWriter pw = new PrintWriter(f,"UTF-8");  //force encoding utf-8
			DLNAResource pa=dlna.getParent();
			int i=0;
			for (DLNAResource ch : pa.getChildren()) {
				if(ch.isFolder()) continue;
				if(ch.getExt()!=null && ch.getExt().isImage()) {
					if(dlna==ch) {
						//PMS.dbg("rz_ImageAsVideoTC: found img_pos="+i);
						img_pos=i;
					}
					//pw.println(ch.getSystemName());  // use shortName(getShortFileNameIfWideChars)
					pw.println(ch.getSrcPath());
					dur_ttl+=clip_itvl;
					i+=1;
				}
			}
			plist.add(f.getAbsolutePath());
			pw.close();
		}
		else {
			plist.add(image_in);
		}
		
		//---- video out ---------
		plist.add(pipe.getInputPipe());
		
		//---- audio clip path ---------
		/*
		s=configuration.getRZ_iav_clip_path1();
		if(s!=null) {
			plist.add("--clippath");
			plist.add(s);
		}
		s=configuration.getRZ_iav_clip_path2();
		if(s!=null) {
			plist.add("--clippath2");
			plist.add(s);
		}
		*/
		//s=params.sess.getRoot().audio_clip_path;
		s=params.sess.getRoot().getCurrentClipPath_a();
		s=ProcessUtil.getShortFileNameIfWideChars(s);  //to avoid unicode in path_name
		if(s!=null) {
			plist.add("--clippath");
			plist.add(s);
		}
		
		//---- set other options ----------
		plist.add("--temp");
		plist.add(configuration.getTempFolder().getAbsolutePath());
		plist.add("--sess_id");
		plist.add(""+params.sess.id);
			
		plist.add("--img_pos");
		plist.add(""+img_pos);
		plist.add("--seltype");
		plist.add(play_type);
		plist.add("--selintvl");
		plist.add(""+configuration.getRZ_iav_clip_interval());

		plist.add("--selfit");
		plist.add(""+configuration.getRZ_iav_imgfit());

		plist.add("--dpsize_w");
		plist.add(""+configuration.getRZ_aav_resize_w());
		plist.add("--dpsize_h");
		plist.add(""+configuration.getRZ_aav_resize_h());
		plist.add("--caption");
		plist.add((configuration.isRZ_iav_caption()?"1":"0"));
		plist.add("--gapless");
		plist.add((configuration.isRZ_iav_gapless()?"1":"0"));
		plist.add("--h264");
		plist.add((renderer.isMuxH264MpegTS()?"1":"0"));
		plist.add("--m2ts");
		//plist.add((renderer.isRZ_TranscodeToM2ts()?"1":"0"));
		//plist.add((renderer.getRZ_TransFormat().equals("m2ts")?"1":"0"));
		plist.add((renderer.getRZ_TransVFormat()==PMS.TRS_M2TS?"1":"0"));
		plist.add("--asp_hack");
		plist.add(""+renderer.getRZ_AspectHack());
		plist.add("--filename");
		plist.add(dlna.getName());	//image_in may be Windows ShortNamed
		//PMS.dbg("filename="+dlna.getName());
		
		String str=null;
		if(media!=null) {
			if(media.getAudioCodes().size() > 0) {
				DLNAMediaAudio audio=media.getAudioCodes().get(0);
				if(audio!=null) {
					str=audio.getGenre();
					if(str!=null && !str.isEmpty()) {
						plist.add("--genre");
						plist.add(str);
					}
					str=audio.getArtist();
					if(str!=null && !str.isEmpty()) {
						plist.add("--artist");
						plist.add(str);
					}
					str=audio.getSongname();
					if(str!=null && !str.isEmpty()) {
						plist.add("--title");
						plist.add(str);
					}
				}
			}			
		}
		//---- opt args from scripts
		if(params.sess.rz_SysMetaIAVOpt!=null && params.sess.rz_SysMetaIAVOpt.length()>0) {	
			String [] sopts=params.sess.rz_SysMetaIAVOpt.split(",");
			for(String s1 :sopts) {
				if(s1.startsWith("--")) {
					//may be OptString type ex. "--opt1 arg1"
					String[] sm=s1.split("[\\s]+");
					for(String s2 :sm) {
						plist.add(s2);
					}
				}
				else plist.add(s1);
			}
		}
		
		double timeseek=params.timeseek;
		double timeseek_sv=timeseek;
		if(auto_play>0) {  //slideshow (auto repeat)
			int stt_pos=img_pos;  //disignated from terminal 
			float auto_play_igap=0;
			
			plist.add("--auto_play");
			plist.add(""+auto_play);

			//--------------------------------------------------------------------------
			// Judge play pos(next/prev) in autoPlay list
			//--------------------------------------------------------------------------
			// ToDO : [stt_pos1_sb] should be real last played pos on player side.(_sb means Seek_Base)
			//        b/c following [stt_pos1_sb] calced by server side is not accurate.
			//        especially, when some audio's duration in list are unknown.
			// HowTo: Read lastPlayPos info from player's log, or do whole followings on player side
			//--------------------------------------------------------------------------
			if(renderer.getRZ_IAV_AutoPlayTrics()>0) {  //test for play next/prev operation
				if(PMS.rz_trace>1) {
					PMS.trace("==== RzImageAsVideoTC: RZ_IAV_AutoPlayTrics ON");
					PMS.trace("RzImageAsVideoTC: TrickJudge Start"
						+", pos0(terminal_new)="     +stt_pos
						+", pos0(terminal_cur)="     +params.sess.playPos0
						+", pos1(inner_cur)="        +params.sess.playPos1
						+", pos1(inner_base)="       +params.sess.playPos1_base
						+", params.timeseek(sec)="   +(timeseek)
						+", sess.playTime(sec)="     +(params.sess.playTime/1000.0)
						+", sess.playTimeTotal(sec)="+(params.sess.playTimeTotal/1000.0)
						+", sess.seek_bias="+(params.sess.seek_bias)
					);
				}

				if(params.sess.playPos0<0) params.sess.playPos0=stt_pos;
				if(params.sess.playPos1<0) params.sess.playPos1=stt_pos;
				if(params.sess.playPos1_base<0) params.sess.playPos1_base=stt_pos;
				
				//--- real pos on list
				int dir=stt_pos-params.sess.playPos0;  // =0: same pos, >0:next, <0: prev
				int stt_pos1;	 // new pos as the result of seek
				int stt_pos1_sb; // base pos for seek
				if(dir!=0) {// next/prev
					stt_pos1_sb=params.sess.playPos1;   	//current PlayPos on serverSide
				}
				else {// pause/skip
					stt_pos1_sb=params.sess.playPos1_base;	//current BasePos on serverSide
				}
				stt_pos1=stt_pos1_sb;  // set fale safe
				
				if(true) {
					//---- this method is good for auto_play=1(serial)
					//---- but, for auto_play=2(shuffle): this can't handle pause/restart, think better way 
					if(params.sess.playTimeTotal>60*1000) {  
						//total playtime from autoPlay started, to ignore short preview play 
						long new_seek[]={params.sess.playTime};
						double seek_add=0,seek=0;
						
						if(dir!=0) { // next/prev
							seek=params.sess.playTime/1000.0 - params.sess.seek_bias;
						}
						else {  // pause/skip
							seek=timeseek - params.sess.seek_bias;;
						}
						if(seek>dur_ttl) {  //cycled in list
							double seek0=seek;
							seek=Math.IEEEremainder(seek,dur_ttl);  //seek in last cycle 
							seek_add=seek0-seek;  //memory cycles
						}
						
						stt_pos1=getNextImage(dlna,stt_pos1_sb,dir,seek,auto_play_igap,new_seek);
						new_seek[0] +=seek_add;  //recover cycles
						
						if(dir!=0) { // next/prev
							params.sess.playTime=0;  //playtime from current audio
							params.sess.seek_bias=0;
							params.sess.playPos1_base=stt_pos1;
						}
						else { // pause/skip
							timeseek -= (new_seek[0]/1000.0);   
						}
						params.sess.playPos0=stt_pos;
						params.sess.playPos1=stt_pos1;
					}
					else {
						stt_pos1=stt_pos;
						params.sess.playPos0=stt_pos;
						params.sess.playPos1=stt_pos1;
					}
					if(timeseek_sv>0) {  //original timeseek
						//---- Only for IAV, memory seek miss-match  
						//---- b/c currently IAV player can't accurate timeseek and bigin clip_itvl*n near the timeseek
						double delta=Math.IEEEremainder(timeseek,clip_itvl);
						params.sess.seek_bias=delta;  // timeseek- clip_itvl*n  

						//---- calib playTime by timeseek
						double diff=(long)(timeseek_sv*1000)-params.sess.playTime;
						params.sess.playTime=(long)(timeseek_sv*1000);
						params.sess.playTimeTotal+=diff;
					}
				}
				plist.add("--auto_play_spos");
				plist.add(""+stt_pos1); 
				
				if(PMS.rz_trace>1) {
					PMS.trace("RzImageAsVideoTC: TrickJudge End "
						+", pos0(terminal_new)="     +stt_pos
						+", pos0(terminal_cur)="     +params.sess.playPos0
						+", pos1(inner_cur)="        +params.sess.playPos1
						+", pos1(inner_base)="       +params.sess.playPos1_base
						+", params.timeseek(sec)="   +(timeseek)
						+", sess.playTime(sec)="     +(params.sess.playTime/1000.0)
						+", sess.playTimeTotal(sec)="+(params.sess.playTimeTotal/1000.0)
						+", sess.seek_bias="+(params.sess.seek_bias)
					);
				}
				
			}
			else {
				plist.add("--auto_play_spos");
				plist.add(""+stt_pos);  //no change pos
			}
		}
		if(timeseek>0) {
			plist.add("--timeseek");
			plist.add(""+params.timeseek);
		}
		
		//---------------------------------------------------------------------------
		// Now, all params fixed: Let's kick player
		//---------------------------------------------------------------------------
		//---- set to cmdarray
		String cmdArray[] = plist.toArray(new String[plist.size()]);

		//---- setup pipe process
		ProcessWrapper mkfifo_process = pipe.getPipeProcess();

		//---- setup transcoder process
		cmdArray = finalizeTranscoderArgs(
			this,
			image_in,
			dlna,
			media,
			params,
			cmdArray);

		ProcessWrapperImpl pw = new ProcessWrapperImpl(cmdArray, params);
		pw.attachProcess(mkfifo_process);
		
		//---- run pipe process
		mkfifo_process.runInNewThread();
		try {
			Thread.sleep(50);
		} catch (InterruptedException e) {
		}
		pipe.deleteLater();

		//---- run transcoder process
		pw.runInNewThread(dlna);
		try {
			Thread.sleep(50);
		} catch (InterruptedException e) {
		}
		return pw;
	}

	//get next image to play : stt_pos + seek in audio list
	private int getNextImage (DLNAResource dlna, int stt_pos, int dir, double seek, float igap, long new_seek[]) {
		DLNAResource pa=null,ch;
		DLNAMediaInfo media=null;
		DLNAMediaAudio audio=null;
		int pos=0,apos=0,apos_prev=0;
		int cnt=0;
		double ttime=0,ttime_prev=0,ttime_prev2=0;

		if(PMS.rz_trace>1) PMS.trace("getNextImage: Start, stt_pos="+stt_pos+", dir="+dir+", seek(sec)="+seek+", igap(sec)="+igap);
		
		if(dlna==null) return stt_pos;
		pa=dlna.getParent();
		if(pa==null) return stt_pos;
		int lmax=pa.getChildren().size();
		boolean found=false;
		int found2=0;
		
		while (true) {
			if(PMS.rz_debug>1) PMS.dbg("getNextImage: pos="+pos);
			ch=pa.getChildren().get(pos);
			if(!found && apos>=stt_pos) {
				found=true;  //target pos found
			}
			if(!ch.isFolder() && ch.getExt()!=null && ch.getExt().isImage()) {
				if(found) {
					//set duration(sec) default, for that have no mediainfo, ex. web contents
					//long dur enable next/prev ope, even if accurate dur is unknown
					ttime_prev2=ttime_prev;
					ttime_prev=ttime;
					ttime+=clip_itvl;
					
					if(PMS.rz_debug>1) {
						PMS.dbg("getNextImage:"
							+" found image in list, apos="+apos
							+", clip_itvl(sec)="+(clip_itvl)
							+", total time(sec)="+ttime
						);
					}
					
					if(dir==0) {  //same pos
						if(ttime>seek) {
							ttime=ttime_prev;   //current pos
							break;
						}
					}
					else if(dir>0) {   //next pos
						if(found2>0) {
							ttime=ttime_prev;
							break;
						}
						else if(ttime>seek) {
							found2=1;  //target's next pos found: break on next pos
						}
					}
					else {  //prev pos
						if(ttime>seek) {
							apos=apos_prev;	//get prev audio pos
							ttime=ttime_prev2;  
							break;
						}
					}
				}
				apos_prev=apos;
				apos++;
			}
			pos++;
			if(pos>=lmax) {
				apos=0;  //pos in audio children
				pos=0;   //pos in all children
			}
			cnt++;
			if(cnt>lmax*2) {   //fale safe, avoid infinit loop !! 
				if(PMS.rz_debug>1) PMS.dbg("getNextImage: apos not found, return original apos");
				apos=stt_pos;  //recover
				break;
			}
		}
		if(new_seek!=null) {
			new_seek[0]=(long)(ttime*1000);  //set to target audio's start time in audio list
		}
		return apos;
	}

	@Override
	public JComponent config() {
		return null;
	}
}
