/*
 * PS3 Media Server, for streaming any medias to your PS3.
 * Copyright (C) 2008  A.Brochard
 *
 * 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; version 2
 * of the License only.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package net.pms.io;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import net.pms.PMS;
import net.pms.encoders.AviDemuxerInputStream;
import net.pms.util.ProcessUtil;
import net.pms.dlna.DLNAResource;
import net.pms.configuration.PmsConfiguration;	//regzamod
import net.pms.io.BufferedOutputFileImpl;	//regzamod

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

public class ProcessWrapperImpl extends Thread implements ProcessWrapper {
	private static final Logger logger = LoggerFactory.getLogger(ProcessWrapperImpl.class);
	private PmsConfiguration configuration = PMS.getConfiguration();//regzamod

	@Override
	public String toString() {
		return super.getName();
	}

	private boolean success;
	private DLNAResource caller;	//regzamod

	public boolean isSuccess() {
		return success;
	}
	private boolean stopping=false;
	public boolean isStopping() {	//regzamod
		return stopping;
	}

	private String cmdLine;
	private Process process;
	private OutputConsumer outConsumer;
	private OutputConsumer stderrConsumer;
	private OutputParams params;
	private boolean destroyed;
	private String[] cmdArray;
	private boolean nullable;
	private ArrayList<ProcessWrapper> attachedProcesses;
	private BufferedOutputFile bo = null;
	private boolean keepStdout;
	private boolean keepStderr;
	private boolean absorb;	//regzamod
	private OutputConsumer textLogger;//regzamod

	public long getWriteCount() {  //regzamod
		if(bo==null) return 0;
		return bo.getWriteCount();
	}
	public long getReadCount() {  //regzamod
		if(bo==null) return 0;
		return bo.getReadCount();
	}
	public long[] getBufferedRangeByte() {  //regzamod
		if(bo==null) {
			long dum[]=new long[2];
			return dum;
		}
		return bo.getBufferedRangeByte();
	}
		
	public ProcessWrapperImpl(String cmdArray[], OutputParams params) {
		//this(cmdArray, params, false, false);
		this(cmdArray, params, false, false, false);	//regzamod
	}

	public ProcessWrapperImpl(String cmdArray[], OutputParams params, boolean keepOutput) {
		//this(cmdArray, params, keepOutput, keepOutput);
		this(cmdArray, params, keepOutput, keepOutput, false);	//regzamod
	}
	
	public ProcessWrapperImpl(String cmdArray[], OutputParams params, boolean keepStdout, boolean keepStderr) {
		//this(cmdArray, params, keepOutput, keepOutput);
		this(cmdArray, params, keepStdout, keepStderr, false);	//regzamod
	}

	//public ProcessWrapperImpl(String cmdArray[], OutputParams params, boolean keepStdout, boolean keepStderr) {
	public ProcessWrapperImpl(String cmdArray[], OutputParams params, boolean keepStdout, boolean keepStderr, boolean absorb ) {	//regzamod
		//regzamod, added absorb=true: Never output Stdout/Stderr to any 
		super(cmdArray[0]);
		File exec = new File(cmdArray[0]);

		if (exec.exists() && exec.isFile()) {
			cmdArray[0] = exec.getAbsolutePath();
		}

		this.cmdArray = cmdArray;
		StringBuilder sb = new StringBuilder("");

		for (int i = 0; i < cmdArray.length; i++) {
			if (i > 0) {
				sb.append(" ");
			}

			if (cmdArray[i] != null && cmdArray[i].indexOf(" ") >= 0) {
				sb.append("\"" + cmdArray[i] + "\"");
			} else {
				sb.append(cmdArray[i]);
			}
		}

		cmdLine = sb.toString();
		this.params = params;
		this.keepStdout = keepStdout;
		this.keepStderr = keepStderr;
		this.absorb = absorb;	//regzamod
		attachedProcesses = new ArrayList<ProcessWrapper>();
	}

	public void attachProcess(ProcessWrapper process) {
		attachedProcesses.add(process);
	}

	public void run() {
		ProcessBuilder pb = new ProcessBuilder(cmdArray);
		if(pb==null) {
			logger.error("ProcessWrapperImpl.run: ProcessBuilder Failed cmdArray="+cmdArray);
			return;
		}
		try {
			if(PMS.rz_debug>0) {	// regzamod
				//logger.debug("ProcessWrapperImpl.run: Starting " + cmdLine);
				PMS.dbg("====>> ProcessWrapperImpl.run: Starting " + cmdLine);
			}
			if (params.outputFile != null && params.outputFile.getParentFile().isDirectory()) {
				pb.directory(params.outputFile.getParentFile());
			}
			if (params.workDir != null && params.workDir.isDirectory()) {
				pb.directory(params.workDir);
			}
			process = pb.start();
			PMS.get().currentProcesses.add(process);
			stopping=false;	//regzamod
			
			if(absorb) {	//regzamod
				// real absorb without any buffering or logging !!
				stderrConsumer=new OutputBufferConsumer(process.getErrorStream(), null);
			}
			else {	//original
				stderrConsumer = keepStderr
					? new OutputTextConsumer(process.getErrorStream(), true)
					: new OutputTextLogger(process.getErrorStream());
			}
			stderrConsumer.start();
			
			outConsumer = null;
			if (params.outputFile != null) {
				logger.debug("Writing in " + params.outputFile.getAbsolutePath());
				
				if(absorb) {	//regzamod
					// real absorb without any buffering or logging !!
					outConsumer=new OutputBufferConsumer(process.getInputStream(), null); 
				}
				else {	//original
					outConsumer = keepStdout
						? new OutputTextConsumer(process.getInputStream(), false)
						: new OutputTextLogger(process.getInputStream());
				}
			} else if (params.input_pipes[0] != null) {
				logger.trace("Reading pipe: " + params.input_pipes[0].getInputPipe());
				bo = params.input_pipes[0].getDirectBuffer();
				if (bo == null || params.losslessaudio || params.lossyaudio || params.no_videoencode) {
					InputStream is = params.input_pipes[0].getInputStream();
					outConsumer = new OutputBufferConsumer((params.avidemux) ? new AviDemuxerInputStream(is, params, attachedProcesses) : is, params);
					bo = (BufferedOutputFile) outConsumer.getBuffer();
				}
				bo.attachThread(this);
				//new OutputTextLogger(process.getInputStream()).start();
				textLogger=new OutputTextLogger(process.getInputStream());	//regzamod
				textLogger.start();	//regzamod
			} else if (params.log) {
				outConsumer = keepStdout
					? new OutputTextConsumer(process.getInputStream(), true)
					: new OutputTextLogger(process.getInputStream());
			} else {
				outConsumer = new OutputBufferConsumer(process.getInputStream(), params);
				bo = (BufferedOutputFile) outConsumer.getBuffer();
				bo.attachThread(this);
			}
			if (params.stdin != null) {
				params.stdin.push(process.getOutputStream());
			}
			if (outConsumer != null) {
				outConsumer.start();
			}

			Integer pid = ProcessUtil.getProcessID(process);

			if (pid != null) {
				logger.debug("Unix process ID (" + cmdArray[0] + "): " + pid);
			}

			ProcessUtil.waitFor(process);

			try {
				if (outConsumer != null) {
					outConsumer.join(1000);
				}
			} catch (InterruptedException e) {
			}
			if (bo != null) {
				((BufferedOutputFileImpl)bo).setCanStop(true,0); //regzamod
				if(PMS.rz_debug>1) PMS.dbg("ProcessWrapperImpl.run: bo.close()");
				bo.close();
			}
		} catch (Exception e) {
			logger.error("Fatal error in process initialization: ", e);
			stopProcess();
			//stop();//regzamod, add test
		} finally {
			if (!destroyed && !params.noexitcheck) {
				try {
					success = true;
					if (process != null && process.exitValue() != 0) {
						logger.info("Process " + cmdArray[0] + " has a return code of " + process.exitValue() + "! Maybe an error occurred... check the log file");
						success = false;
						stopping=true;	//regzamod, nearly stopping
					}
				} catch (IllegalThreadStateException itse) {
					logger.error("An error occurred", itse);
				}
			}
			if (attachedProcesses != null) {
				for (ProcessWrapper pw : attachedProcesses) {
					if (pw != null) {
						pw.stopProcess();
						//((ProcessWrapperImpl)pw).stop1();//regzamod, add test
					}
				}
			}
			PMS.get().currentProcesses.remove(process);
		}
	}

	public void runInNewThread() {
		this.start();
	}
	
	public void runInNewThread(DLNAResource caller) {
		this.caller=caller;
		this.start();
	}
	
	//public void stop1() {	//regzamod test add
	//	this.stop();
	//}

	public InputStream getInputStream(long seek) throws IOException {
		if (bo != null) {
			return bo.getInputStream(seek);
		} else if (outConsumer != null && outConsumer.getBuffer() != null) {
			return outConsumer.getBuffer().getInputStream(seek);
		} else if (params.outputFile != null) {
			BlockerFileInputStream fIn = new BlockerFileInputStream(this, params.outputFile, params.minFileSize);
			fIn.skip(seek);
			return fIn;
		}
		return null;
	}

	public List<String> getOtherResults() {
		if (outConsumer == null) {
			return null;
		}
		try {
			outConsumer.join(1000);
		} catch (InterruptedException e) {
		}
		return outConsumer.getResults();
	}

	public List<String> getResults() {
		try {
			stderrConsumer.join(1000);
		} catch (InterruptedException e) {
		}
		return stderrConsumer.getResults();
	}

	public synchronized void stopProcess() {
		if (!destroyed) {
			destroyed = true;
			if (process != null) {
				Integer pid = ProcessUtil.getProcessID(process);
				if (pid != null) {
					logger.debug("Stopping Unix process " + pid + ": " + this);
				} else {
					logger.debug("Stopping process: " + this);
				}
				
				ProcessUtil.destroy(process);
			}

			if (attachedProcesses != null) {
				for (ProcessWrapper pw : attachedProcesses) {
					if (pw != null) {
						pw.stopProcess();
						//((ProcessWrapperImpl)pw).stop1();//regzamod, add test
					}
				}
			}
			if (outConsumer != null) {
				if(outConsumer.getBuffer() != null) {
					outConsumer.getBuffer().reset();
				}
			}
			
			if(bo!=null) {//regzamod
				((BufferedOutputFileImpl)bo).setCanStop(true,0);
			} 
			if((configuration.getRZ_ForceMemFree()&1)!=0) {//regzamod, add
				if(PMS.rz_debug>1) PMS.dbg("ProcessWrapperImpl.StopProcess: input_pipes[].close()");
				if(params.input_pipes[0]!=null) {
					params.input_pipes[0].close();
				}
				if(params.output_pipes[0]!=null) {
					params.output_pipes[0].close();
				}
				if(params.output_pipes[1]!=null) {
					params.output_pipes[1].close();
				}
			}
			if((configuration.getRZ_ForceMemFree()&2)!=0) {	//regzamod, add
				if(caller!=null) {
					//PMS.dbg("ProcessWrapperImpl: stopping, call caller.extProcEnded()");
					//may cause IOException etc. mal fanctions
					synchronized (caller) {
						caller.extProcEnded((ProcessWrapper)this);	//danger!!
					}
				}
				/*
				stderrConsumer.stop();//regzamod, add test
				if(textLogger!=null) textLogger.stop();//regzamod, add test
				if (outConsumer != null) {
					outConsumer=null; //yet using, set null will cause error!!
					outConsumer.stop();
				}
				stderrConsumer.stop();
				if(textLogger!=null) textLogger.stop();
				*/
			}
		}
	}

	public boolean isDestroyed() {
		return destroyed;
	}

	public boolean isReadyToStop() {
		return nullable;
	}

	public void setReadyToStop(boolean nullable) {
		if (nullable != this.nullable) {
			logger.trace("Ready to Stop: " + nullable);
		}
		this.nullable = nullable;
	}
	
	public boolean isRunning() {	//regzamod, add
	    try {
	        process.exitValue();
	        return false;
	    } catch (Exception e) {
	        return true;
	    }
	}
}
