//
//  ========================================================================
//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.test.support.rawhttp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import junit.framework.Assert;

import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeaders;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.View;
import org.eclipse.jetty.test.support.StringUtil;
import org.eclipse.jetty.toolchain.test.StringAssert;
import org.eclipse.jetty.util.ByteArrayOutputStream2;

/**
 * Assists in testing of HTTP Responses.
 */
public class HttpResponseTester
{
    private class PH extends HttpParser.EventHandler
    {
        @Override
        public void content(Buffer ref) throws IOException
        {
            if (content == null)
                content = new ByteArrayOutputStream2();
            content.write(ref.asArray());
        }

        @Override
        public void headerComplete() throws IOException
        {
            contentType = fields.get(HttpHeaders.CONTENT_TYPE_BUFFER);
            if (contentType != null)
            {
                String calcCharset = MimeTypes.getCharsetFromContentType(contentType);
                if (calcCharset != null)
                {
                    charset = calcCharset;
                }
            }
        }

        @Override
        public void messageComplete(long contextLength) throws IOException
        {
        }

        @Override
        public void parsedHeader(Buffer name, Buffer value) throws IOException
        {
            fields.add(name,value);
        }

        @Override
        public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
        {
            reset();
            HttpResponseTester.this.method = getString(method);
            HttpResponseTester.this.uri = getString(url);
            HttpResponseTester.this.version = getString(version);
        }

        @Override
        public void startResponse(Buffer version, int status, Buffer reason) throws IOException
        {
            reset();
            HttpResponseTester.this.version = getString(version);
            HttpResponseTester.this.status = status;
            HttpResponseTester.this.reason = getString(reason);
        }
    }

    public static List<HttpResponseTester> parseMulti(CharSequence rawHTTP) throws IOException
    {
        List<HttpResponseTester> responses = new ArrayList<HttpResponseTester>();
        String parse = rawHTTP.toString();
        while (StringUtil.isNotBlank(parse))
        {
            HttpResponseTester response = new HttpResponseTester();
            parse = response.parse(parse);
            responses.add(response);
        }

        return responses;
    }

    private HttpFields fields = new HttpFields();
    private CharSequence rawResponse;
    private String method;
    private String uri;
    private String version;
    private int status;
    private String reason;
    private Buffer contentType;
    private ByteArrayOutputStream2 content;
    private String charset;
    private String defaultCharset;

    public HttpResponseTester()
    {
        this("UTF-8");
    }

    public HttpResponseTester(String defCharset)
    {
        this.defaultCharset = defCharset;
    }

    public String getMethod()
    {
        return method;
    }

    public String getURI()
    {
        return uri;
    }

    public String getVersion()
    {
        return version;
    }

    public int getStatus()
    {
        return status;
    }

    public CharSequence getRawResponse()
    {
        return rawResponse;
    }

    public String getReason()
    {
        return reason;
    }

    public String getContentType()
    {
        if (contentType == null)
        {
            return null;
        }
        return contentType.toString();
    }

    public ByteArrayOutputStream2 getContentBytes()
    {
        return content;
    }

    public String getContent()
    {
        return content.toString();
    }

    public String getBody()
    {
        return content.toString();
    }

    private byte[] getByteArray(CharSequence str)
    {
        if (charset == null)
        {
            return str.toString().getBytes();
        }

        try
        {
            return str.toString().getBytes(charset);
        }
        catch (Exception e)
        {
            return str.toString().getBytes();
        }
    }

    private String getString(Buffer buffer)
    {
        return getString(buffer.asArray());
    }

    private String getString(byte[] b)
    {
        if (charset == null)
        {
            return new String(b);
        }

        try
        {
            return new String(b,charset);
        }
        catch (Exception e)
        {
            return new String(b);
        }
    }

    /**
     * @param name
     * @return the header value as a date
     * @see org.eclipse.jetty.http.HttpFields#getDateField(java.lang.String)
     */
    public long getDateHeader(String name)
    {
        return fields.getDateField(name);
    }

    /**
     * @param name
     * @return the header value as a long
     * @throws NumberFormatException
     * @see org.eclipse.jetty.http.HttpFields#getLongField(java.lang.String)
     */
    public long getLongHeader(String name) throws NumberFormatException
    {
        return fields.getLongField(name);
    }

    /**
     * @param name
     * @return the header value
     * @see org.eclipse.jetty.http.HttpFields#getStringField(java.lang.String)
     */
    public String getHeader(String name)
    {
        return fields.getStringField(name);
    }

    public boolean hasHeader(String headerKey)
    {
        return fields.containsKey(headerKey);
    }

    /**
     * Parse on HTTP Response
     * 
     * @param rawHTTP
     *            Raw HTTP to parse
     * @return Any unparsed data in the rawHTTP (eg pipelined requests)
     * @throws IOException
     */
    public String parse(CharSequence rawHTTP) throws IOException
    {
        this.charset = defaultCharset;
        this.rawResponse = rawHTTP;
        ByteArrayBuffer buf = new ByteArrayBuffer(getByteArray(rawHTTP));
        View view = new View(buf);
        HttpParser parser = new HttpParser(view,new PH());
        parser.parse();
        return getString(view.asArray());
    }

    public void reset()
    {
        fields.clear();
        method = null;
        uri = null;
        version = null;
        status = 0;
        reason = null;
        content = null;
    }

    /**
     * Make sure that status code is "OK"
     */
    public void assertStatusOK()
    {
        assertStatus(HttpStatus.OK_200,"OK");
    }

    public void assertStatusOK(String msg)
    {
        assertStatus(msg,HttpStatus.OK_200,"OK");
    }

    public void assertStatus(int expectedStatus, String expectedReason)
    {
        Assert.assertEquals("Response.status",expectedStatus,this.status);
        Assert.assertEquals("Response.reason",expectedReason,this.reason);
    }

    public void assertStatus(String msg, int expectedStatus, String expectedReason)
    {
        Assert.assertEquals(msg + ": Response.status",expectedStatus,this.status);
        Assert.assertEquals(msg + ": Response.reason",expectedReason,this.reason);
    }

    public void assertStatus(String msg, int expectedStatus)
    {
        assertStatus(msg,expectedStatus,HttpStatus.getMessage(expectedStatus));
    }

    public void assertContentType(String expectedType)
    {
        assertHeader("Content-Type",expectedType);
    }

    private void assertHeader(String headerKey, String expectedValue)
    {
        String actual = fields.getStringField(headerKey);
        Assert.assertNotNull("Response[" + headerKey + "] should not be null",actual);
        Assert.assertEquals("Response[" + headerKey + "]",expectedValue,actual);
    }

    public void assertHeader(String msg, String headerKey, String expectedValue)
    {
        String actual = fields.getStringField(headerKey);
        Assert.assertNotNull(msg + ": Response[" + headerKey + "] should not be null, expecting <" + expectedValue + ">",actual);
        Assert.assertEquals(msg + ": Response[" + headerKey + "]",expectedValue,actual);
    }

    public void assertBody(String expected)
    {
        Assert.assertNotNull("Response.content should not be null",this.content);
        String actual = this.content.toString();
        Assert.assertEquals("Response.content",expected,actual);
    }

    public void assertBody(String msg, String expected)
    {
        Assert.assertNotNull(msg + ": Response.content should not be null",this.content);
        String actual = this.content.toString();
        Assert.assertEquals(msg + ": Response.content",expected,actual);
    }
    
    public void assertNoBody(String msg)
    {
        Assert.assertNull(msg + ": Response.content should be null",this.content);
    }

    public void assertBodyContains(String msg, String expectedNeedle)
    {
        StringAssert.assertContains(msg + ": Response Content",this.content.toString(),expectedNeedle);
    }

    public void assertHeaderExists(String msg, String expectedHeaderKey)
    {
        Assert.assertTrue(msg + ": header <" + expectedHeaderKey + "> should exist",fields.containsKey(expectedHeaderKey));
    }

    public void assertHeaderNotPresent(String msg, String headerKey)
    {
        Assert.assertFalse(msg + ": header <" + headerKey + "> should NOT exist",fields.containsKey(headerKey));
    }
    
    public List<HttpResponseTester> findBodyMultiparts(String boundary) throws IOException
    {
        List<HttpResponseTester> multiparts = new ArrayList<HttpResponseTester>();

        BufferedReader buf = new BufferedReader(new StringReader(getBody()));
        String line;
        String startBoundary = "--" + boundary;
        String endBoundary = "--" + boundary + "--";
        HttpResponseTester resp = null;
        boolean parsingHeader = true;
        boolean previousBodyLine = false;

        while ((line = buf.readLine()) != null)
        {
            if (line.equals(startBoundary))
            {
                // end of multipart, start a new one.
                if (resp != null)
                {
                    multiparts.add(resp);
                }
                resp = new HttpResponseTester();
                parsingHeader = true;
                previousBodyLine = false;
                continue;
            }

            if (line.equals(endBoundary))
            {
                if (resp != null)
                {
                    multiparts.add(resp);
                }
                break;
            }

            if (parsingHeader)
            {
                if (line.equals(""))
                {
                    parsingHeader = false;
                    continue;
                }

                resp.parseHeader(line);
            }
            else
            {
                if (previousBodyLine)
                {
                    resp.appendBody("\n");
                }
                resp.appendBody(line);
                previousBodyLine = true;
            }
        }

        return multiparts;
    }

    public void parseHeader(String line)
    {
        int idx = line.indexOf(":");
        String key = line.substring(0,idx).trim();
        String val = line.substring(idx + 1).trim();

        fields.add(key,val);
    }

    public void appendBody(String s) throws IOException
    {
        appendBody(s.getBytes());
    }

    public void appendBody(byte buf[]) throws IOException
    {
        if (content == null)
        {
            content = new ByteArrayOutputStream2();
        }

        content.write(buf);
    }
}
