package jp.sourceforge.nicoro;

import static jp.sourceforge.nicoro.Log.LOG_TAG;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

import android.annotation.SuppressLint;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;

public class VideoProxyRtspServer extends LooperThread {
	private static final boolean DEBUG_LOGV = Release.IS_DEBUG & true;
	private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;

	private static final int MSG_ID_INITIALIZE_RTSP = 1;
	private static final int MSG_ID_ACCEPT_RTSP = 2;
	private static final int MSG_ID_PARSE_REQUEST = 3;
	private static final int MSG_ID_SEND_RESPONSE = 4;
	private static final int MSG_ID_ACCEPT_MEDIA = 5;
	private static final int MSG_ID_CLOSE = 6;

	public static final int PROXY_PORT = 52525;
	public static final int MEDIA_PORT = 52526;

	private ServerSocket mServerSocket;
	private Socket mSocket;
	private InputStream mSocketInputStream;
	private OutputStream mSocketOutputStream;

	private ServerSocket mServerSocketMedia;
	private Socket mSocketMedia;

	private VideoLoader mVideoLoader;

	private WeakReference<Handler> mRefHandlerOnPreparedServer;
	private int mWhatOnPreparedServer;

	public VideoProxyRtspServer(VideoLoader videoLoader) {
		super("RTSP-ProxyServer");
		mVideoLoader = videoLoader;
	}

	// LooperThread

	@Override
	public boolean handleMessage(Message msg) {
		switch (msg.what) {
		case MSG_ID_INITIALIZE_RTSP: {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "MSG_ID_INITIALIZE_RTSP");
			}
			try {
				ServerSocket serverSocket = new ServerSocket();
				mServerSocket = serverSocket;
				serverSocket.setReuseAddress(true);
				serverSocket.setSoTimeout(180 * 1000);
				serverSocket.bind(new InetSocketAddress("localhost", PROXY_PORT));
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, serverSocket.toString());
				}

				ServerSocket serverSocketMedia = new ServerSocket();
				mServerSocketMedia = serverSocketMedia;
				serverSocketMedia.setReuseAddress(true);
				serverSocketMedia.setSoTimeout(180 * 1000);
				serverSocketMedia.bind(new InetSocketAddress("localhost", MEDIA_PORT));
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, serverSocketMedia.toString());
				}

				Handler handlerOnPreparedServer =
					(mRefHandlerOnPreparedServer == null) ? null
							: mRefHandlerOnPreparedServer.get();
				if (handlerOnPreparedServer != null) {
					handlerOnPreparedServer.sendEmptyMessage(mWhatOnPreparedServer);
				}

				getHandler().sendEmptyMessage(MSG_ID_ACCEPT_RTSP);
			} catch (IOException e) {
				// TODO
				Log.e(LOG_TAG, e.toString(), e);
			}
		} break;
		case MSG_ID_ACCEPT_RTSP: {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "MSG_ID_ACCEPT_RTSP");
			}
			try {
				mSocket = mServerSocket.accept();
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, mSocket.toString());
				}

				mSocketInputStream = mSocket.getInputStream();
				mSocketOutputStream = mSocket.getOutputStream();

				getHandler().sendEmptyMessage(MSG_ID_PARSE_REQUEST);
			} catch (IOException e) {
				// TODO
				Log.e(LOG_TAG, e.toString(), e);
			}

		} break;
		case MSG_ID_PARSE_REQUEST: {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "MSG_ID_PARSE_REQUEST");
			}

			InputStream in = mSocketInputStream;

			try {
				String[] headers = readHeader(in);
				if (headers != null) {
					if (DEBUG_LOGD) {
						Log.d(LOG_TAG, "VideoProxyRtspServer: ===== request begin =====");
						for (String s : headers) {
							Log.d(LOG_TAG, s);
						}
						Log.d(LOG_TAG, "VideoProxyRtspServer: ===== request end =====");
					}

					getHandler().obtainMessage(MSG_ID_SEND_RESPONSE, headers).sendToTarget();
				} else {
					getHandler().sendEmptyMessage(MSG_ID_ACCEPT_RTSP);
				}
			} catch (IOException e) {
				// TODO
				Log.e(LOG_TAG, e.toString(), e);
			}
		} break;
		case MSG_ID_SEND_RESPONSE: {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "MSG_ID_SEND_RESPONSE");
			}

			OutputStream out = mSocketOutputStream;
			String[] headers = (String[]) msg.obj;

			try {
				String cseq = null;
				for (String s : headers) {
					if (s.startsWith("CSeq:")) {
						cseq = s;
						break;
					}
				}
				StringBuilder builder = new StringBuilder();

				if (headers[0].startsWith("OPTIONS ")) {
					builder.append("RTSP/1.0 200 OK\r\n");
					if (cseq != null) {
						builder.append(cseq).append("\r\n");
					}
					builder.append("Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n");
					builder.append("\r\n");
					String s = builder.toString();
					if (DEBUG_LOGD) {
						Log.d(LOG_TAG, s);
					}
					out.write(s.getBytes());

					getHandler().sendEmptyMessage(MSG_ID_PARSE_REQUEST);
				} else if (headers[0].startsWith("DESCRIBE ")) {
					builder.append("RTSP/1.0 200 OK\r\n");
					if (cseq != null) {
						builder.append(cseq).append("\r\n");
					}
					builder.append("Content-Type: application/sdp\r\n");
					builder.append("\r\n");

					builder.append("v=0\r\n");
					builder.append("o=user1 1 1 IN IP4 localhost\r\n");
					builder.append("s=NicoRo RTSP\r\n");
					builder.append("m=video ").append(MEDIA_PORT).append(" RTP/AVP 96\r\n");
					// TODO 自前でファイル解析しないと駄目？
					builder.append("a=rtpmap:96 mpeg4-generic/44100\r\n");
					builder.append("a=fmtp:96\r\n");
//					builder.append("c=IN IP4 localhost\r\n");
					builder.append("c=IN IP4 127.0.0.1/127\r\n");
					String s = builder.toString();
					if (DEBUG_LOGD) {
						Log.d(LOG_TAG, s);
					}
					out.write(s.getBytes());

					getHandler().sendEmptyMessage(MSG_ID_ACCEPT_RTSP);
//					getHandler().sendEmptyMessage(MSG_ID_ACCEPT_MEDIA);
				} else {

				}
				out.flush();

			} catch (IOException e) {
				// TODO
				Log.e(LOG_TAG, e.toString(), e);
			}
		} break;
		case MSG_ID_CLOSE: {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "MSG_ID_SEND_CLOSE");
			}
			if (mSocketInputStream != null) {
				try {
					mSocketInputStream.close();
				} catch (IOException e) {
					// TODO
					Log.e(LOG_TAG, e.toString(), e);
				}
				mSocketInputStream = null;
			}
			if (mSocketOutputStream != null) {
				try {
					mSocketOutputStream.close();
				} catch (IOException e) {
					// TODO
					Log.e(LOG_TAG, e.toString(), e);
				}
				mSocketOutputStream = null;
			}
			if (mSocket != null) {
				try {
					mSocket.close();
				} catch (IOException e) {
					// TODO
					Log.e(LOG_TAG, e.toString(), e);
				}
				mSocket = null;
			}
			if (mServerSocket != null) {
				try {
					mServerSocket.close();
				} catch (IOException e) {
					// TODO
					Log.e(LOG_TAG, e.toString(), e);
				}
				mServerSocket = null;
			}
			if (mSocketMedia != null) {
				try {
					mSocketMedia.close();
				} catch (IOException e) {
					// TODO
					Log.e(LOG_TAG, e.toString(), e);
				}
				mSocketMedia = null;
			}
			if (mServerSocketMedia != null) {
				try {
					mServerSocketMedia.close();
				} catch (IOException e) {
					// TODO
					Log.e(LOG_TAG, e.toString(), e);
				}
				mServerSocketMedia = null;
			}
		} break;
		case MSG_ID_ACCEPT_MEDIA: {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "MSG_ID_ACCEPT_MEDIA");
			}
			try {
				mSocketMedia = mServerSocketMedia.accept();
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, mSocketMedia.toString());
				}

			} catch (IOException e) {
				// TODO
				Log.e(LOG_TAG, e.toString(), e);
			}

		} break;
		default: {
			assert false : msg.what;
		} break;
		}

		return true;
	}


	public void startProxy() {
		start();
		getHandler().sendEmptyMessage(MSG_ID_INITIALIZE_RTSP);
	}

    @SuppressLint("NewApi")
	public void stopProxy() {
		getHandler().sendEmptyMessage(MSG_ID_CLOSE);
		quit();
	}

	public Uri getUri() {
		Uri uri = Uri.parse("rtsp://localhost:" + PROXY_PORT + "/file."
				+ mVideoLoader.getMovieType());
		if (DEBUG_LOGD) {
			Log.d(LOG_TAG, Log.buf().append("proxy url=")
					.append(uri.toString()).toString());
		}
		return uri;
	}

	public void registerOnPreparedServer(Handler handler, int what) {
		mRefHandlerOnPreparedServer = new WeakReference<Handler>(handler);
		mWhatOnPreparedServer = what;
	}

//	private InputStream createVideoLoaderWrapper(final VideoLoader videoLoader) {
//		return videoLoader.createInputStream();
//	}

	private String[] readHeader(InputStream in) throws IOException {
		ArrayList<String> array = new ArrayList<String>();
		byte[] buffer = new byte[2048];
		int i = 0;
		while (true) {
			int n = in.read();
			if (n < 0) {
				// TODO
				return null;
//				if (i > 0) {
//					String s = new String(buffer, 0, i, "UTF-8");
//					array.add(s);
//				}
//				break;
			}
			buffer[i] = (byte) n;
			if (i >= 1 && buffer[i - 1] == '\r' && buffer[i] == '\n') {
				if (i == 1) {
					// 改行２連続→ヘッダ終了
					break;
				}
				String s = new String(buffer, 0, i - 1, "UTF-8");
				array.add(s);
				i = 0;
			} else {
				++i;
			}
		}
		return array.toArray(new String[array.size()]);
	}
}
