package org.apache.maven.doxia.module.apt;

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

import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Iterator;

import org.apache.maven.doxia.parser.AbstractParserTest;
import org.apache.maven.doxia.parser.Parser;
import org.apache.maven.doxia.parser.ParseException;

import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
import org.apache.maven.doxia.sink.impl.SinkEventElement;
import org.apache.maven.doxia.sink.impl.SinkEventTestingSink;

/**
 * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
 */
public class AptParserTest
    extends AbstractParserTest
{

    private AptParser parser;

    @Override
    protected void setUp()
        throws Exception
    {
        super.setUp();

        parser = (AptParser) lookup( Parser.class, "apt" );
    }

    protected Parser createParser()
    {
        return parser;
    }

    protected String parseFileToAptSink( String file ) throws ParseException, IOException
    {
        try( StringWriter output = new StringWriter();
             Reader reader = getTestReader( file ) )
        {
            Sink sink = new AptSink( output );
            createParser().parse( reader, sink );

            return output.toString();
        }
    }

    public void testLineBreak()
        throws Exception
    {
        String linebreak = parseFileToAptSink( "test/linebreak" );

        assertTrue( linebreak.contains( "Line\\" + EOL + "break." ) );
    }

    public void testSnippetMacro()
        throws Exception
    {
        String macro = parseFileToAptSink( "test/macro" );

        assertTrue( macro.contains( "<modelVersion\\>4.0.0\\</modelVersion\\>" ) );
    }

    public void testCommentsBeforeTitle()
        throws Exception
    {
        String comments = parseFileToAptSink( "test/comments" );

        assertEquals( 0, comments.indexOf( "~~ comments before title" + EOL + "~~ like a license header, for example"
            + EOL + " -----" + EOL + " Test DOXIA-379" ) );
    }

    public void testSnippet()
        throws Exception
    {
        // DOXIA-259

        SinkEventTestingSink sink = new SinkEventTestingSink();
        try ( Reader reader = getTestReader( "test/snippet" ) )
        {
            createParser().parse( reader, sink );
        }

        Iterator<SinkEventElement> it = sink.getEventList().iterator();

        assertEquals( it, "head", "head_", "body", "list", "listItem", "text", "verbatim", "text", "verbatim_",
                      "paragraph", "text", "paragraph_", "listItem_", "listItem", "text", "verbatim", "text",
                      "verbatim_", "paragraph", "text", "paragraph_", "listItem_", "list_", "body_" );
    }

    public void testSnippetTrailingSpace()
        throws Exception
    {
        // DOXIA-425
        String text = "%{snippet|id=myid|file=pom.xml}  " + EOL;

        SinkEventTestingSink sink = new SinkEventTestingSink();

        parser.parse( text, sink );

        Iterator<SinkEventElement> it = sink.getEventList().iterator();

        assertEquals( it, "head", "head_", "body", "verbatim", "text", "verbatim_", "body_" );
    }

    public void testTocMacro()
        throws Exception
    {
        String toc = parseFileToAptSink( "test/toc" );

        // No section, only subsection 1 and 2
        assertTrue( toc.contains( "* {{{SubSection_1.1}SubSection 1.1}}" ) );
        assertFalse( toc.contains( "* {{{SubSection_1.1.2.1.1}SubSection 1.1.2.1.1}}" ) );
    }

    /**
     * Parses the test document test.apt and re-emits
     * it into parser/test.apt.
     *
     * @throws IOException if the test file cannot be read.
     * @throws ParseException if the test file cannot be parsed.
     */
    public void testTestDocument()
        throws IOException, ParseException
    {
        try( Writer writer = getTestWriter( "test" );
             Reader reader = getTestReader( "test" ) )
        {
            Sink sink = new AptSink( writer );
            createParser().parse( reader, sink );
        }
    }

    public void testBoxedVerbatim()
        throws Exception
    {
        String text = "+--" + EOL + "boxed verbatim" + EOL + "+--" + EOL
                + "---" + EOL + "un-boxed verbatim" + EOL + "---" + EOL;

        SinkEventTestingSink sink = new SinkEventTestingSink();

        parser.parse( text, sink );

        Iterator<SinkEventElement> it = sink.getEventList().iterator();

        assertStartsWith( it, "head", "head_", "body" );
        assertEquals( it.next(), "verbatim", SinkEventAttributeSet.BOXED );
        assertStartsWith( it, "text", "verbatim_" );

        assertEquals( it.next(), "verbatim", new Object[] { null } );
        assertEquals( it, "text", "verbatim_", "body_" );
    }

    public void testMultiLinesInTableCells()
        throws Exception
    {
        String text = "*----------*--------------+----------------:" + EOL +
                " cell 1, | cell 1,2       | cell 1,3" + EOL +
                " 1       |                | " + EOL +
                "*----------*--------------+----------------:" + EOL +
                " cell 2,1 | cell 2,       | cell 2,3" + EOL +
                "          | 2             |" + EOL +
                "*----------*--------------+----------------:" + EOL +
                " cell 3,1 | cell 3,2      | cell 3," + EOL +
                "          |               | 3" + EOL +
                "*----------*--------------+----------------:" + EOL;

        SinkEventTestingSink sink = new SinkEventTestingSink();

        parser.parse( text, sink );

        Iterator<SinkEventElement> it = sink.getEventList().iterator();

        assertStartsWith( it, "head", "head_", "body", "table", "tableRows", "tableRow", "tableCell" );
        assertEquals( it.next(), "text", "cell 1, 1" );

        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "cell 1,2" );

        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "cell 1,3" );

        assertStartsWith( it, "tableCell_", "tableRow_", "tableRow", "tableCell" );
        assertEquals( it.next(), "text", "cell 2,1" );

        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "cell 2, 2" );

        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "cell 2,3" );
        
        assertStartsWith( it, "tableCell_", "tableRow_", "tableRow", "tableCell" );
        assertEquals( it.next(), "text", "cell 3,1" );

        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "cell 3,2" );

        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "cell 3, 3" );

        assertEquals( it, "tableCell_", "tableRow_", "tableRows_", "table_", "body_" );
    }

    public void testLineBreakInTableCells()
        throws Exception
    {
        String text = "*----------*--------------+----------------:" + EOL +
                " cell 1,\\ | cell 1,2       | cell 1,3" + EOL +
                " 1       |                | " + EOL +
                "*----------*--------------+----------------:" + EOL +
                " cell 2,1 | cell 2,\\     | cell 2,3" + EOL +
                "          | 2             |" + EOL +
                "*----------*--------------+----------------:" + EOL +
                " cell 3,1 | cell 3,2      | cell 3,\\" + EOL +
                "          |               | 3" + EOL +
                "*----------*--------------+----------------:" + EOL;

        SinkEventTestingSink sink = new SinkEventTestingSink();

        parser.parse( text, sink );

        Iterator<SinkEventElement> it = sink.getEventList().iterator();

        assertStartsWith( it, "head", "head_", "body", "table", "tableRows", "tableRow", "tableCell" );
        assertEquals( it.next(), "text", "cell 1,\u00A0" );

        assertEquals( it.next().getName(), "lineBreak" );
        assertEquals( it.next(), "text", "1" );

        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "cell 1,2" );

        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "cell 1,3" );

        assertStartsWith( it, "tableCell_", "tableRow_", "tableRow", "tableCell" );
        assertEquals( it.next(), "text", "cell 2,1" );

        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "cell 2,\u00A0" );

        assertEquals( it.next().getName(), "lineBreak" );
        assertEquals( it.next(), "text", "2" );

        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "cell 2,3" );

        assertStartsWith( it, "tableCell_", "tableRow_", "tableRow", "tableCell" );
        assertEquals( it.next(), "text", "cell 3,1" );

        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "cell 3,2" );

        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "cell 3,\u00A0" );

        assertEquals( it.next().getName(), "lineBreak" );
        assertEquals( it.next(), "text", "3" );

        assertEquals( it, "tableCell_", "tableRow_", "tableRows_", "table_", "body_" );
    }

    public void testDOXIA38()
        throws Exception
    {
        String text =
                "*----------*--------------*---------------*" + EOL +
                "| Centered |   Centered   |   Centered    |" + EOL +
                "*----------*--------------+---------------:" + EOL +
                "| Centered | Left-aligned | Right-aligned |" + EOL +
                "*----------*--------------+---------------:";

        SinkEventTestingSink sink = new SinkEventTestingSink();

        parser.parse( text, sink );

        Iterator<SinkEventElement> it = sink.getEventList().iterator();

        assertStartsWith( it, "head", "head_", "body", "table", "tableRows", "tableRow" );
        assertAttributeEquals( it.next(), "tableCell", SinkEventAttributeSet.ALIGN, "center" );
        assertEquals( it.next(), "text", "Centered" );
        assertEquals( it.next().getName(), "tableCell_" );
        
        assertAttributeEquals( it.next(), "tableCell", SinkEventAttributeSet.ALIGN, "center" );
        assertEquals( it.next(), "text", "Centered" );
        assertEquals( it.next().getName(), "tableCell_" );
        
        assertAttributeEquals( it.next(), "tableCell", SinkEventAttributeSet.ALIGN, "center" );
        assertEquals( it.next(), "text", "Centered" );
        assertStartsWith( it, "tableCell_", "tableRow_", "tableRow" );
        
        assertAttributeEquals( it.next(), "tableCell", SinkEventAttributeSet.ALIGN, "center" );
        assertEquals( it.next(), "text", "Centered" );
        assertEquals( it.next().getName(), "tableCell_" );
        
        assertAttributeEquals( it.next(), "tableCell", SinkEventAttributeSet.ALIGN, "left" );
        assertEquals( it.next(), "text", "Left-aligned" );
        assertEquals( it.next().getName(), "tableCell_" );
        
        assertAttributeEquals( it.next(), "tableCell", SinkEventAttributeSet.ALIGN, "right" );
        assertEquals( it.next(), "text", "Right-aligned" );
        assertEquals( it, "tableCell_", "tableRow_", "tableRows_", "table_", "body_" );
    }

    public void testSpecialCharactersInTables()
        throws Exception
    {
        // DOXIA-323, DOXIA-433
        String text =
                "  \\~ \\= \\- \\+ \\* \\[ \\] \\< \\> \\{ \\} \\\\ \\u2713" + EOL
                + EOL
                + "*--------------------------------------------------+---------------+" + EOL
                + "| \\~ \\= \\- \\+ \\* \\[ \\] \\< \\> \\{ \\} \\\\ \\u2713 | special chars |" + EOL
                + "*--------------------------------------------------+---------------+";

        SinkEventTestingSink sink = new SinkEventTestingSink();
        parser.parse( text, sink );

        Iterator<SinkEventElement> it = sink.getEventList().iterator();

        assertStartsWith( it, "head", "head_", "body", "paragraph" );
        assertEquals( it.next(), "text", "~ = - + * [ ] < > { } \\ \u2713" );

        assertStartsWith( it, "paragraph_", "table", "tableRows", "tableRow", "tableCell" );
        assertEquals( it.next(), "text", "~ = - + * [ ] < > { } \\ \u2713" );

        assertEquals( it, "tableCell_", "tableCell", "text", "tableCell_", "tableRow_", "tableRows_", "table_", "body_" );
    }

    public void testSpacesAndBracketsInAnchors()
        throws Exception
    {
        final String text = "  {Anchor with spaces (and brackets)}" + EOL
            + "  Link to {{Anchor with spaces (and brackets)}}" + EOL
            + "  {{{http://fake.api#method(with, args)}method(with, args)}}" + EOL;

        final SinkEventTestingSink sink = new SinkEventTestingSink();

        parser.parse( text, sink );

        Iterator<SinkEventElement> it = sink.getEventList().iterator();

        assertStartsWith( it, "head", "head_", "body", "paragraph" );
        assertEquals( it.next(), "anchor", "Anchor_with_spaces_and_brackets" );

        assertEquals( it.next(), "text", "Anchor with spaces (and brackets)" );

        assertStartsWith( it, "anchor_", "text" );
        assertEquals( it.next(), "link", "#Anchor_with_spaces_and_brackets" );

        assertEquals( it.next(), "text", "Anchor with spaces (and brackets)" );

        assertStartsWith( it, "link_", "text" );
        assertEquals( it.next(), "link", "http://fake.api#method(with, args)" );

        assertEquals( it.next(), "text", "method(with, args)" );

        assertEquals( it, "link_", "paragraph_", "body_" );
    }

    public void testSectionTitleAnchors()
        throws Exception
    {
        // DOXIA-420
        String text = "Enhancements to the APT format" + EOL + EOL
            + "{Title with anchor}" + EOL;

        SinkEventTestingSink sink = new SinkEventTestingSink();

        parser.parse( text, sink );

        Iterator<SinkEventElement> it = sink.getEventList().iterator();

        assertEquals( it, "head", "head_", "body", "section1", "sectionTitle1", "text", "sectionTitle1_", "section1_",
                      "section1", "sectionTitle1", "anchor", "text", "anchor_", "sectionTitle1_", "section1_", "body_" );
    }
    
    public void testTableHeaders() throws Exception
    {
        // DOXIA-404
        String text = "*-----------+-----------+" + EOL + 
        		"|| Header 1 || Header 2 |" + EOL +
        		"*-----------+-----------+" + EOL +
        		"  Cell 1    | Cell 2    |" + EOL +
        		"*-----------+-----------+" + EOL +
        		"  Cell 3    | Cell 4    |" + EOL +
        		"*-----------+-----------+" + EOL;
        
        SinkEventTestingSink sink = new SinkEventTestingSink();

        parser.parse( text, sink );

        Iterator<SinkEventElement> it = sink.getEventList().iterator();

        assertStartsWith( it, "head", "head_", "body", "table", "tableRows" );
        assertStartsWith( it, "tableRow", "tableHeaderCell", "text", "tableHeaderCell_", "tableHeaderCell", "text",
                          "tableHeaderCell_", "tableRow_" );
        assertStartsWith( it, "tableRow", "tableCell", "text", "tableCell_", "tableCell", "text", "tableCell_",
                          "tableRow_" );
        assertStartsWith( it, "tableRow", "tableCell", "text", "tableCell_", "tableCell", "text", "tableCell_",
                          "tableRow_" );
        assertEquals( it, "tableRows_", "table_", "body_" );
    }
    
    public void testEscapedPipeInTableCell() throws Exception
    {
        // DOXIA-479
        String text="*---+---+" + EOL + 
        		"| cell \\| pipe | next cell " + EOL + 
        		"*---+---+" + EOL;
        
        SinkEventTestingSink sink = new SinkEventTestingSink();

        parser.parse( text, sink );

        Iterator<SinkEventElement> it = sink.getEventList().iterator();
        assertStartsWith( it, "head", "head_", "body", "table", "tableRows", "tableRow", "tableCell" );
        assertEquals( it.next(), "text", "cell | pipe" );
        assertStartsWith( it, "tableCell_", "tableCell" );
        assertEquals( it.next(), "text", "next cell" );
        assertEquals( it, "tableCell_", "tableRow_", "tableRows_", "table_", "body_" );
    }

    public void testLiteralAnchor()
        throws Exception
    {
        // DOXIA-397
        String text =
            "{{{../apidocs/groovyx/net/http/ParserRegistry.html##parseText(org.apache.http.HttpResponse)}ParserRegistry}}";

        SinkEventTestingSink sink = new SinkEventTestingSink();

        parser.parse( text, sink );

        Iterator<SinkEventElement> it = sink.getEventList().iterator();
        assertStartsWith( it, "head", "head_", "body", "section1", "sectionTitle1" );
        assertEquals( it.next(), "link",
                      "../apidocs/groovyx/net/http/ParserRegistry.html#parseText(org.apache.http.HttpResponse)" );
        assertEquals( it.next(), "text", "ParserRegistry" );
        assertEquals( it, "link_", "sectionTitle1_", "section1_", "body_" );
    }

    protected String outputExtension()
    {
        return "apt";
    }
}
