package org.apache.maven.doxia.sink.impl;

/*
 * 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.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.maven.doxia.logging.Log;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.sink.SinkEventAttributes;
import org.apache.maven.doxia.sink.SinkFactory;

/**
 * The RandomAccessSink provides the ability to create a {@link Sink} with hooks.
 * A page can be prepared by first creating its structure and specifying the positions of these hooks.
 * After specifying the structure, the page can be filled with content from one or more models.
 * These hooks can prevent you to have to loop over the model multiple times to build the page as desired. 
 * 
 * @author Robert Scholte
 * @since 1.3
 */
public class RandomAccessSink
    implements Sink
{
    private SinkFactory sinkFactory;

    private String encoding;

    private OutputStream coreOutputStream;

    private Sink coreSink;

    private List<Sink> sinks = new ArrayList<Sink>();

    private List<ByteArrayOutputStream> outputStreams = new ArrayList<ByteArrayOutputStream>();

    private Sink currentSink;

    public RandomAccessSink( SinkFactory sinkFactory, OutputStream stream )
        throws IOException
    {
        this.sinkFactory = sinkFactory;
        this.coreOutputStream = stream;
        this.currentSink = sinkFactory.createSink( stream );
        this.coreSink = this.currentSink;
    }

    public RandomAccessSink( SinkFactory sinkFactory, OutputStream stream, String encoding )
        throws IOException
    {
        this.sinkFactory = sinkFactory;
        this.coreOutputStream = stream;
        this.encoding = encoding;
        this.currentSink = sinkFactory.createSink( stream, encoding );
        this.coreSink = this.currentSink;
    }

    public RandomAccessSink( SinkFactory sinkFactory, File outputDirectory, String outputName )
        throws IOException
    {
        this.sinkFactory = sinkFactory;
        this.coreOutputStream = new FileOutputStream( new File( outputDirectory, outputName ) );
        this.currentSink = sinkFactory.createSink( coreOutputStream );
        this.coreSink = this.currentSink;
    }

    public RandomAccessSink( SinkFactory sinkFactory, File outputDirectory, String outputName, String encoding )
        throws IOException
    {
        this.sinkFactory = sinkFactory;
        this.coreOutputStream = new FileOutputStream( new File( outputDirectory, outputName ) );
        this.encoding = encoding;
        this.currentSink = sinkFactory.createSink( coreOutputStream, encoding );
        this.coreSink = this.currentSink;
    }

    /**
     * By calling this method a sink reference is added at the current position. You can write to both the new sink
     * reference and the original sink. After flushing all sinks will be flushed in the right order.
     * 
     * @return a subsink reference you can write to
     */
    public Sink addSinkHook()
    {
        Sink subSink = null;
        try
        {
            ByteArrayOutputStream subOut = new ByteArrayOutputStream();
            ByteArrayOutputStream newOut = new ByteArrayOutputStream();

            outputStreams.add( subOut );
            outputStreams.add( newOut );

            if ( encoding != null )
            {
                subSink = sinkFactory.createSink( subOut, encoding );
                currentSink = sinkFactory.createSink( newOut, encoding );
            }
            else
            {
                subSink = sinkFactory.createSink( subOut );
                currentSink = sinkFactory.createSink( newOut );
            }
            sinks.add( subSink );
            sinks.add( currentSink );
        }
        catch ( IOException e )
        {
            // IOException can only be caused by our own ByteArrayOutputStream
        }
        return subSink;
    }

    @Override
    public void anchor( String name )
    {
        currentSink.anchor( name );
    }

    @Override
    public void anchor( String name, SinkEventAttributes attributes )
    {
        currentSink.anchor( name, attributes );
    }

    @Override
    public void anchor_()
    {
        currentSink.anchor_();
    }

    @Override
    public void author()
    {
        currentSink.author();
    }

    @Override
    public void author( SinkEventAttributes attributes )
    {
        currentSink.author( attributes );
    }

    @Override
    public void author_()
    {
        currentSink.author_();
    }

    @Override
    public void body()
    {
        currentSink.body();
    }

    @Override
    public void body( SinkEventAttributes attributes )
    {
        currentSink.body( attributes );
    }

    @Override
    public void body_()
    {
        currentSink.body_();
    }

    @Override
    public void bold()
    {
        currentSink.bold();
    }

    @Override
    public void bold_()
    {
        currentSink.bold_();
    }

    /**
     * Close all sinks
     */
    public void close()
    {
        for ( Sink sink  : sinks )
        {
            // sink is responsible for closing it's stream
            sink.close();
        }
        coreSink.close();
    }

    @Override
    public void comment( String comment )
    {
        currentSink.comment( comment );
    }

    @Override
    public void date()
    {
        currentSink.date();
    }

    @Override
    public void date( SinkEventAttributes attributes )
    {
        currentSink.date( attributes );
    }

    @Override
    public void date_()
    {
        currentSink.date_();
    }

    @Override
    public void definedTerm()
    {
        currentSink.definedTerm();
    }

    @Override
    public void definedTerm( SinkEventAttributes attributes )
    {
        currentSink.definedTerm( attributes );
    }

    @Override
    public void definedTerm_()
    {
        currentSink.definedTerm_();
    }

    @Override
    public void definition()
    {
        currentSink.definition();
    }

    @Override
    public void definition( SinkEventAttributes attributes )
    {
        currentSink.definition( attributes );
    }

    @Override
    public void definitionList()
    {
        currentSink.definitionList();
    }

    @Override
    public void definitionList( SinkEventAttributes attributes )
    {
        currentSink.definitionList( attributes );
    }

    @Override
    public void definitionListItem()
    {
        currentSink.definitionListItem();
    }

    @Override
    public void definitionListItem( SinkEventAttributes attributes )
    {
        currentSink.definitionListItem( attributes );
    }

    @Override
    public void definitionListItem_()
    {
        currentSink.definitionListItem_();
    }

    @Override
    public void definitionList_()
    {
        currentSink.definitionList_();
    }

    @Override
    public void definition_()
    {
        currentSink.definition_();
    }

    @Override
    public void figure()
    {
        currentSink.figure();
    }

    @Override
    public void figure( SinkEventAttributes attributes )
    {
        currentSink.figure( attributes );
    }

    @Override
    public void figureCaption()
    {
        currentSink.figureCaption();
    }

    @Override
    public void figureCaption( SinkEventAttributes attributes )
    {
        currentSink.figureCaption( attributes );
    }

    @Override
    public void figureCaption_()
    {
        currentSink.figureCaption_();
    }

    @Override
    public void figureGraphics( String name )
    {
        currentSink.figureGraphics( name );
    }

    @Override
    public void figureGraphics( String src, SinkEventAttributes attributes )
    {
        currentSink.figureGraphics( src, attributes );
    }

    @Override
    public void figure_()
    {
        currentSink.figure_();
    }

    /**
     * Flush all sinks
     */
    public void flush()
    {
        for ( int i = 0; i < sinks.size(); i++ )
        {
            // first flush to get complete buffer
            // sink is responsible for flushing it's stream
            Sink sink = sinks.get( i );
            sink.flush();

            ByteArrayOutputStream stream = outputStreams.get( i );
            try
            {
                coreOutputStream.write( stream.toByteArray() );
            }
            catch ( IOException e )
            {
                // @todo
            }
        }
        coreSink.flush();
    }

    @Override
    public void head()
    {
        currentSink.head();
    }

    @Override
    public void head( SinkEventAttributes attributes )
    {
        currentSink.head( attributes );
    }

    @Override
    public void head_()
    {
        currentSink.head_();
    }

    @Override
    public void horizontalRule()
    {
        currentSink.horizontalRule();
    }

    @Override
    public void horizontalRule( SinkEventAttributes attributes )
    {
        currentSink.horizontalRule( attributes );
    }

    @Override
    public void italic()
    {
        currentSink.italic();
    }

    @Override
    public void italic_()
    {
        currentSink.italic_();
    }

    @Override
    public void lineBreak()
    {
        currentSink.lineBreak();
    }

    @Override
    public void lineBreak( SinkEventAttributes attributes )
    {
        currentSink.lineBreak( attributes );
    }

    @Override
    public void link( String name )
    {
        currentSink.link( name );
    }

    @Override
    public void link( String name, SinkEventAttributes attributes )
    {
        currentSink.link( name, attributes );
    }

    @Override
    public void link_()
    {
        currentSink.link_();
    }

    @Override
    public void list()
    {
        currentSink.list();
    }

    @Override
    public void list( SinkEventAttributes attributes )
    {
        currentSink.list( attributes );
    }

    @Override
    public void listItem()
    {
        currentSink.listItem();
    }

    @Override
    public void listItem( SinkEventAttributes attributes )
    {
        currentSink.listItem( attributes );
    }

    @Override
    public void listItem_()
    {
        currentSink.listItem_();
    }

    @Override
    public void list_()
    {
        currentSink.list_();
    }

    @Override
    public void monospaced()
    {
        currentSink.monospaced();
    }

    @Override
    public void monospaced_()
    {
        currentSink.monospaced_();
    }

    @Override
    public void nonBreakingSpace()
    {
        currentSink.nonBreakingSpace();
    }

    @Override
    public void numberedList( int numbering )
    {
        currentSink.numberedList( numbering );
    }

    @Override
    public void numberedList( int numbering, SinkEventAttributes attributes )
    {
        currentSink.numberedList( numbering, attributes );
    }

    @Override
    public void numberedListItem()
    {
        currentSink.numberedListItem();
    }

    @Override
    public void numberedListItem( SinkEventAttributes attributes )
    {
        currentSink.numberedListItem( attributes );
    }

    @Override
    public void numberedListItem_()
    {
        currentSink.numberedListItem_();
    }

    @Override
    public void numberedList_()
    {
        currentSink.numberedList_();
    }

    @Override
    public void pageBreak()
    {
        currentSink.pageBreak();
    }

    @Override
    public void paragraph()
    {
        currentSink.paragraph();
    }

    @Override
    public void paragraph( SinkEventAttributes attributes )
    {
        currentSink.paragraph( attributes );
    }

    @Override
    public void paragraph_()
    {
        currentSink.paragraph_();
    }

    @Override
    public void rawText( String text )
    {
        currentSink.rawText( text );
    }

    @Override
    public void section( int level, SinkEventAttributes attributes )
    {
        currentSink.section( level, attributes );
    }

    @Override
    public void section1()
    {
        currentSink.section1();
    }

    @Override
    public void section1_()
    {
        currentSink.section1_();
    }

    @Override
    public void section2()
    {
        currentSink.section2();
    }

    @Override
    public void section2_()
    {
        currentSink.section2_();
    }

    @Override
    public void section3()
    {
        currentSink.section3();
    }

    @Override
    public void section3_()
    {
        currentSink.section3_();
    }

    @Override
    public void section4()
    {
        currentSink.section4();
    }

    @Override
    public void section4_()
    {
        currentSink.section4_();
    }

    @Override
    public void section5()
    {
        currentSink.section5();
    }

    @Override
    public void section5_()
    {
        currentSink.section5_();
    }

    @Override
    public void section6()
    {
        currentSink.section5();
    }

    @Override
    public void section6_()
    {
        currentSink.section5_();
    }

    @Override
    public void sectionTitle()
    {
        currentSink.sectionTitle();
    }

    @Override
    public void sectionTitle( int level, SinkEventAttributes attributes )
    {
        currentSink.sectionTitle( level, attributes );
    }

    @Override
    public void sectionTitle1()
    {
        currentSink.sectionTitle1();
    }

    @Override
    public void sectionTitle1_()
    {
        currentSink.sectionTitle1_();
    }

    @Override
    public void sectionTitle2()
    {
        currentSink.sectionTitle2();
    }

    @Override
    public void sectionTitle2_()
    {
        currentSink.sectionTitle2_();
    }

    @Override
    public void sectionTitle3()
    {
        currentSink.sectionTitle3();
    }

    @Override
    public void sectionTitle3_()
    {
        currentSink.sectionTitle3_();
    }

    @Override
    public void sectionTitle4()
    {
        currentSink.sectionTitle4();
    }

    @Override
    public void sectionTitle4_()
    {
        currentSink.sectionTitle4_();
    }

    @Override
    public void sectionTitle5()
    {
        currentSink.sectionTitle5();
    }

    @Override
    public void sectionTitle5_()
    {
        currentSink.sectionTitle5_();
    }

    @Override
    public void sectionTitle6()
    {
        currentSink.sectionTitle5();
    }

    @Override
    public void sectionTitle6_()
    {
        currentSink.sectionTitle5_();
    }

    @Override
    public void sectionTitle_()
    {
        currentSink.sectionTitle_();
    }

    @Override
    public void sectionTitle_( int level )
    {
        currentSink.sectionTitle_( level );
    }

    @Override
    public void section_( int level )
    {
        currentSink.section_( level );
    }

    @Override
    public void table()
    {
        currentSink.table();
    }

    @Override
    public void table( SinkEventAttributes attributes )
    {
        currentSink.table( attributes );
    }

    @Override
    public void tableCaption()
    {
        currentSink.tableCaption();
    }

    @Override
    public void tableCaption( SinkEventAttributes attributes )
    {
        currentSink.tableCaption( attributes );
    }

    @Override
    public void tableCaption_()
    {
        currentSink.tableCaption_();
    }

    @Override
    public void tableCell()
    {
        currentSink.tableCell();
    }

    @Override
    public void tableCell( String width )
    {
        currentSink.tableCell( width );
    }

    @Override
    public void tableCell( SinkEventAttributes attributes )
    {
        currentSink.tableCell( attributes );
    }

    @Override
    public void tableCell_()
    {
        currentSink.tableCell_();
    }

    @Override
    public void tableHeaderCell()
    {
        currentSink.tableHeaderCell();
    }

    @Override
    public void tableHeaderCell( String width )
    {
        currentSink.tableHeaderCell( width );
    }

    @Override
    public void tableHeaderCell( SinkEventAttributes attributes )
    {
        currentSink.tableHeaderCell( attributes );
    }

    @Override
    public void tableHeaderCell_()
    {
        currentSink.tableHeaderCell_();
    }

    @Override
    public void tableRow()
    {
        currentSink.tableRow();
    }

    @Override
    public void tableRow( SinkEventAttributes attributes )
    {
        currentSink.tableRow( attributes );
    }

    @Override
    public void tableRow_()
    {
        currentSink.tableRow_();
    }

    @Override
    public void tableRows( int[] justification, boolean grid )
    {
        currentSink.tableRows( justification, grid );
    }

    @Override
    public void tableRows_()
    {
        currentSink.tableRows_();
    }

    @Override
    public void table_()
    {
        currentSink.table_();
    }

    @Override
    public void text( String text )
    {
        currentSink.text( text );
    }

    @Override
    public void text( String text, SinkEventAttributes attributes )
    {
        currentSink.text( text, attributes );
    }

    @Override
    public void title()
    {
        currentSink.title();
    }

    @Override
    public void title( SinkEventAttributes attributes )
    {
        currentSink.title( attributes );
    }

    @Override
    public void title_()
    {
        currentSink.title_();
    }

    @Override
    public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
    {
        currentSink.unknown( name, requiredParams, attributes );
    }

    @Override
    public void verbatim( boolean boxed )
    {
        currentSink.verbatim( boxed );
    }

    @Override
    public void verbatim( SinkEventAttributes attributes )
    {
        currentSink.verbatim( attributes );
    }

    @Override
    public void verbatim_()
    {
        currentSink.verbatim_();
    }

    @Override
    public void enableLogging( Log log )
    {
        currentSink.enableLogging( log );
    }
}