/****************************************************************
 * 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.stream;

import junit.framework.Assert;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.util.ByteSequence;
import org.apache.james.mime4j.util.ContentUtil;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

public class RawFieldParserTest {

    private RawFieldParser parser;

    @Before
    public void setUp() throws Exception {
        parser = new RawFieldParser();
    }

    @Test
    public void testBasicTokenParsing() throws Exception {
        String s = "   raw: \" some stuff \"";
        ByteSequence raw = ContentUtil.encode(s);
        ParserCursor cursor = new ParserCursor(0, s.length());

        parser.skipWhiteSpace(raw, cursor);

        Assert.assertFalse(cursor.atEnd());
        Assert.assertEquals(3, cursor.getPos());

        StringBuilder strbuf1 = new StringBuilder();
        parser.copyContent(raw, cursor, RawFieldParser.INIT_BITSET(':'), strbuf1);

        Assert.assertFalse(cursor.atEnd());
        Assert.assertEquals(6, cursor.getPos());
        Assert.assertEquals("raw", strbuf1.toString());
        Assert.assertEquals(':', raw.byteAt(cursor.getPos()));
        cursor.updatePos(cursor.getPos() + 1);

        parser.skipWhiteSpace(raw, cursor);

        Assert.assertFalse(cursor.atEnd());
        Assert.assertEquals(8, cursor.getPos());

        StringBuilder strbuf2 = new StringBuilder();
        parser.copyQuotedContent(raw, cursor, strbuf2);

        Assert.assertTrue(cursor.atEnd());
        Assert.assertEquals(" some stuff ", strbuf2.toString());

        parser.copyQuotedContent(raw, cursor, strbuf2);
        Assert.assertTrue(cursor.atEnd());

        parser.skipWhiteSpace(raw, cursor);
        Assert.assertTrue(cursor.atEnd());
    }


    @Test
    public void testUtf8StringParsing() throws Exception {
        String s = "grå \"rød\"";
        ByteSequence raw = ContentUtil.encode(s);
        ParserCursor cursor = new ParserCursor(0, 2 + s.length());

        StringBuilder strbuf1 = new StringBuilder();
        parser.copyContent(raw, cursor, RawFieldParser.INIT_BITSET(':'), strbuf1);
        Assert.assertFalse(cursor.atEnd());
        Assert.assertEquals("grå", strbuf1.toString());

        parser.skipWhiteSpace(raw, cursor);

        StringBuilder strbuf2 = new StringBuilder();
        parser.copyQuotedContent(raw, cursor, strbuf2);
        Assert.assertEquals("rød", strbuf2.toString());
    }

    @Test
    public void testTokenParsingWithQuotedPairs() throws Exception {
        String s = "raw: \"\\\"some\\stuff\\\\\"";
        ByteSequence raw = ContentUtil.encode(s);
        ParserCursor cursor = new ParserCursor(0, s.length());

        parser.skipWhiteSpace(raw, cursor);

        Assert.assertFalse(cursor.atEnd());
        Assert.assertEquals(0, cursor.getPos());

        StringBuilder strbuf1 = new StringBuilder();
        parser.copyContent(raw, cursor, RawFieldParser.INIT_BITSET(':'), strbuf1);

        Assert.assertFalse(cursor.atEnd());
        Assert.assertEquals("raw", strbuf1.toString());
        Assert.assertEquals(':', raw.byteAt(cursor.getPos()));
        cursor.updatePos(cursor.getPos() + 1);

        parser.skipWhiteSpace(raw, cursor);

        Assert.assertFalse(cursor.atEnd());

        StringBuilder strbuf2 = new StringBuilder();
        parser.copyQuotedContent(raw, cursor, strbuf2);

        Assert.assertTrue(cursor.atEnd());
        Assert.assertEquals("\"some\\stuff\\", strbuf2.toString());
    }

    @Test
    public void testTokenParsingIncompleteQuote() throws Exception {
        String s = "\"stuff and more stuff  ";
        ByteSequence raw = ContentUtil.encode(s);
        ParserCursor cursor = new ParserCursor(0, s.length());
        StringBuilder strbuf1 = new StringBuilder();
        parser.copyQuotedContent(raw, cursor, strbuf1);
        Assert.assertEquals("stuff and more stuff  ", strbuf1.toString());
    }

    @Test
    public void testSkipComments() throws Exception {
        String s = "(some (((maybe))human readable) stuff())";
        ByteSequence raw = ContentUtil.encode(s);
        ParserCursor cursor = new ParserCursor(0, s.length());

        parser.skipComment(raw, cursor);
        Assert.assertTrue(cursor.atEnd());
    }

    @Test
    public void testSkipCommentsWithQuotedPairs() throws Exception {
        String s = "(some (((\\)maybe))human readable\\() stuff())";
        ByteSequence raw = ContentUtil.encode(s);
        ParserCursor cursor = new ParserCursor(0, s.length());

        parser.skipComment(raw, cursor);
        Assert.assertTrue(cursor.atEnd());
    }

    @Test
    public void testTokenParsingTokensWithUnquotedBlanks() throws Exception {
        String s = "  stuff and   \tsome\tmore  stuff  ;";
        ByteSequence raw = ContentUtil.encode(s);
        ParserCursor cursor = new ParserCursor(0, s.length());
        String result = parser.parseToken(raw, cursor, RawFieldParser.INIT_BITSET(';'));
        Assert.assertEquals("stuff and some more stuff", result);
    }

    @Test
    public void testTokenParsingTokensWithComments() throws Exception {
        String s = " (blah-blah)  stuff(blah-blah) and some mo(blah-blah)re  stuff (blah-blah) ;";
        ByteSequence raw = ContentUtil.encode(s);
        ParserCursor cursor = new ParserCursor(0, s.length());
        String result = parser.parseToken(raw, cursor, RawFieldParser.INIT_BITSET(';'));
        Assert.assertEquals("stuff and some more stuff", result);
    }

    @Test
    public void testTokenParsingMixedValuesAndQuotedValues() throws Exception {
        String s = "  stuff and    \" some more \"   \"stuff  ;";
        ByteSequence raw = ContentUtil.encode(s);
        ParserCursor cursor = new ParserCursor(0, s.length());
        String result = parser.parseValue(raw, cursor, RawFieldParser.INIT_BITSET(';'));
        Assert.assertEquals("stuff and  some more  stuff  ;", result);
    }

    @Test
    public void testTokenParsingMixedValuesAndQuotedValues2() throws Exception {
        String s = "stuff\"more\"stuff;";
        ByteSequence raw = ContentUtil.encode(s);
        ParserCursor cursor = new ParserCursor(0, s.length());
        String result = parser.parseValue(raw, cursor, RawFieldParser.INIT_BITSET(';'));
        Assert.assertEquals("stuffmorestuff", result);
    }

    @Test
    public void testTokenParsingQuotedValuesWithComments() throws Exception {
        String s = " (blah blah)  \"(stuff)(and)(some)(more)(stuff)\" (yada yada) ";
        ByteSequence raw = ContentUtil.encode(s);
        ParserCursor cursor = new ParserCursor(0, s.length());
        String result = parser.parseValue(raw, cursor, RawFieldParser.INIT_BITSET(';'));
        Assert.assertEquals("(stuff)(and)(some)(more)(stuff)", result);
    }

    @Test
    public void testBasicParsing() throws Exception {
        String s = "raw: stuff;\r\n  more stuff";
        ByteSequence raw = ContentUtil.encode(s);

        RawField field = parser.parseField(raw);
        Assert.assertSame(raw, field.getRaw());
        Assert.assertEquals("raw", field.getName());
        Assert.assertEquals("stuff;  more stuff", field.getBody());
        Assert.assertEquals(s, field.toString());
    }

    @Test
    public void testParsingNoBlankAfterColon() throws Exception {
        String s = "raw:stuff";
        ByteSequence raw = ContentUtil.encode(s);

        RawField field = parser.parseField(raw);
        Assert.assertSame(raw, field.getRaw());
        Assert.assertEquals("raw", field.getName());
        Assert.assertEquals("stuff", field.getBody());
        Assert.assertEquals(s, field.toString());
    }

    @Test
    public void testParsingObsoleteSyntax() throws Exception {
        String s = "raw  \t  : stuff;\r\n  more stuff";
        ByteSequence raw = ContentUtil.encode(s);

        RawField field = parser.parseField(raw);
        Assert.assertSame(raw, field.getRaw());
        Assert.assertEquals("raw", field.getName());
        Assert.assertEquals("stuff;  more stuff", field.getBody());
        Assert.assertEquals(s, field.toString());
    }

    @Test
    public void testParsingInvalidSyntax1() throws Exception {
        String s = "raw    stuff;\r\n  more stuff";
        ByteSequence raw = ContentUtil.encode(s);

        try {
            parser.parseField(raw);
            org.junit.Assert.fail("MimeException should have been thrown");
        } catch (MimeException expected) {
        }
    }

    @Test
    public void testLongString() throws Exception {
        String body = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";
        String s = "raw: " + body;
        ByteSequence raw = ContentUtil.encode(s);

        RawField rawField = parser.parseField(raw);
        Assert.assertEquals("raw", rawField.getName());
        Assert.assertEquals(body, rawField.getBody());
    }

    @Test
    public void testParsingInvalidSyntax2() throws Exception {
        String s = "raw    \t \t";
        ByteSequence raw = ContentUtil.encode(s);

        try {
            parser.parseField(raw);
            org.junit.Assert.fail("MimeException should have been thrown");
        } catch (MimeException expected) {
        }
    }

    @Test
    public void testNameValueParseBasics() {
        String s = "test";
        ByteSequence buf = ContentUtil.encode(s);
        ParserCursor cursor = new ParserCursor(0, s.length());

        NameValuePair param = parser.parseParameter(buf, cursor);
        org.junit.Assert.assertEquals("test", param.getName());
        org.junit.Assert.assertEquals(null, param.getValue());
        org.junit.Assert.assertEquals(s.length(), cursor.getPos());
        org.junit.Assert.assertTrue(cursor.atEnd());

        s = "test;";
        buf = ContentUtil.encode(s);
        cursor = new ParserCursor(0, s.length());

        param = parser.parseParameter(buf, cursor);
        org.junit.Assert.assertEquals("test", param.getName());
        org.junit.Assert.assertEquals(null, param.getValue());
        org.junit.Assert.assertEquals(s.length(), cursor.getPos());
        org.junit.Assert.assertTrue(cursor.atEnd());

        s = "test=stuff";
        buf = ContentUtil.encode(s);
        cursor = new ParserCursor(0, s.length());

        param = parser.parseParameter(buf, cursor);
        org.junit.Assert.assertEquals("test", param.getName());
        org.junit.Assert.assertEquals("stuff", param.getValue());
        org.junit.Assert.assertEquals(s.length(), cursor.getPos());
        org.junit.Assert.assertTrue(cursor.atEnd());

        s = "   test  =   stuff ";
        buf = ContentUtil.encode(s);
        cursor = new ParserCursor(0, s.length());

        param = parser.parseParameter(buf, cursor);
        org.junit.Assert.assertEquals("test", param.getName());
        org.junit.Assert.assertEquals("stuff", param.getValue());
        org.junit.Assert.assertEquals(s.length(), cursor.getPos());
        org.junit.Assert.assertTrue(cursor.atEnd());

        s = "   test  =   stuff ;1234";
        buf = ContentUtil.encode(s);
        cursor = new ParserCursor(0, s.length());

        param = parser.parseParameter(buf, cursor);
        org.junit.Assert.assertEquals("test", param.getName());
        org.junit.Assert.assertEquals("stuff", param.getValue());
        org.junit.Assert.assertEquals(s.length() - 4, cursor.getPos());
        org.junit.Assert.assertFalse(cursor.atEnd());

        s = "test  = \"stuff\"";
        buf = ContentUtil.encode(s);
        cursor = new ParserCursor(0, s.length());

        param = parser.parseParameter(buf, cursor);
        org.junit.Assert.assertEquals("test", param.getName());
        org.junit.Assert.assertEquals("stuff", param.getValue());

        s = "test  = text(text of some kind)/stuff(stuff of some kind)";
        buf = ContentUtil.encode(s);
        cursor = new ParserCursor(0, s.length());

        param = parser.parseParameter(buf, cursor);
        org.junit.Assert.assertEquals("test", param.getName());
        org.junit.Assert.assertEquals("text/stuff", param.getValue());

        s = "test  = \"  stuff\\\"\"";
        buf = ContentUtil.encode(s);
        cursor = new ParserCursor(0, s.length());

        param = parser.parseParameter(buf, cursor);
        org.junit.Assert.assertEquals("test", param.getName());
        org.junit.Assert.assertEquals("  stuff\"", param.getValue());

        s = "test  = \"  stuff\\\\\\\"\"";
        buf = ContentUtil.encode(s);
        cursor = new ParserCursor(0, s.length());

        param = parser.parseParameter(buf, cursor);
        org.junit.Assert.assertEquals("test", param.getName());
        org.junit.Assert.assertEquals("  stuff\\\"", param.getValue());

        s = "  test";
        buf = ContentUtil.encode(s);
        cursor = new ParserCursor(0, s.length());

        param = parser.parseParameter(buf, cursor);
        org.junit.Assert.assertEquals("test", param.getName());
        org.junit.Assert.assertEquals(null, param.getValue());

        s = "  ";
        buf = ContentUtil.encode(s);
        cursor = new ParserCursor(0, s.length());

        param = parser.parseParameter(buf, cursor);
        org.junit.Assert.assertEquals("", param.getName());
        org.junit.Assert.assertEquals(null, param.getValue());

        s = " = stuff ";
        buf = ContentUtil.encode(s);
        cursor = new ParserCursor(0, s.length());

        param = parser.parseParameter(buf, cursor);
        org.junit.Assert.assertEquals("", param.getName());
        org.junit.Assert.assertEquals("stuff", param.getValue());
    }

    @Test
    public void testNameValueListParseBasics() {
        ByteSequence buf = ContentUtil.encode(
                "test; test1 =  stuff   ; test2 =  \"stuff; stuff\"; test3=\"stuff");
        ParserCursor cursor = new ParserCursor(0, buf.length());
        List<NameValuePair> params = parser.parseParameters(buf, cursor);

        org.junit.Assert.assertEquals("test", params.get(0).getName());
        org.junit.Assert.assertEquals(null, params.get(0).getValue());
        org.junit.Assert.assertEquals("test1", params.get(1).getName());
        org.junit.Assert.assertEquals("stuff", params.get(1).getValue());
        org.junit.Assert.assertEquals("test2", params.get(2).getName());
        org.junit.Assert.assertEquals("stuff; stuff", params.get(2).getValue());
        org.junit.Assert.assertEquals("test3", params.get(3).getName());
        org.junit.Assert.assertEquals("stuff", params.get(3).getValue());
        org.junit.Assert.assertEquals(buf.length(), cursor.getPos());
        org.junit.Assert.assertTrue(cursor.atEnd());
    }

    @Test
    public void testNameValueListParseEmpty() {
        ByteSequence buf = ContentUtil.encode("    ");
        ParserCursor cursor = new ParserCursor(0, buf.length());
        List<NameValuePair> params = parser.parseParameters(buf, cursor);
        org.junit.Assert.assertEquals(0, params.size());
    }

    @Test
    public void testNameValueListParseEscaped() {
        ByteSequence buf = ContentUtil.encode(
                "test1 =  \"\\\"stuff\\\"\"; test2= \"\\\\\"; test3 = \"stuff; stuff\"");
        ParserCursor cursor = new ParserCursor(0, buf.length());
        List<NameValuePair> params = parser.parseParameters(buf, cursor);
        org.junit.Assert.assertEquals(3, params.size());
        org.junit.Assert.assertEquals("test1", params.get(0).getName());
        org.junit.Assert.assertEquals("\"stuff\"", params.get(0).getValue());
        org.junit.Assert.assertEquals("test2", params.get(1).getName());
        org.junit.Assert.assertEquals("\\", params.get(1).getValue());
        org.junit.Assert.assertEquals("test3", params.get(2).getName());
        org.junit.Assert.assertEquals("stuff; stuff", params.get(2).getValue());
    }

    @Test
    public void testRawBodyParse() {
        ByteSequence buf = ContentUtil.encode(
                "  text/plain ; charset=ISO-8859-1; "
                        + "boundary=foo; param1=value1; param2=\"value2\"; param3=value3");
        ParserCursor cursor = new ParserCursor(0, buf.length());
        RawBody body = parser.parseRawBody(buf, cursor);
        org.junit.Assert.assertNotNull(body);
        org.junit.Assert.assertEquals("text/plain", body.getValue());
        List<NameValuePair> params = body.getParams();
        org.junit.Assert.assertEquals(5, params.size());
        org.junit.Assert.assertEquals("charset", params.get(0).getName());
        org.junit.Assert.assertEquals("ISO-8859-1", params.get(0).getValue());
        org.junit.Assert.assertEquals("boundary", params.get(1).getName());
        org.junit.Assert.assertEquals("foo", params.get(1).getValue());
        org.junit.Assert.assertEquals("param1", params.get(2).getName());
        org.junit.Assert.assertEquals("value1", params.get(2).getValue());
        org.junit.Assert.assertEquals("param2", params.get(3).getName());
        org.junit.Assert.assertEquals("value2", params.get(3).getValue());
        org.junit.Assert.assertEquals("param3", params.get(4).getName());
        org.junit.Assert.assertEquals("value3", params.get(4).getValue());
    }

    @Test
    public void testRawBodyParseWithComments() {
        ByteSequence buf = ContentUtil.encode(
                "  text/(nothing special)plain ; charset=(latin)ISO-8859-1; "
                        + "boundary=foo(bar);");
        ParserCursor cursor = new ParserCursor(0, buf.length());
        RawBody body = parser.parseRawBody(buf, cursor);
        org.junit.Assert.assertNotNull(body);
        org.junit.Assert.assertEquals("text/plain", body.getValue());
        List<NameValuePair> params = body.getParams();
        org.junit.Assert.assertEquals(2, params.size());
        org.junit.Assert.assertEquals("charset", params.get(0).getName());
        org.junit.Assert.assertEquals("ISO-8859-1", params.get(0).getValue());
        org.junit.Assert.assertEquals("boundary", params.get(1).getName());
        org.junit.Assert.assertEquals("foo", params.get(1).getValue());
    }

    @Test
    public void testRawBodyParseEmptyParam() {
        ByteSequence buf = ContentUtil.encode(
                "multipart/alternative;; boundary=\"boundary\"");
        ParserCursor cursor = new ParserCursor(0, buf.length());
        RawBody body = parser.parseRawBody(buf, cursor);
        org.junit.Assert.assertNotNull(body);
        org.junit.Assert.assertEquals("multipart/alternative", body.getValue());
        List<NameValuePair> params = body.getParams();
        org.junit.Assert.assertEquals(2, params.size());
        org.junit.Assert.assertEquals("", params.get(0).getName());
        org.junit.Assert.assertEquals(null, params.get(0).getValue());
        org.junit.Assert.assertEquals("boundary", params.get(1).getName());
        org.junit.Assert.assertEquals("boundary", params.get(1).getValue());
    }

    @Test
    public void testRawBodyParseFolded() {
        ByteSequence buf = ContentUtil.encode(
                "multipart/alternative; boundary=\"simple\r\n boundary\"");
        ParserCursor cursor = new ParserCursor(0, buf.length());
        RawBody body = parser.parseRawBody(buf, cursor);
        org.junit.Assert.assertNotNull(body);
        org.junit.Assert.assertEquals("multipart/alternative", body.getValue());
        List<NameValuePair> params = body.getParams();
        org.junit.Assert.assertEquals(1, params.size());
        org.junit.Assert.assertEquals("boundary", params.get(0).getName());
        org.junit.Assert.assertEquals("simple boundary", params.get(0).getValue());
    }

    @Test
    public void testRegressionForContentDispositionParsingASCIIonly() {
        ByteSequence buf = ContentUtil.encode(
                "name=\"filedata\"; filename=\"Sanity a.doc\"");
        ParserCursor cursor = new ParserCursor(0, buf.length());
        List<NameValuePair> params = parser.parseParameters(buf, cursor);

        org.junit.Assert.assertEquals(2, params.size());
        org.junit.Assert.assertEquals("name", params.get(0).getName());
        org.junit.Assert.assertEquals("filedata", params.get(0).getValue());
        org.junit.Assert.assertEquals("filename", params.get(1).getName());
        org.junit.Assert.assertEquals("Sanity a.doc", params.get(1).getValue());
   }

    @Test
    public void testRegressionForContentDispositionParsingUTF8() {
        ByteSequence buf = ContentUtil.encode("name=\"filedata\"; filename=\"Sanity ä.doc\"");
        ParserCursor cursor = new ParserCursor(0, buf.length());
        List<NameValuePair> params = parser.parseParameters(buf, cursor);

        org.junit.Assert.assertEquals(2, params.size());
        org.junit.Assert.assertEquals("name", params.get(0).getName());
        org.junit.Assert.assertEquals("filedata", params.get(0).getValue());
        org.junit.Assert.assertEquals("filename", params.get(1).getName());
        org.junit.Assert.assertEquals("Sanity ä.doc", params.get(1).getValue());
   }


}
