package com.app.downloadstream1;

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.ConnectionClosedException;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpServerConnection;
import org.apache.http.HttpStatus;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.RequestLine;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;

import android.util.Log;

public class LocalHttpServer extends Thread {
	
	public static final String TAG = "LocalHttpServer";
	
	private final ServerSocket mServer;
    private final HttpParams mParams; 
    private final HttpService httpService;
    protected TailStream mStream;
    private WorkerThread wt;
    
    public LocalHttpServer(int port, TailStream stream) throws IOException
	{
		super("LocalHttpServer");
		
		mServer = new ServerSocket(port);
		this.mStream = stream;
		mParams = new BasicHttpParams();
		mParams
		  .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
		  .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
		  .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
		  .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true);

		setDaemon(true);
		
		// Set up the HTTP protocol processor
		BasicHttpProcessor httpproc = new BasicHttpProcessor();
		httpproc.addInterceptor(new ResponseContent());
		httpproc.addInterceptor(new ResponseConnControl());
		
		 // Set up request handlers
		HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
		reqistry.register("*",new HttpStreamHandler());
		
		// Set up the HTTP service
        this.httpService = new HttpService(
                httpproc, 
                new DefaultConnectionReuseStrategy(), 
                new DefaultHttpResponseFactory());
        this.httpService.setParams(this.mParams);
        this.httpService.setHandlerResolver(reqistry);
                
	}
    
    public void reset(TailStream stream) {
    	wt.shutdown();
    	this.mStream = stream;
    }
    
    public void shutdown() {
    	wt.shutdown();
    	interrupt();
    	
    	try {
			mServer.close();
		} catch (IOException e) {}
		mStream = null;
    }
    
    public String makeUri()
	{
		return "http://127.0.0.1:" + getPort() + "/";
	}
    
    public int getPort()
	{
		if (mServer == null)
			throw new IllegalStateException("Not bound.");

		return mServer.getLocalPort();
	}
    
    public void run() {
    	System.out.println("Listening on port " + this.mServer.getLocalPort());
    	while (!Thread.interrupted()) {
    		try {
                // Set up HTTP connection
                Socket socket = this.mServer.accept();
                DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
                System.out.println("Incoming connection from " + socket.getInetAddress());
                conn.bind(socket, this.mParams);

                // Start worker thread
                wt = new WorkerThread(this.httpService, conn);
                wt.setDaemon(true);
                wt.start();
            } catch (InterruptedIOException ex) {
                break;
            } catch (IOException e) {
                System.err.println("I/O error initialising connection thread: " 
                        + e.getMessage());
                break;
            }
    	}
    	
    }
    
    static class WorkerThread extends Thread {

        private final HttpService httpservice;
        private final HttpServerConnection conn;
        
        public WorkerThread(
                final HttpService httpservice, 
                final HttpServerConnection conn) {
            super();
            this.httpservice = httpservice;
            this.conn = conn;
        }
        
        public void run() {
            System.out.println("New connection thread");
            HttpContext context = new BasicHttpContext(null);
            try {
                while (!Thread.interrupted() && this.conn.isOpen()) {
                    this.httpservice.handleRequest(this.conn, context);
                }
            } catch (ConnectionClosedException ex) {
                System.err.println("Client closed connection");
            } catch (IOException ex) {
                System.err.println("I/O error: " + ex.getMessage());
            } catch (HttpException ex) {
                System.err.println("Unrecoverable HTTP protocol violation: " + ex.getMessage());
            } finally {
                try {
                    this.conn.shutdown();
                } catch (IOException ignore) {}
            }
        }
        
        public void shutdown() {
        	try {
        		this.conn.shutdown();
        	}catch(IOException e) {
        		Log.e(TAG, e.toString());
        	}
        }

    }
    	
    class HttpStreamHandler implements HttpRequestHandler {
    	private void interpretRangeThenSeek(HttpRequest req,
  			  TailStream stream) throws IOException {
    		Header hdr = req.getLastHeader("Range");

			if (hdr == null)
				return;

			String rangeStr = hdr.getValue();
			Pattern pattern = Pattern.compile("bytes=(\\d+)-(\\d+)");
			Matcher matcher = pattern.matcher(rangeStr);

			if (matcher.matches() == false)
			{
				Log.w(TAG, "Failed to parse range header: " + rangeStr);
				return;
			}

			long low;
			long high;

			try {
				low = Long.parseLong(matcher.group(1));
				high = Long.parseLong(matcher.group(2));
			} catch (NumberFormatException e) {
				Log.w(TAG, "Failed to parse range header: " + rangeStr);
				return;
			}

			/* We assume that high is actually just the end of the
			 * stream as it was originally defined, so we aren't going
			 * to honor it explicitly. */
			assert stream.size() == high;

			Log.i(TAG, "Serving range " + low + "-" + high);
			stream.seek(low);
    	}
    	
		public void handle(HttpRequest request, HttpResponse response,
				HttpContext context) throws HttpException, IOException {
			RequestLine reqLine = request.getRequestLine();
			Log.v(TAG, "reqLine=" + reqLine);

			String method = reqLine.getMethod().toUpperCase(Locale.ENGLISH);
			if (method.equals("GET") == false)
			{
				throw new MethodNotSupportedException(method +
				  " method not supported");
			}

			TailStream stream = mStream;

			/* Side-effect: will open the stream, so we can seek if
			 * necessary. */
			RandomAccessStreamEntity ent =
			  new RandomAccessStreamEntity(stream);

			interpretRangeThenSeek(request, stream);

			response.setEntity(ent);
			response.setStatusCode(HttpStatus.SC_OK);
		}
			
		}
    
    public class RandomAccessStreamEntity
	  extends AbstractHttpEntity
	{
		private final static int BUFFER_SIZE = 2048;

		private final TailStream mStream;
		private final long mLength;
		private boolean mConsumed = false;

		public RandomAccessStreamEntity(TailStream stream)
		  throws IOException
		{
			super();

			mStream = stream;

			try {
				stream.open();
			} catch (IOException e) {
				Log.e(TAG, "Stream open failure", e);
				throw e;
			}

			mLength = stream.size();
		}

		public long getContentLength()
		{
			return mLength;
		}

		public void writeTo(OutputStream outstream)
		  throws IOException
		{
			Log.i(TAG, "writeTo...");

			try {
				byte[] b = new byte[BUFFER_SIZE];
				int n;

				while ((n = mStream.read(b)) >= 0)
					outstream.write(b, 0, n);

				mConsumed = true;
			} finally {
				Log.i(TAG, "writeTo finished...");
				mStream.close();
			}
		}

		public void consumeContent()
		  throws IOException
		{
			throw new RuntimeException("Is this used?");
		}

		public InputStream getContent()
		  throws IOException, IllegalStateException
		{
			return mStream;
		}

		public boolean isRepeatable()
		{
			return false;
		}

		public boolean isStreaming()
		{
			return !mConsumed;
		}
	}
}

    
    



