package com.interpress_project.modernshare.svnserver;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;

import com.interpress_project.modernshare.AppKeys;
import com.interpress_project.modernshare.ipcommon.SystemBase;

public class RequestThread extends Thread {
	private final SystemBase sb = SystemBase.getInstance();
	private Socket requestSocket = null, tgtSocket = null;
	private int targetport = 0, tcpbufsz = 0, connectionTimeout = 0;
	private int total_count = 0, out_count = 0;
	private int bufsz = AppKeys.BUFFER_SZ; // Internal buffer size.
	private BufferedInputStream sock_bis = null, svnsock_bis = null;
	private BufferedOutputStream sock_bos = null, svnsock_bos = null;

	/**
	 * RequestThread
	 * @param socket
	 * @param tcpbufsz
	 * @param connectionTimeout
	 */
	public RequestThread(Socket socket, int targetport, int tcpbufsz, int connectionTimeout, int threadTimeout) {
		this.requestSocket = socket;
		this.targetport = targetport;
		this.tcpbufsz = tcpbufsz;
		this.connectionTimeout = connectionTimeout;

		try {
			this.requestSocket.setSendBufferSize(tcpbufsz); // No need to setup RecievedBufferSize.
			this.requestSocket.setKeepAlive(true);
			this.requestSocket.setTcpNoDelay(true);
			this.requestSocket.setSoTimeout(threadTimeout); // Timeout for deadlock.
		}
		catch (SocketException ex) {
			// Just ignore. 
		}
	}

	/**
	 * connect_server
	 * @param hostname
	 * @param port
	 * @throws SocketException
	 * @throws IOException
	 */
	private void connect_server(String hostname, int port) throws SocketException, IOException {
		sb.getLogger().debug("Enter connect_server().");
		/**
		 * アプリケーションが RFC1323 で定義されている 64K バイトを超える受信ウィンドウを
		 * 使用可能にする必要がある場合には、ローカルアドレスにバインドする前に 推奨値を
		 * ServerSocket で設定する必要があります。 
		 */
		InetSocketAddress endpoint = new InetSocketAddress(hostname, port);
		tgtSocket = new Socket();
		tgtSocket.setReceiveBufferSize(tcpbufsz);
		tgtSocket.setSendBufferSize(tcpbufsz);
		tgtSocket.setKeepAlive(true);
		tgtSocket.setTcpNoDelay(true);

		tgtSocket.connect(endpoint, connectionTimeout);

		svnsock_bis = new BufferedInputStream(tgtSocket.getInputStream(), tcpbufsz);
		svnsock_bos = new BufferedOutputStream(tgtSocket.getOutputStream(), tcpbufsz);

		sb.getLogger().debug("connect_server() is successful.");
	}

	/**
	 * run
	 */
	public void run() {
		try {
			_run();
		}
		finally {
			try {
				tgtSocket.close();
				tgtSocket = null;
			}
			catch (IOException ex) {
				sb.getLogger().error("IOException closing targetSocket: " + ex.toString());
			}
			try {
				requestSocket.close();
				requestSocket = null;
			}
			catch (IOException ex) {
				sb.getLogger().error("IOException closing requestSocket: " + ex.toString());
			}
			sb.getLogger().debug("Connection accepted done!");
		}
	}

	/**
	 * _run
	 */
	private void _run() {
		try {
			connect_server("127.0.0.1", targetport);
		}
		catch (SocketTimeoutException ex) {
			sb.getLogger().fatal("Unable to connect svnserve backend. Server is forcely terminated.", ex);
			System.exit(-1);
		}
		catch (SocketException ex) {
			sb.getLogger().fatal("SocketException connecting svnserve. Server is forcely terminated.", ex);
			System.exit(-1);
		}
		catch (IOException ex) {
			sb.getLogger().fatal("IOException connection svnserve. Server is forcely terminated.", ex);
			System.exit(-1);
		}

		try {
			sock_bis = new BufferedInputStream(requestSocket.getInputStream(), tcpbufsz);
			sock_bos = new BufferedOutputStream(requestSocket.getOutputStream(), tcpbufsz);
		}
		catch (IOException ex) {
			sb.getLogger().error("IOException creating buffer: ", ex);
			return;
		}
		StreamGobblerDst2Command t1 = new StreamGobblerDst2Command(sock_bis, svnsock_bos);
		t1.setDaemon(true);
		t1.start();

		StreamGobblerCommand2Dst t2 = new StreamGobblerCommand2Dst(svnsock_bis, sock_bos);
		t2.setDaemon(true);
		t2.start();

		try {
			t1.join();
			t2.join();
		}
		catch (InterruptedException ex) {
			sb.getLogger().debug("InterruptedException in thread join: ", ex);
		}
	}

	/**
	 * StreamGobblerDst2Command 
	 * @author ys
	 */
	private class StreamGobblerDst2Command extends Thread {
		private BufferedInputStream bis = null;
		private BufferedOutputStream bos = null;

		/**
		 * StreamGobblerDst2Command
		 * @param bis
		 * @param bos
		 */
		public StreamGobblerDst2Command(BufferedInputStream bis, BufferedOutputStream bos) {
			setStreams(bis, bos);
		}

		/**
		 * setStreams
		 * @param bis
		 * @param bos
		 */
		public void setStreams(BufferedInputStream bis, BufferedOutputStream bos) {
			this.bis = bis;
			this.bos = bos;
		}

		/**
		 * run
		 */
		public void run() {
			byte[] buffer = new byte[bufsz];
			int len = 0;

			while (true) {
				try {
					len = bis.read(buffer, 0, bufsz);
				}
				catch (SocketTimeoutException ex) {
					/**
					 * 接続した後、処理をすることなく停止状態に陥る場合がある。この際はbStarted2状況を確認して、
					 * 強制的にコマンドを終了させる。
					 */
					sb.getLogger().debug("(Read timeout)StreamGobblerDst2Comand read()  : " + ex.toString());
					if (total_count < out_count) {
						total_count = out_count;
						continue;
					}
					// Command2Dstスレッドが全く稼働していない場合
					sb.getLogger().error("(Read timeout) ALL THREADS SEEM NOT TO RUN, THREAD RECOVERY STARTED.");
					try {
						svnsock_bis.close();
					}
					catch (IOException ex2) {
						sb.getLogger().debug("IOException closing svnsock_bis: ", ex2);
					}
					break;
				}
				catch (Exception ex) {
					sb.getLogger().debug("StreamGobblerDst2Comand read() : " + ex.toString());
					break;
				}
				if (len == -1) {
					break;
				}

				try {
					bos.write(buffer, 0, len);
					bos.flush();
				}
				catch (Exception ex) {
					sb.getLogger().debug("StreamGobblerDst2Comand write/flush() : " + ex.toString());
					break;
				}
			} /* End Of while(true) loop. */

			try {
				sb.getLogger().debug("Enter close1");
				bos.flush();
				bos.close();
				bos = null;
			}
			catch (Exception ex) {
				sb.getLogger().error("StreamGobblerDst2Comand close(): " + ex.toString());
			}
			sb.getLogger().debug("StreamGobblerDst2Comand() exit peacefully.");
		}
	}

	/**
	 * StreamGobblerCommand2Dst
	 * svnserveからのデータはブロック化されているわけではないので受け取り側がbufszの大きさの
	 * バッファリングをする必要がある。
	 * @author ys
	 */
	private class StreamGobblerCommand2Dst extends Thread {
		private BufferedInputStream bis = null;
		private BufferedOutputStream bos = null;

		/**
		 * StreamGobblerCommand2Dst
		 * @param bis
		 * @param bos
		 */
		public StreamGobblerCommand2Dst(BufferedInputStream bis, BufferedOutputStream bos) {
			setStreams(bis, bos);
		}

		/**
		 * setStreams
		 * @param bis
		 * @param bos
		 */
		public void setStreams(BufferedInputStream bis, BufferedOutputStream bos) {
			this.bis = bis;
			this.bos = bos;
		}

		/**
		 * flushBuffer
		 * @param buffer
		 * @param sz
		 * @throws Exception
		 */
		private void flushBuffer(ByteBuffer buffer, int sz) throws Exception {
			if (sz > 0) {
				bos.write(buffer.array(), 0, sz);
				bos.flush();

				buffer.clear();
			}
		}

		/**
		 * run
		 */
		public void run() {
			boolean bClosed = false;
			ByteBuffer buffer = ByteBuffer.allocate(bufsz);
			int c, i;
			while (true) {
				for (i = 0; i < bufsz; i++) {
					try {
						if (bis.available() == 0) {
							flushBuffer(buffer, i);
							i = 0;
						}
						c = bis.read();
						++out_count;
					}
					catch (SocketException ex) {
						bClosed = true; // Just make flag on.
						sb.getLogger().debug("(IGNORABLE) StreamGobblerComand2Dst loop: " + ex.toString());
						break;
					}
					catch (Exception ex) {
						bClosed = true; // Just make flag on.
						sb.getLogger().error("Exception caught in StreamGobblerCommand2Dest(): " + ex.toString());
						break;
					}

					if (c == -1) {
						bClosed = true; // Just make flag on.
						break;
					}
					buffer.put(i, (byte) c);
				} /* End of for loop. */

				try {
					flushBuffer(buffer, i);
				}
				catch (Exception ex) {
					bClosed = true; // Just make flag on.
					sb.getLogger().error(ex);
				}

				if (bClosed) {
					break;
				}
			} /* End of while(true) loop. */

			// Command側だけを閉じる。これによってコマンドを終了させる。			
			try {
				sb.getLogger().debug("Enter close2");
				bis.close();
				bis = null;
			}
			catch (IOException ex) {
				sb.getLogger().error(ex);
			}
			sb.getLogger().debug("StreamGobblerComand2Dst() exit peacefully.");
		}
	}
}
