/*
 * 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.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;

import net.pms.PMS;

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

import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;

public class WindowsNamedPipe extends Thread implements ProcessWrapper {
	private static final Logger logger = LoggerFactory.getLogger(WindowsNamedPipe.class);

	/**
	 * Size for the buffer used in defining pipes for Windows in bytes. The buffer is used
	 * to copy from memory to an {@link java.io.OutputStream OutputStream} such as
	 * {@link net.pms.io.BufferedOutputFile BufferedOutputFile}.
	 */
	private static final int BUFSIZE = 500000;

	public interface Kernel32 extends StdCallLibrary {
		Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32",
			Kernel32.class
		);
		Kernel32 SYNC_INSTANCE = (Kernel32) Native.synchronizedLibrary(INSTANCE);

		class SECURITY_ATTRIBUTES extends Structure {
			public int nLength = size();
			public Pointer lpSecurityDescriptor;
			public boolean bInheritHandle;
		}

		public static class LPOVERLAPPED extends Structure {
		}

		Pointer CreateNamedPipeA(String lpName, int dwOpenMode, int dwPipeMode,
			int nMaxInstances, int nOutBufferSize, int nInBufferSize,
			int nDefaultTimeOut, SECURITY_ATTRIBUTES lpSecurityAttributes
		);

		boolean ConnectNamedPipe(Pointer handle, LPOVERLAPPED overlapped);

		boolean DisconnectNamedPipe(Pointer handle);

		boolean FlushFileBuffers(Pointer handle);

		boolean CloseHandle(Pointer handle);

		boolean ReadFile(Pointer hFile, Pointer lpBuffer,
			int nNumberOfBytesToRead, IntByReference lpNumberOfBytesRead,
			LPOVERLAPPED lpOverlapped
		);

		boolean WriteFile(Pointer hFile, Pointer lpBuffer,
			int nNumberOfBytesToRead, IntByReference lpNumberOfBytesRead,
			LPOVERLAPPED lpOverlapped
		);
	}

	private String name;
	private boolean in;
	private boolean forcereconnect;
	private Pointer handle1;
	private Pointer handle2;
	private OutputStream writable;
	private InputStream readable;
	private Thread forced;
	private boolean b2;
	private FileOutputStream debug;
	public static boolean loop = true;
	private BufferedOutputFile directBuffer;

	public WindowsNamedPipe(String n, boolean forcereconnect, boolean in, OutputParams params, boolean run) {
		this.name = n;
		this.name = "\\\\.\\pipe\\" + this.name;
		this.in = in;
		this.forcereconnect = forcereconnect;
		logger.trace("Creating pipe " + this.name);

		try {
			if (PMS.get().isWindows()) {
				handle1 = Kernel32.INSTANCE.CreateNamedPipeA(this.name, 3, 0, 255,
					BUFSIZE, BUFSIZE, 0, null);
				if (forcereconnect) {
					handle2 = Kernel32.INSTANCE.CreateNamedPipeA(this.name, 3, 0, 255,
						BUFSIZE, BUFSIZE, 0, null);
				}
				if (params != null) {
					directBuffer = new BufferedOutputFileImpl(params);
				} else {
					writable = new PipedOutputStream();
					readable = new PipedInputStream((PipedOutputStream) writable, BUFSIZE);
				}
				start();
				if (forcereconnect) {
					forced = new Thread(new Runnable() {
						public void run() {
							b2 = Kernel32.INSTANCE.ConnectNamedPipe(handle2, null);
						}
					});
					forced.start();
				}
				//debug = new FileOutputStream(n);
				//Thread.sleep(150);
			}
		} catch (Exception e1) {
			e1.printStackTrace();
		}
	}

	public void run() {
		logger.trace("Waiting for pipe connection " + this.name);
		boolean b1 = Kernel32.INSTANCE.ConnectNamedPipe(handle1, null);

		if (forcereconnect) {
			while (forced.isAlive()) {
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
				}
			}
			logger.trace("Forced reconnection of " + name + " with result : " + b2);
			handle1 = handle2;
		}

		logger.trace("Result of " + this.name + " : " + b1);

		try {
			if (b1) {
				if (in) {
					IntByReference ibr = new IntByReference();
					Memory buffer = new Memory(BUFSIZE);
					//while (loop) {
					while (loop && !PMS.getServerStopping()) {
						boolean fSuccess = Kernel32.INSTANCE.ReadFile(handle1,
							buffer, BUFSIZE, ibr, null);
						int cbBytesRead = ibr.getValue();
						if (cbBytesRead == -1) {
							if (directBuffer != null) {
								directBuffer.close();
							}
							if (writable != null) {
								writable.close();
							}
							if (debug != null) {
								debug.close();
							}
							break;
						}
						if (directBuffer != null) {
							directBuffer.write(buffer.getByteArray(0, cbBytesRead));
						}
						if (writable != null) {
							writable.write(buffer.getByteArray(0, cbBytesRead));
						}
						if (debug != null) {
							debug.write(buffer.getByteArray(0, cbBytesRead));
						}

						if (!fSuccess || cbBytesRead == 0) {
							if (directBuffer != null) {
								directBuffer.close();
							}
							if (writable != null) {
								writable.close();
							}
							if (debug != null) {
								debug.close();
							}
							break;
						}
					}
				} else {
					byte b[] = new byte[BUFSIZE];
					IntByReference ibw = new IntByReference();
					Memory buffer = new Memory(BUFSIZE);
					//while (loop) {
					while (loop && !PMS.getServerStopping()) {
						int cbBytesRead = readable.read(b);
						if (cbBytesRead == -1) {
							readable.close();
							if (debug != null) {
								debug.close();
							}
							break;
						}
						buffer.write(0, b, 0, cbBytesRead);
						boolean fSuccess = Kernel32.INSTANCE.WriteFile(handle1,
							buffer, cbBytesRead, ibw, null);
						int cbWritten = ibw.getValue();
						//logger.info(name + "fSuccess" + fSuccess + " cbWritten: " + cbWritten);
						if (debug != null) {
							debug.write(buffer.getByteArray(0, cbBytesRead));
						}
						if (!fSuccess || cbWritten == 0) {
							readable.close();
							if (debug != null) {
								debug.close();
							}
							break;
						}
					}
				}
			}
		} catch (IOException e) {
			logger.debug("Error: " + e.getMessage());
		}

		if(PMS.getServerStopping()) {  
			return;  //regzam, test for avoiding error_dump on server stop
		}
		if (!in) {

			logger.trace("Disconnected pipe: " + name);
			Kernel32.INSTANCE.FlushFileBuffers(handle1);
			Kernel32.INSTANCE.DisconnectNamedPipe(handle1);
		} else {
			//if(!PMS.getServerStopping()) {  //judge add, by regzam 
				Kernel32.INSTANCE.CloseHandle(handle1);
			//}
		}
	}

	public String getPipeName() {
		return name;
	}

	public OutputStream getWritable() {
		return writable;
	}

	public InputStream getReadable() {
		return readable;
	}

	public BufferedOutputFile getDirectBuffer() {
		return directBuffer;
	}

	@Override
	public InputStream getInputStream(long seek) throws IOException {
		return null;
	}

	@Override
	public ArrayList<String> getResults() {
		return null;
	}

	@Override
	public boolean isDestroyed() {
		return !isAlive();
	}

	@Override
	public void runInNewThread() {
	}

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

	@Override
	public void setReadyToStop(boolean nullable) {
	}

	@Override
	public void stopProcess() {
		interrupt();
	}
	public void closeHandle() {	//add, regzamod for test
		if(handle1!=null) {
			Kernel32.INSTANCE.CloseHandle(handle1);
		}
	}

}
