/****************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one   *
 * or more contributor license agreements.  See the NOTICE file *
 * distributed with this work for additional information        *
 * regarding copyright ownership.  The ASF licenses this file   *
 * to you under the Apache License, Version 2.0 (the            *
 * "License"); you may not use this file except in compliance   *
 * with the License.  You may obtain a copy of the License at   *
 *                                                              *
 *   http://www.apache.org/licenses/LICENSE-2.0                 *
 *                                                              *
 * Unless required by applicable law or agreed to in writing,   *
 * software distributed under the License is distributed on an  *
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
 * KIND, either express or implied.  See the License for the    *
 * specific language governing permissions and limitations      *
 * under the License.                                           *
 ****************************************************************/

package org.apache.james.mime4j.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.james.mime4j.io.BufferedLineReaderInputStream;
import org.apache.james.mime4j.io.LineReaderInputStream;
import org.apache.james.mime4j.io.MimeBoundaryInputStream;
import org.apache.james.mime4j.util.ByteArrayBuffer;


import junit.framework.TestCase;

public class MimeBoundaryInputStreamTest extends TestCase {

    public void testBasicReading() throws IOException {
        String text = "Line 1\r\nLine 2\r\n--boundary\r\n" +
                "Line 3\r\nLine 4\r\n--boundary--";
        
        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes("US-ASCII"));
        
        BufferedLineReaderInputStream buffer = new BufferedLineReaderInputStream(bis, 4096); 
        
        MimeBoundaryInputStream mime1 = new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals("Line 1\r\nLine 2", read(mime1, 5));
        
        assertFalse(mime1.isLastPart());
        
        MimeBoundaryInputStream mime2 = new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals("Line 3\r\nLine 4", read(mime2, 5));

        assertTrue(mime2.isLastPart());
    }
    
    public void testLenientLineDelimiterReading() throws IOException {
        String text = "Line 1\r\nLine 2\n--boundary\n" +
                "Line 3\r\nLine 4\n--boundary--\n";
        
        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes("US-ASCII"));
        
        BufferedLineReaderInputStream buffer = new BufferedLineReaderInputStream(bis, 4096); 
        
        MimeBoundaryInputStream mime1 = new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals("Line 1\r\nLine 2", read(mime1, 5));
        
        assertFalse(mime1.isLastPart());
        
        MimeBoundaryInputStream mime2 = new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals("Line 3\r\nLine 4", read(mime2, 5));

        assertTrue(mime2.isLastPart());
    }
    
    public void testBasicReadingSmallBuffer1() throws IOException {
        String text = "yadayadayadayadayadayadayadayadayadayadayadayadayadayadayadayada\r\n--boundary\r\n" +
                "blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah\r\n--boundary--";
        
        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes("US-ASCII"));
        
        BufferedLineReaderInputStream buffer = new BufferedLineReaderInputStream(bis, 20); 
        
        MimeBoundaryInputStream mime1 = new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals("yadayadayadayadayadayadayadayadayadayadayadayadayadayadayadayada", 
                read(mime1, 10));
        
        assertFalse(mime1.isLastPart());
        
        MimeBoundaryInputStream mime2 = new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals("blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah", 
                read(mime2, 10));

        assertTrue(mime2.isLastPart());
    }
    
    public void testBasicReadingSmallBuffer2() throws IOException {
        String text = "yadayadayadayadayadayadayadayadayadayadayadayadayadayadayadayada\r\n--boundary\r\n" +
                "blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah\r\n--boundary--";
        
        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes("US-ASCII"));
        
        BufferedLineReaderInputStream buffer = new BufferedLineReaderInputStream(bis, 20); 
        
        MimeBoundaryInputStream mime1 = new MimeBoundaryInputStream(buffer, "boundary");
        
        assertEquals("yadayadayadayadayadayadayadayadayadayadayadayadayadayadayadayada", 
                read(mime1, 25));
        
        assertFalse(mime1.isLastPart());
        
        MimeBoundaryInputStream mime2 = new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals("blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah", 
                read(mime2, 25));

        assertTrue(mime2.isLastPart());
    }
    
    public void testBasicReadingByOneByte() throws IOException {
        String text = "Line 1\r\nLine 2\r\n--boundary\r\n" +
                "Line 3\r\nLine 4\r\n--boundary--";
        
        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes("US-ASCII"));
        
        BufferedLineReaderInputStream buffer = new BufferedLineReaderInputStream(bis, 4096); 
        
        MimeBoundaryInputStream mime1 = new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals("Line 1\r\nLine 2", readByOneByte(mime1));
        
        assertFalse(mime1.isLastPart());
        
        MimeBoundaryInputStream mime2 = new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals("Line 3\r\nLine 4", readByOneByte(mime2));

        assertTrue(mime2.isLastPart());
    }
    
    /**
     * Tests that a CRLF immediately preceding a boundary isn't included in
     * the stream.
     */
    public void testCRLFPrecedingBoundary() throws IOException {
        String text = "Line 1\r\nLine 2\r\n--boundary\r\n" +
                "Line 3\r\nLine 4\r\n\r\n--boundary\r\n";
        
        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes("US-ASCII"));
        
        BufferedLineReaderInputStream buffer = new BufferedLineReaderInputStream(bis, 4096); 
        
        MimeBoundaryInputStream mime1 = new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals("Line 1\r\nLine 2", read(mime1, 5));
        
        assertFalse(mime1.isLastPart());
        
        MimeBoundaryInputStream mime2 = new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals("Line 3\r\nLine 4\r\n", read(mime2, 5));

        assertFalse(mime2.isLastPart());
    }
    
    private String readByOneByte(InputStream is) throws IOException {
        StringBuilder sb = new StringBuilder();
        int b = 0;
        while ((b = is.read()) != -1) {
            sb.append((char) b);
        }
        return sb.toString();
    }

    private String read(InputStream is, int bufsize) throws IOException {
        StringBuilder sb = new StringBuilder();
        int l;
        byte[] tmp = new byte[bufsize];
        while ((l = is.read(tmp)) != -1) {
            for (int i = 0; i < l; i++) {
                sb.append((char) tmp[i]);
            }
        }
        return sb.toString();
    }
    
    /**
     * Tests that a stream containing only a boundary is empty.
     */
    public void testImmediateBoundary() throws IOException {
        String text = "--boundary\r\n";
        
        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes());
        BufferedLineReaderInputStream buffer = new BufferedLineReaderInputStream(bis, 4096); 
        
        MimeBoundaryInputStream stream = 
            new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals(-1, stream.read());
        
        text = "\r\n--boundary\r\n";
        
        bis = new ByteArrayInputStream(text.getBytes());
        buffer = new BufferedLineReaderInputStream(bis, 4096); 
        stream = 
            new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals(-1, stream.read());
    }
    
    /**
     * Tests that hasMoreParts behave as expected.
     */
    public void testHasMoreParts() throws IOException {
        String text = "--boundary--\r\n";
        
        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes());
        BufferedLineReaderInputStream buffer = new BufferedLineReaderInputStream(bis, 4096); 
        MimeBoundaryInputStream stream = 
            new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals(-1, stream.read());
        assertTrue(stream.isLastPart());
    }
    
    /**
     * Tests that a stream containing only a boundary is empty.
     */
    public void testPrefixIsBoundary() throws IOException {
        String text = "Line 1\r\n\r\n--boundaryyada\r\n";
        
        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes());
        BufferedLineReaderInputStream buffer = new BufferedLineReaderInputStream(bis, 4096); 
        MimeBoundaryInputStream stream = 
            new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals("Line 1\r\n", read(stream, 100));
        
        text = "--boundaryyada\r\n";
        
        bis = new ByteArrayInputStream(text.getBytes());
        buffer = new BufferedLineReaderInputStream(bis, 4096); 
        stream = new MimeBoundaryInputStream(buffer, "boundary");
        assertEquals(-1, stream.read());
    }
    
    
    public void testBasicReadLine() throws Exception {
        
        String[] teststrs = new String[5];
        teststrs[0] = "Hello\r\n";
        teststrs[1] = "This string should be much longer than the size of the input buffer " +
                "which is only 20 bytes for this test\r\n";
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 15; i++) {
            sb.append("123456789 ");
        }
        sb.append("and stuff like that\r\n");
        teststrs[2] = sb.toString();
        teststrs[3] = "\r\n";
        teststrs[4] = "And goodbye\r\n";

        String term = "\r\n--1234\r\n";
        
        ByteArrayOutputStream outstream = new ByteArrayOutputStream();
        
        for (String teststr : teststrs) {
            outstream.write(teststr.getBytes("US-ASCII"));
        }
        outstream.write(term.getBytes("US-ASCII"));
        byte[] raw = outstream.toByteArray();
        
        BufferedLineReaderInputStream inbuffer = new BufferedLineReaderInputStream(new ByteArrayInputStream(raw), 20); 
        LineReaderInputStream instream = new MimeBoundaryInputStream(inbuffer, "1234"); 
        
        ByteArrayBuffer linebuf = new ByteArrayBuffer(8); 
        for (String teststr : teststrs) {
            linebuf.clear();
            instream.readLine(linebuf);
            String s = new String(linebuf.toByteArray(), "US-ASCII");
            assertEquals(teststr, s);
        }
        assertEquals(-1, instream.readLine(linebuf));
        assertEquals(-1, instream.readLine(linebuf));
    }
    
    public void testReadEmptyLine() throws Exception {
        
        String teststr = "01234567890123456789\n\n\r\n\r\r\n\n\n\n\n\n--1234\r\n";
        byte[] raw = teststr.getBytes("US-ASCII");
        
        BufferedLineReaderInputStream inbuffer = new BufferedLineReaderInputStream(new ByteArrayInputStream(raw), 20); 
        LineReaderInputStream instream = new MimeBoundaryInputStream(inbuffer, "1234"); 
        
        ByteArrayBuffer linebuf = new ByteArrayBuffer(8); 
        linebuf.clear();
        instream.readLine(linebuf);
        String s = new String(linebuf.toByteArray(), "US-ASCII");
        assertEquals("01234567890123456789\n", s);
        
        linebuf.clear();
        instream.readLine(linebuf);
        s = new String(linebuf.toByteArray(), "US-ASCII");
        assertEquals("\n", s);
        
        linebuf.clear();
        instream.readLine(linebuf);
        s = new String(linebuf.toByteArray(), "US-ASCII");
        assertEquals("\r\n", s);

        linebuf.clear();
        instream.readLine(linebuf);
        s = new String(linebuf.toByteArray(), "US-ASCII");
        assertEquals("\r\r\n", s);

        linebuf.clear();
        instream.readLine(linebuf);
        s = new String(linebuf.toByteArray(), "US-ASCII");
        assertEquals("\n", s);

        linebuf.clear();
        instream.readLine(linebuf);
        s = new String(linebuf.toByteArray(), "US-ASCII");
        assertEquals("\n", s);

        linebuf.clear();
        instream.readLine(linebuf);
        s = new String(linebuf.toByteArray(), "US-ASCII");
        assertEquals("\n", s);

        linebuf.clear();
        instream.readLine(linebuf);
        s = new String(linebuf.toByteArray(), "US-ASCII");
        assertEquals("\n", s);

        assertEquals(-1, instream.readLine(linebuf));
        assertEquals(-1, instream.readLine(linebuf));
    }
    
    public void testboundaryLongerThanBuffer() throws IOException {
        String text = "--looooooooooooooooooooooooooong-boundary\r\n";
        
        ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes());
        BufferedLineReaderInputStream buffer = new BufferedLineReaderInputStream(bis, 10); 
        
        try {
            new MimeBoundaryInputStream(buffer, "looooooooooooooooooooooooooong-boundary");
            fail("IllegalArgumentException should have been thrown");
        } catch (IllegalArgumentException expected) {
        }
    }

}
