package org.mortbay.jetty;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.Random;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import junit.framework.TestCase;

import org.mortbay.jetty.handler.AbstractHandler;
import org.mortbay.util.IO;

/**
 * HttpServer Tester.
 */
public class HttpServerTestBase extends TestCase
{

    // ~ Static fields/initializers
    // ---------------------------------------------

    /** The request. */
    private static final String REQUEST1_HEADER="POST / HTTP/1.0\n"+"Host: localhost\n"+"Content-Type: text/xml; charset=utf-8\n"+"Connection: close\n"+"Content-Length: ";
    private static final String REQUEST1_CONTENT="<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
            +"<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+"        xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n"
            +"</nimbus>";
    private static final String REQUEST1=REQUEST1_HEADER+REQUEST1_CONTENT.getBytes().length+"\n\n"+REQUEST1_CONTENT;

    /** The expected response. */
    private static final String RESPONSE1="HTTP/1.1 200 OK\n"+"Connection: close\n"+"Server: Jetty(6.1.x)\n"+"\n"+"Hello world\n";

    // Break the request up into three pieces, splitting the header.
    private static final String FRAGMENT1=REQUEST1.substring(0,16);
    private static final String FRAGMENT2=REQUEST1.substring(16,34);
    private static final String FRAGMENT3=REQUEST1.substring(34);

    /** Second test request. */
    private static final String REQUEST2_HEADER="POST / HTTP/1.0\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Content-Length: ";

    private static final String REQUEST2_CONTENT="<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
            +"<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+"        xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n"
            +"    <request requestId=\"1\">\n"+"        <getJobDetails>\n"+"            <jobId>73</jobId>\n"+"        </getJobDetails>\n"+"    </request>\n"
            +"</nimbus>";

    private static final String REQUEST2=REQUEST2_HEADER+REQUEST2_CONTENT.getBytes().length+"\n\n"+REQUEST2_CONTENT;

    private static final String RESPONSE2_CONTENT="<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
            +"<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+"        xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n"
            +"    <request requestId=\"1\">\n"+"        <getJobDetails>\n"+"            <jobId>73</jobId>\n"+"        </getJobDetails>\n"+"    </request>\n"
            +"</nimbus>\n";
    private static final String RESPONSE2="HTTP/1.1 200 OK\n"+"Content-Length: "+RESPONSE2_CONTENT.getBytes().length+"\n"+"Server: Jetty(6.1.x)\n"+"\n"
            +RESPONSE2_CONTENT;

    // Useful constants
    private static final long PAUSE=5L;
    private static final int LOOPS=20;
    private static final String HOST="localhost";

    private Connector _connector;
    private int port=0;

    protected void tearDown() throws Exception
    {
        super.tearDown();
        Thread.sleep(250);
    }

    // ~ Methods
    // ----------------------------------------------------------------

    /**
     * Feed the server the entire request at once.
     * 
     * @throws Exception
     * @throws InterruptedException
     */
    public void testRequest1_jetty() throws Exception, InterruptedException
    {
        Server server=startServer(new HelloWorldHandler());
        Socket client=new Socket(HOST,port);
        OutputStream os=client.getOutputStream();

        os.write(REQUEST1.getBytes());
        os.flush();

        // Read the response.
        String response=readResponse(client);

        // Shut down
        client.close();
        server.stop();

        // Check the response
        assertEquals("response",RESPONSE1,response);
    }

    /* --------------------------------------------------------------- */
    public void testFragmentedChunk() throws Exception
    {

        Server server=startServer(new EchoHandler());
        Socket client=new Socket(HOST,port);
        OutputStream os=client.getOutputStream();

        os.write(("GET /R2 HTTP/1.1\015\012"+"Host: localhost\015\012"+"Transfer-Encoding: chunked\015\012"+"Content-Type: text/plain\015\012"
                +"Connection: close\015\012"+"\015\012").getBytes());
        os.flush();
        Thread.sleep(PAUSE);
        os.write(("5\015\012").getBytes());
        os.flush();
        Thread.sleep(PAUSE);
        os.write(("ABCDE\015\012"+"0;\015\012\015\012").getBytes());
        os.flush();

        // Read the response.
        String response=readResponse(client);

        // Shut down
        client.close();
        server.stop();

        assertTrue(true); // nothing checked yet.

    }

    /**
     * Feed the server fragmentary headers and see how it copes with it.
     * 
     * @throws Exception
     * @throws InterruptedException
     */
    public void testRequest1Fragments_jetty() throws Exception, InterruptedException
    {
        Server server=startServer(new HelloWorldHandler());
        String response;

        try
        {
            Socket client=new Socket(HOST,port);
            OutputStream os=client.getOutputStream();

            // Write a fragment, flush, sleep, write the next fragment, etc.
            os.write(FRAGMENT1.getBytes());
            os.flush();
            Thread.sleep(PAUSE);
            os.write(FRAGMENT2.getBytes());
            os.flush();
            Thread.sleep(PAUSE);
            os.write(FRAGMENT3.getBytes());
            os.flush();

            // Read the response
            response=readResponse(client);

            // Shut down
            client.close();
        }
        finally
        {
            server.stop();
        }

        // Check the response
        assertEquals("response",RESPONSE1,response);
    }

    public void testRequest2_jetty() throws Exception
    {
        byte[] bytes=REQUEST2.getBytes();
        Server server=startServer(new EchoHandler());

        try
        {
            for (int i=0; i<LOOPS; i++)
            {
                Socket client=new Socket(HOST,port);
                OutputStream os=client.getOutputStream();

                os.write(bytes);
                os.flush();

                // Read the response
                String response=readResponse(client);
                client.close();

                // Check the response
                assertEquals("response "+i,RESPONSE2,response);
            }
        }
        finally
        {
            // Shut down
            server.stop();
        }
    }

    /**
     * @throws Exception
     */
    public void testRequest2Fragments_jetty() throws Exception
    {
        Random random=new Random(System.currentTimeMillis());
        byte[] bytes=REQUEST2.getBytes();
        final int pointCount=2;
        Server server=startServer(new EchoHandler());

        try
        {

            for (int i=0; i<LOOPS; i++)
            {

                int[] points=new int[pointCount];
                StringBuffer message=new StringBuffer();

                message.append("iteration #"+(i+1));

                // Pick fragment points at random
                for (int j=0; j<points.length; ++j)
                {
                    points[j]=random.nextInt(bytes.length);
                }

                // Sort the list
                Arrays.sort(points);

                Socket client=new Socket(HOST,port);
                OutputStream os=client.getOutputStream();

                writeFragments(bytes,points,message,os);

                // Read the response
                String response=readResponse(client);

                // Close the client
                client.close();

                // Check the response
                assertEquals("response for "+i+" "+message.toString(),RESPONSE2,response);
            }
        }
        finally
        {
            // Shut down
            server.stop();
        }
    }

    public void testRequest2Iterate_jetty() throws Exception
    {
        byte[] bytes=REQUEST2.getBytes();
        Server server=startServer(new EchoHandler());

        try
        {
            for (int i=0; i<bytes.length; i+=3)
            {
                int[] points=new int[] { i };
                StringBuffer message=new StringBuffer();

                message.append("iteration #"+(i+1));

                // Sort the list
                Arrays.sort(points);

                Socket client=new Socket(HOST,port);
                OutputStream os=client.getOutputStream();

                writeFragments(bytes,points,message,os);

                // Read the response
                String response=readResponse(client);

                // Close the client
                client.close();

                // Check the response
                assertEquals("response for "+i+" "+message.toString(),RESPONSE2,response);
            }
        }
        finally
        {
            // Shut down
            server.stop();
        }
    }

    /**
     * After several iterations, I generated some known bad fragment points.
     * 
     * @throws Exception
     */
    public void testRequest2KnownBad_jetty() throws Exception
    {
        byte[] bytes=REQUEST2.getBytes();
        int[][] badPoints=new int[][]
        {
        { 70 }, // beginning here, drops last line of request
                { 71 }, // no response at all
                { 72 }, // again starts drops last line of request
                { 74 }, // again, no response at all
        };
        Server server=startServer(new EchoHandler());

        try
        {
            for (int i=0; i<badPoints.length; ++i)
            {
                Socket client=new Socket(HOST,port);
                OutputStream os=client.getOutputStream();
                StringBuffer message=new StringBuffer();

                message.append("iteration #"+(i+1));
                writeFragments(bytes,badPoints[i],message,os);

                // Read the response
                String response=readResponse(client);

                // Close the client
                client.close();

                // Check the response
                // TODO - change to equals when code gets fixed
                assertNotSame("response for "+message.toString(),RESPONSE2,response);
            }
        }
        finally
        {
            // Shut down
            server.stop();
        }
    }

    /**
     * After several iterations, I generated some known bad fragment points.
     * 
     * @throws Exception
     */
    public void testFlush() throws Exception
    {
        Server server=startServer(new DataHandler());
        try
        {   
            String[] encoding = {"NONE","UTF-8","ISO-8859-1","ISO-8859-2","JIS"};


            for (int e =0; e<encoding.length;e++)
            {
                for (int b=1;b<100;b+=50)
                {
                    for (int w=1024;w<10000;w+=4096)
                    {
                        for (int c=0;c<2;c++)
                        {
                            String test=encoding[e]+"x"+b+"x"+w+"x"+"x"+c;
                            URL url=new URL("http://"+HOST+":"+port+"/?writes="+w+"&block="+b+ (e==0?"":("&encoding="+encoding[e]))+(c==0?"&chars=true":""));
                            InputStream in = (InputStream)url.getContent();
                            String response=IO.toString(in);

                            assertEquals(test,b*w,response.length());
                        }
                    }
                }
            }


        }
        finally
        {
            // Shut down
            server.stop();
            Thread.yield();
        }

    }

    
    
    
    
    /**
     * Read entire response from the client. Close the output.
     * 
     * @param client
     *            Open client socket.
     * 
     * @return The response string.
     * 
     * @throws IOException
     */
    private static String readResponse(Socket client) throws IOException
    {
        BufferedReader br=null;

        try
        {
            br=new BufferedReader(new InputStreamReader(client.getInputStream()));

            StringBuffer sb=new StringBuffer();
            String line;

            while ((line=br.readLine())!=null)
            {
                sb.append(line);
                sb.append('\n');
            }

            return sb.toString();
        }
        finally
        {
            if (br!=null)
            {
                br.close();
            }
        }
    }

    protected HttpServerTestBase(Connector connector)
    {
        _connector=connector;
    }

    /**
     * Create the server.
     * 
     * @param handler
     * 
     * @return Newly created server, ready to start.
     * 
     * @throws Exception
     */
    private Server startServer(Handler handler) throws Exception
    {
        Server server=new Server();

        _connector.setPort(0);
        server.setConnectors(new Connector[]
        { _connector });
        server.setHandler(handler);
        server.start();
        port=_connector.getLocalPort();
        return server;
    }

    private void writeFragments(byte[] bytes, int[] points, StringBuffer message, OutputStream os) throws IOException, InterruptedException
    {
        int last=0;

        // Write out the fragments
        for (int j=0; j<points.length; ++j)
        {
            int point=points[j];

            os.write(bytes,last,point-last);
            last=point;
            os.flush();
            Thread.sleep(PAUSE);

            // Update the log message
            message.append(" point #"+(j+1)+": "+point);
        }

        // Write the last fragment
        os.write(bytes,last,bytes.length-last);
        os.flush();
        Thread.sleep(PAUSE);
    }

    // ~ Inner Classes
    // ----------------------------------------------------------
    private static class EchoHandler extends AbstractHandler
    {

        // ~ Methods
        // ------------------------------------------------------------
        public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException
        {

            Request base_request=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest();
            base_request.setHandled(true);

            PrintWriter writer=response.getWriter();
            BufferedReader reader=request.getReader();
            int count=0;
            String line;

            while ((line=reader.readLine())!=null)
            {
                writer.print(line);
                writer.print('\n');
                count+=line.length();
            }

            if (count==0)
                throw new IllegalStateException("no input recieved");
        }
    }

    // ----------------------------------------------------------
    private static class HelloWorldHandler extends AbstractHandler
    {
        // ------------------------------------------------------------
        public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException
        {
            Request base_request=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest();
            base_request.setHandled(true);
            response.setStatus(200);
            response.getOutputStream().print("Hello world");
        }
    }

    // ----------------------------------------------------------
    private static class DataHandler extends AbstractHandler
    {
        // ------------------------------------------------------------
        public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException
        {
            Request base_request=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest();
            base_request.setHandled(true);
            response.setStatus(200);
            
            String tmp = request.getParameter("writes");
            int writes=Integer.parseInt(tmp==null?"10":tmp);
            tmp = request.getParameter("block");
            int block=Integer.parseInt(tmp==null?"10":tmp);
            String encoding=request.getParameter("encoding");
            String chars=request.getParameter("chars");
            
            String chunk = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
                .substring(0,block);
            response.setContentType("text/plain");
            if (encoding==null)
            {
                byte[] bytes=chunk.getBytes("ISO-8859-1");
                OutputStream out=response.getOutputStream();
                for (int i=0;i<writes;i++)
                    out.write(bytes);
            }
            else if ("true".equals(chars))
            {
                response.setCharacterEncoding(encoding);
                Writer out=response.getWriter();
                char[] c=chunk.toCharArray();
                for (int i=0;i<writes;i++)
                    out.write(c);
            }
            else 
            {
                response.setCharacterEncoding(encoding);
                Writer out=response.getWriter();
                for (int i=0;i<writes;i++)
                    out.write(chunk);
            }
        }
    }

}
