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.BindException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Date;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.Header;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.ParseException;
import org.apache.http.ProtocolVersion;
import org.apache.http.RequestLine;
import org.apache.http.StatusLine;
import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicLineFormatter;
import org.apache.http.message.BasicLineParser;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.ByteArrayBuffer;
import org.apache.http.util.EncodingUtils;

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

public class VideoProxyHttpServer 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 = 1;
	private static final int MSG_ID_RECEIVE_REQUEST = 2;
	private static final int MSG_ID_CLOSE = 3;

    private static final int PROXY_PORT_MIN = 49152;
    private static final int PROXY_PORT_MAX = 65535;

	static final byte[] CRLF = new byte[] { HTTP.CR, HTTP.LF };

	private ServerSocket mServerSocket;
	private ConcurrentLinkedQueue<SocketConnection> mSocketConnectionQueue =
		new ConcurrentLinkedQueue<SocketConnection>();
	private VideoLoaderInterface mVideoLoader;

	private WeakReference<Handler> mRefHandlerOnPreparedServer;
	private int mWhatOnPreparedServer;

    private WeakReference<Handler> mRefHandlerOnReconnectServer;
    private int mWhatOnReconnectServer;

    private int mProxyPort = 52525;

	public VideoProxyHttpServer(VideoLoaderInterface videoLoader) {
		super("HTTP-ProxyServer");
		mVideoLoader = videoLoader;
	}

	// LooperThread

	@Override
	public boolean handleMessage(Message msg) {
		Handler handler = getHandler();
		switch (msg.what) {
		case MSG_ID_INITIALIZE: {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "MSG_ID_INITIALIZE");
			}
			try {
				mServerSocket = createServerSocket();
				Handler handlerOnPreparedServer =
					(mRefHandlerOnPreparedServer == null) ? null
							: mRefHandlerOnPreparedServer.get();
				if (handlerOnPreparedServer != null) {
					handlerOnPreparedServer.sendEmptyMessage(mWhatOnPreparedServer);
				}

				handler.sendEmptyMessage(MSG_ID_RECEIVE_REQUEST);
			} catch (BindException e) {
			    // Address already in use 等
			    // TIME_WAIT時間（60秒）待ってみる？
                // TODO
                Log.e(LOG_TAG, e.toString(), e);
                handler.sendEmptyMessage(MSG_ID_CLOSE);
//                handler.sendEmptyMessage(MSG_ID_INITIALIZE);
			} catch (IOException e) {
				// TODO
				Log.e(LOG_TAG, e.toString(), e);
				handler.sendEmptyMessage(MSG_ID_CLOSE);
			}
		} break;
		case MSG_ID_RECEIVE_REQUEST: {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "MSG_ID_RECEIVE_REQUEST");
			}
			try {
				Socket socket = mServerSocket.accept();
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, socket.toString());
				}
				InetAddress socketAddress = socket.getLocalAddress();
				if (socketAddress.equals(mServerSocket.getInetAddress())) {
	                SocketConnection socketConnection =
	                    new SocketConnection(socket);
	                mSocketConnectionQueue.add(socketConnection);
	                socketConnection.start();
	                socketConnection.open();
				} else {
				    Log.i(LOG_TAG, "Local server is accessed by remote!");
				    try {
				        socket.close();
	                    if (DEBUG_LOGD) {
	                        Log.d(LOG_TAG, Log.buf().append("Close Socket: ")
	                                .append(socket.toString()).toString());
	                    }
				    } catch (IOException e) {
		                Log.e(LOG_TAG, e.toString(), e);
				    }
				}

				handler.sendEmptyMessage(MSG_ID_RECEIVE_REQUEST);
			} catch (SocketTimeoutException e) {
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, e.toString(), e);
				}
				// CLOSE待ちでないなら再度accept
				if (!handler.hasMessages(MSG_ID_CLOSE)) {
					handler.sendEmptyMessage(MSG_ID_RECEIVE_REQUEST);
				}
			} catch (IOException e) {
				// TODO
				Log.e(LOG_TAG, e.toString(), e);
                if (!handler.hasMessages(MSG_ID_CLOSE)) {
				    handler.sendEmptyMessage(MSG_ID_CLOSE);
				}
			}
		} break;
		case MSG_ID_CLOSE: {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "MSG_ID_CLOSE");
			}
			// エラーとstopProxyによって重複されて呼ばれることがあるのでキャンセル
			handler.removeMessages(MSG_ID_CLOSE);
			for (SocketConnection sc : mSocketConnectionQueue) {
				sc.close();
			}
			mSocketConnectionQueue.clear();
			assert mServerSocket != null;
			if (mServerSocket != null) {
				try {
					mServerSocket.close();
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, Log.buf().append("Close ServerSocket: ")
                                .append(mServerSocket.toString()).toString());
                    }
				} catch (IOException e) {
					Log.e(LOG_TAG, e.toString(), e);
				}
				mServerSocket = null;
			}
		} break;
		default: {
			assert false : msg.what;
		} break;
		}

		return true;
	}


	/**
	 * Proxyサーバー開始
	 */
	public void startProxy() {
		start();
		getHandler().sendEmptyMessage(MSG_ID_INITIALIZE);
	}

    /**
     * Proxyサーバー停止
     */
    @SuppressLint("NewApi")
	public void stopProxy() {
	    // 先に終了メッセージ登録しておかないと不要な警告ログが出る可能性
        getHandler().sendEmptyMessage(MSG_ID_CLOSE);
		// TODO accept中のスレッド止めるがいきなりclose()で良いか？
		ServerSocket serverSocket = mServerSocket;
		if (serverSocket != null) {
			try {
				serverSocket.close();
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, Log.buf().append("Close ServerSocket: ")
                            .append(serverSocket.toString()).toString());
                }
			} catch (IOException e) {
				Log.e(LOG_TAG, e.toString(), e);
			}
		}
		quit();
	}

	/**
	 * ProxyサーバーのUri取得
	 * @return
	 */
	public Uri getUri() {
		// /file名まで続かないとうまく認識してくれない
		Uri uri = Uri.parse("http://localhost:" + mProxyPort + "/file."
				+ mVideoLoader.getMovieType());
		if (DEBUG_LOGD) {
			Log.d(LOG_TAG, Log.buf().append("proxy url=")
					.append(uri.toString()).toString());
		}
		return uri;
	}

	/**
	 * サーバーの準備完了時に通知するよう登録
	 * @param handler {@link android.os.Message}の送り先
	 * @param what {@link android.os.Message#what}の指定
	 */
	public void registerOnPreparedServer(Handler handler, int what) {
		mRefHandlerOnPreparedServer = new WeakReference<Handler>(handler);
		mWhatOnPreparedServer = what;
	}

    /**
     * サーバーへの再接続発生時に通知するよう登録
     * @param handler {@link android.os.Message}の送り先
     * @param what {@link android.os.Message#what}の指定
     */
	public void registerOnReconnectServer(Handler handler, int what) {
	    mRefHandlerOnReconnectServer = new WeakReference<Handler>(handler);
	    mWhatOnReconnectServer = what;
	}

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

	private ServerSocket createServerSocket() throws IOException {
		final int startProxyPort = mProxyPort;
		while (true) {
		    ServerSocket serverSocket = null;
		    try {
		        serverSocket = new ServerSocket();
		        serverSocket.setReuseAddress(true);
		        serverSocket.setSoTimeout(180 * 1000);
        		InetSocketAddress address = new InetSocketAddress("localhost", mProxyPort);
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, Log.buf().append("Try created ServerSocket: ")
                            .append(address.toString()).toString());
                }
        		serverSocket.bind(address);
        		if (DEBUG_LOGD) {
        			Log.d(LOG_TAG, Log.buf().append("Created ServerSocket: ")
        			        .append(serverSocket.toString()).toString());
        		}
                return serverSocket;
		    } catch (BindException e) {
		        Log.i(LOG_TAG, e.toString(), e);
		        if (serverSocket != null) {
		            try {
		                serverSocket.close();
		                if (DEBUG_LOGD) {
		                    Log.d(LOG_TAG, Log.buf().append("Close ServerSocket: ")
		                            .append(serverSocket.toString()).toString());
		                }
		            } catch (IOException e2) {
		                Log.w(LOG_TAG, e2.toString(), e2);
		            }
		        }
		        // 別のport番号でリトライ。一周したらそのまま例外飛ばす
		        ++mProxyPort;
		        if (mProxyPort > PROXY_PORT_MAX) {
		            mProxyPort = PROXY_PORT_MIN;
		        }
		        if (mProxyPort == startProxyPort) {
		            throw e;
		        }
		    }
		}
	}

    static boolean hasKeepAlive(HttpRequest httpRequest) {
        Header headerConnection = httpRequest.getFirstHeader(
                HTTP.CONN_DIRECTIVE);
        if (headerConnection == null) {
            return false;
        }
        return HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(
                headerConnection.getValue());
    }

	private class SocketConnection extends LooperThread {
		private static final int MSG_ID_SC_RECEIVE_REQUEST = 0;
		private static final int MSG_ID_SC_SEND_RESPONSE_HEADER = 1;
		private static final int MSG_ID_SC_SEND_RESPONSE_ENTITY = 2;
		private static final int MSG_ID_SC_WRITE_STREAM = 3;
		private static final int MSG_ID_SC_SKIP_STREAM = 4;
		private static final int MSG_ID_SC_CLOSE = 5;

		private Socket mSocket;
		private InputStream mInputSocket;
		private OutputStream mOutputSocket;
		private InputStream mInputVideo;
		private byte[] mBuffer;
		private HttpRequest mHttpRequest;

		private int mRangeBegin = -1;
		private int mRangeEnd = -1;
		private int mReadLength = 0;

		public SocketConnection(Socket socket) {
			super("HTTP-ProxyServer-" + socket.toString());
			mSocket = socket;
		}

        @SuppressLint("NewApi")
		@Override
		public boolean handleMessage(Message msg) {
			switch (msg.what) {
			case MSG_ID_SC_RECEIVE_REQUEST: {
				if (DEBUG_LOGV) {
					Log.v(LOG_TAG, "MSG_ID_SC_RECEIVE_REQUEST");
				}
				try {
					mInputSocket = mSocket.getInputStream();
					HttpRequest httpRequest = parseHttpRequest(mInputSocket);
					mHttpRequest = httpRequest;
					String reqMethod = httpRequest.getRequestLine().getMethod();
					String reqUri = httpRequest.getRequestLine().getUri();
					String reqVersion = httpRequest.getRequestLine().getProtocolVersion().toString();

					if (DEBUG_LOGD) {
						Log.d(LOG_TAG, "VideoProxyHttpServer: ===== HTTP request header begin =====");
						Log.d(LOG_TAG, Log.buf().append(reqMethod).append(' ')
								.append(reqUri).append(' ').append(reqVersion).toString());
						Util.logHeaders(LOG_TAG, httpRequest.getAllHeaders());
						Log.d(LOG_TAG, "VideoProxyHttpServer: ===== HTTP request header end =====");
					}

					if ("HEAD".equals(reqMethod)) {
						getHandler().sendEmptyMessage(MSG_ID_SC_SEND_RESPONSE_HEADER);
					} else if ("GET".equals(reqMethod)) {
						getHandler().sendEmptyMessage(MSG_ID_SC_SEND_RESPONSE_ENTITY);
					} else {
						// TODO
						getHandler().sendEmptyMessage(MSG_ID_SC_CLOSE);
					}
				} catch (IOException e) {
					// TODO
					Log.e(LOG_TAG, e.toString(), e);
					getHandler().sendEmptyMessage(MSG_ID_SC_CLOSE);
				} catch (ParseException e) {
                    // TODO
                    Log.e(LOG_TAG, e.toString(), e);
                    getHandler().sendEmptyMessage(MSG_ID_SC_CLOSE);
				}
			} break;
			case MSG_ID_SC_SEND_RESPONSE_HEADER: {
				if (DEBUG_LOGV) {
					Log.v(LOG_TAG, "MSG_ID_SC_SEND_RESPONSE_HEADER");
				}
				try {
					HttpRequest httpRequest = mHttpRequest;
					ProtocolVersion responseVersion;
					if (0 < HttpVersion.HTTP_1_1.compareToVersion(
							httpRequest.getRequestLine().getProtocolVersion())) {
						responseVersion = HttpVersion.HTTP_1_0;
					} else {
						responseVersion = HttpVersion.HTTP_1_1;
					}

					HttpResponse httpResponse = createHttpResponseHeader(
					        responseVersion, httpRequest);

					if (mRangeBegin >= 0) {
					    assert mRangeEnd >= 0;
					    // 途中から再接続の通知
		                Handler handlerOnReconnectServer =
		                    (mRefHandlerOnReconnectServer == null) ? null
		                            : mRefHandlerOnReconnectServer.get();
		                if (handlerOnReconnectServer != null) {
		                    handlerOnReconnectServer.sendEmptyMessage(
		                            mWhatOnReconnectServer);
		                }
					}

					OutputStream out = mSocket.getOutputStream();
					mOutputSocket = out;

					writeHttpResponseHeader(out, httpResponse);

					if (!hasKeepAlive(httpRequest)) {
						getHandler().sendEmptyMessage(MSG_ID_SC_CLOSE);
					}
				} catch (IOException e) {
					// TODO
					Log.e(LOG_TAG, e.toString(), e);
					getHandler().sendEmptyMessage(MSG_ID_SC_CLOSE);
				}
			} break;
			case MSG_ID_SC_SEND_RESPONSE_ENTITY: {
				if (DEBUG_LOGV) {
					Log.v(LOG_TAG, "MSG_ID_SC_SEND_RESPONSE_ENTITY");
				}
				try {
					// 接続が途中で切れた場合はRange指定が来ることがある

					HttpRequest httpRequest = mHttpRequest;
					ProtocolVersion responseVersion;
					if (0 < HttpVersion.HTTP_1_1.compareToVersion(
							httpRequest.getRequestLine().getProtocolVersion())) {
						responseVersion = HttpVersion.HTTP_1_0;
					} else {
						responseVersion = HttpVersion.HTTP_1_1;
					}

					HttpResponse httpResponse = createHttpResponseHeader(responseVersion,
							httpRequest);

					OutputStream out = mSocket.getOutputStream();
					mOutputSocket = out;

					writeHttpResponseHeader(out, httpResponse);

					mInputVideo = createVideoLoaderWrapper(mVideoLoader);
					// タイミング的にありえないはずだがnullになるかも
					if (mInputVideo == null) {
					    assert false;
	                    // TODO
	                    getHandler().sendEmptyMessage(MSG_ID_SC_CLOSE);
					} else {
    					mBuffer = new byte[8096];
    					mReadLength = 0;
    					if (mRangeBegin >= 0) {
    						assert mRangeEnd >= 0;

                            // 途中から再接続の通知
                            Handler handlerOnReconnectServer =
                                (mRefHandlerOnReconnectServer == null) ? null
                                        : mRefHandlerOnReconnectServer.get();
                            if (handlerOnReconnectServer != null) {
                                handlerOnReconnectServer.sendEmptyMessage(
                                        mWhatOnReconnectServer);
                            }

    						getHandler().obtainMessage(MSG_ID_SC_SKIP_STREAM,
    								Long.valueOf(mRangeBegin)).sendToTarget();
    					} else {
    						getHandler().sendEmptyMessage(MSG_ID_SC_WRITE_STREAM);
    					}
					}
				} catch (IOException e) {
					// TODO
					Log.e(LOG_TAG, e.toString(), e);
					getHandler().sendEmptyMessage(MSG_ID_SC_CLOSE);
				}
			} break;
			case MSG_ID_SC_WRITE_STREAM: {
				InputStream inVideo = mInputVideo;
				assert inVideo != null;
				byte[] buffer = mBuffer;
				try {
					int length = buffer.length;
					if (mRangeEnd >= 0) {
						if (length > (mRangeEnd + 1 - mReadLength)) {
							length = (mRangeEnd + 1 - mReadLength);
						}
					}
					assert length > 0;
					int read = inVideo.read(buffer, 0, length);
					if (read > 0) {
						mOutputSocket.write(buffer, 0, read);
						mReadLength += read;
						if (mRangeEnd >= 0 && mReadLength >= mRangeEnd + 1) {
							assert mReadLength == mRangeEnd + 1;
							mOutputSocket.flush();
							if (!hasKeepAlive(mHttpRequest)) {
								getHandler().sendEmptyMessage(MSG_ID_SC_CLOSE);
							}
						} else {
							getHandler().sendEmptyMessage(MSG_ID_SC_WRITE_STREAM);
						}
					} else if (read == 0) {
						mOutputSocket.flush();
						getHandler().sendEmptyMessageDelayed(
								MSG_ID_SC_WRITE_STREAM, 100);
					} else {
						mOutputSocket.flush();

						if (!hasKeepAlive(mHttpRequest)) {
							getHandler().sendEmptyMessage(MSG_ID_SC_CLOSE);
						}
						break;
					}
				} catch (IOException e) {
					// TODO
					Log.e(LOG_TAG, e.toString(), e);
					getHandler().sendEmptyMessage(MSG_ID_SC_CLOSE);
				}
			} break;
			case MSG_ID_SC_SKIP_STREAM: {
                assert mInputVideo != null;
				try {
					long skip = (Long) msg.obj;
					long realSkip = mInputVideo.skip(skip);
					if (realSkip < skip) {
						long newSkip;
						long delay;
						if (realSkip > 0) {
							newSkip = skip - realSkip;
							delay = 0;
						} else {
							// retry skip
							newSkip = skip;
							delay = 100;
						}
						Handler handler = getHandler();
						handler.sendMessageDelayed(handler.obtainMessage(
								MSG_ID_SC_SKIP_STREAM, Long.valueOf(newSkip)),
								delay);
					} else {
						assert realSkip == skip;
						getHandler().sendEmptyMessage(MSG_ID_SC_WRITE_STREAM);
					}
				} catch (IOException e) {
					// TODO
					Log.e(LOG_TAG, e.toString(), e);
					getHandler().sendEmptyMessage(MSG_ID_SC_CLOSE);
				}
			} break;
			case MSG_ID_SC_CLOSE: {
			    Handler handler = getHandler();
			    handler.removeMessages(MSG_ID_SC_SEND_RESPONSE_HEADER);
                handler.removeMessages(MSG_ID_SC_SEND_RESPONSE_ENTITY);
                handler.removeMessages(MSG_ID_SC_WRITE_STREAM);
                handler.removeMessages(MSG_ID_SC_SKIP_STREAM);
                handler.removeMessages(MSG_ID_SC_CLOSE);
				if (DEBUG_LOGV) {
					Log.v(LOG_TAG, "MSG_ID_SC_CLOSE");
				}
				if (mInputSocket != null) {
					try {
						mInputSocket.close();
					} catch (IOException e) {
						// TODO
						Log.e(LOG_TAG, e.toString(), e);
					}
					mInputSocket = null;
				}
				if (mOutputSocket != null) {
					try {
						mOutputSocket.close();
					} catch (IOException e) {
						Log.e(LOG_TAG, e.toString(), e);
					}
					mOutputSocket = null;
				}
				try {
					mSocket.close();
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, Log.buf().append("Close Socket: ")
                                .append(mSocket.toString()).toString());
                    }
				} catch (IOException e) {
					Log.e(LOG_TAG, e.toString(), e);
				}
				mSocket = null;
				if (mInputVideo != null) {
					try {
						mInputVideo.close();
					} catch (IOException e) {
						Log.e(LOG_TAG, e.toString(), e);
					}
					mInputVideo = null;
				}
				mSocketConnectionQueue.remove(this);
				quit();
			} break;
			}
			return true;
		}

		public void open() {
			getHandler().sendEmptyMessage(MSG_ID_SC_RECEIVE_REQUEST);
		}
		public void close() {
			// TODO 読み込み途中ストップの強制終了？
		    Socket socket = mSocket;
		    if (socket != null && !socket.isClosed()) {
    			try {
    				socket.close();
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, Log.buf().append("Close Socket: ")
                                .append(socket.toString()).toString());
                    }
    			} catch (IOException e) {
    				// TODO
    				Log.e(LOG_TAG, e.toString(), e);
    			}
		    }
			getHandler().sendEmptyMessage(MSG_ID_SC_CLOSE);
		}

		private ByteArrayBuffer readHeaderLine(InputStream in,
				ByteArrayBuffer buffer) throws IOException {
			buffer.setLength(0);
			while (true) {
				int read = in.read();
				if (read < 0) {
					break;
				}
				buffer.append(read);
				int bufferLength = buffer.length();
				if (bufferLength >= 2
						&& buffer.byteAt(bufferLength - 2) == HTTP.CR
							&& buffer.byteAt(bufferLength - 1) == HTTP.LF) {
					buffer.setLength(bufferLength - 2);
					break;
				}
			}
			return buffer;
		}

		private HttpRequest parseHttpRequest(InputStream in) throws IOException {
			ByteArrayBuffer byteBuffer = new ByteArrayBuffer(256);
			readHeaderLine(in, byteBuffer);

			BasicLineParser parser = new BasicLineParser();
			RequestLine requestLine = BasicLineParser.parseRequestLine(
					EncodingUtils.getAsciiString(byteBuffer.buffer(), 0, byteBuffer.length()),
					parser);
			BasicHttpRequest httpRequest = new BasicHttpRequest(requestLine);

			while (true) {
				readHeaderLine(in, byteBuffer);
				if (byteBuffer.length() == 0) {
					break;
				}
				Header header = BasicLineParser.parseHeader(
						EncodingUtils.getAsciiString(byteBuffer.buffer(), 0, byteBuffer.length()),
						parser);
				httpRequest.addHeader(header);
			}
			return httpRequest;
		}

		private HttpResponse createHttpResponseHeader(ProtocolVersion responseVersion,
				HttpRequest httpRequest) {
			HttpResponse httpResponse = new BasicHttpResponse(
					responseVersion,
					HttpStatus.SC_OK,
					"OK");
			addHeaderForResponse(httpResponse, httpRequest);
			return httpResponse;
		}

		private void writeHttpResponseHeader(OutputStream out,
				HttpResponse httpResponse) throws IOException {
		    assert httpResponse != null;
		    StatusLine statusLine = httpResponse.getStatusLine();
		    assert statusLine != null;
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, "VideoProxyHttpServer: ===== HTTP response begin =====");
			}
			BasicLineFormatter formatter = new BasicLineFormatter();
			String s = BasicLineFormatter.formatStatusLine(
			        statusLine, formatter);
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, s);
			}
			out.write(EncodingUtils.getAsciiBytes(s));
			out.write(CRLF);
			HeaderIterator it = httpResponse.headerIterator();
			while (it.hasNext()) {
				s = BasicLineFormatter.formatHeader(
						it.nextHeader(), formatter);
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, s);
				}
				out.write(EncodingUtils.getAsciiBytes(s));
				out.write(CRLF);
			}
			out.write(CRLF);
			out.flush();

			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, "VideoProxyHttpServer: ===== HTTP response end =====");
			}
		}

		private HttpResponse addHeaderForResponse(HttpResponse httpResponse, HttpRequest httpRequest) {
			httpResponse.addHeader(HTTP.CONTENT_TYPE,
					mVideoLoader.getContentType());
			httpResponse.addHeader("ETag",
					mVideoLoader.getETag());
			httpResponse.addHeader("Accept-Ranges", "bytes");
			httpResponse.addHeader("Last-Modified",
					mVideoLoader.getLastModified());
			httpResponse.addHeader(HTTP.CONN_DIRECTIVE, "close");
			httpResponse.addHeader(HTTP.DATE_HEADER,
			        DateUtils.formatDate(new Date()));

            final long contentLength = mVideoLoader.getContentLength();
			Header range = httpRequest.getFirstHeader("Range");
			boolean hasRange = false;
			if (range != null) {
				String value = range.getValue();
				Matcher matcher = Pattern.compile(
						"\\s*bytes\\s*=\\s*([0-9]+)-([0-9]*)").matcher(value);
				if (matcher.find()) {
				    hasRange = true;
					String rangeBegin = matcher.group(1);
					String rangeEnd = matcher.group(2);
					if (TextUtils.isEmpty(rangeEnd)) {
					    rangeEnd = Long.toString(contentLength - 1);
					}
					httpResponse.addHeader("Content-Range", "bytes "
							+ rangeBegin + "-" + rangeEnd
							+ "/" + contentLength);
					mRangeBegin = Integer.parseInt(rangeBegin);
					mRangeEnd = Integer.parseInt(rangeEnd);
				}
			}
            long contentLen;
			if (hasRange) {
                contentLen = mRangeEnd - mRangeBegin + 1;
                httpResponse.setStatusLine(httpResponse.getProtocolVersion(),
                        HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
			} else {
			    contentLen = contentLength;
			}
            httpResponse.addHeader(HTTP.CONTENT_LEN,
                    Long.toString(contentLen));

			return httpResponse;
		}
	}
}
