/*
 * [TestBufferedRatio.java]
 *
 * Summary: Discover best way to allocate buffers.
 *
 * Copyright: (c) 2014-2017 Roedy Green, Canadian Mind Products, http://mindprod.com
 *
 * Licence: This software may be copied and used freely for any purpose but military.
 *          http://mindprod.com/contact/nonmil.html
 *
 * Requires: JDK 1.8+
 *
 * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
 *
 * Version History:
 *  1.0 2014-05-10 initial version
 */
package com.mindprod.example;

import com.mindprod.common18.EIO;
import com.mindprod.hunkio.HunkIO;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.Random;

import static java.lang.System.*;

/**
 * Discover best way to allocate buffers.
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 1.0 2014-05-10 initial version
 * @since 2014-05-10
 */
public final class TestBufferedRatio
    {
    /**
     * how many benchmark trials
     */
    private static final int TRIALS = 5;

    /**
     * size of test file in chars
     */
    private static final long FILESIZE = 200L * 1024 * 1024;

    /**
     * size of line in chars including \n
     */
    private static final int LINESIZE = 100;

    /**
     * allows us to display seconds to zero decimal  places.
     */
    private static final DecimalFormat df0 = new DecimalFormat( "###,##0" );

    /**
     * allows us to display seconds to two decimal  places.
     */
    private static final DecimalFormat df2 = new DecimalFormat( "###,##0.00" );

    /**
     * chars we allow in our random data
     */
    private static final String ALLOWED = "abcdefghijklmnopqrstuvwxyzABCDEFGHJKMLMOPQRSTUVWXYZ 0123456789 +!@$#$%^&*()_-?/|\\\n";

    /**
     * character set to use
     */
    private static final Charset charset = Charset.forName( "UTF-16" );

    /**
     * sample date file we read to test
     */
    private static File sampleDataFile;

    /**
     * display nanoseconds as seconds to two decimal places.
     *
     * @param nanoseconds how many nanoseconds ( billionths of a second )
     *
     * @return displayable String.
     */
    private static String asSeconds( double nanoseconds )
        {
        return df2.format( nanoseconds / 1000000000d ) + " seconds";
        }

    /**
     * generate 100K of sample data in a temporary file.
     */
    private static void generateSampleData() throws IOException
        {
        // place on hard disk, not SSD
        HunkIO.createTempFile( "temp_", ".tmp", new File( "H:/temp" ) );
        sampleDataFile = File.createTempFile( "temp_", ".tmp" );
        final Random wheel = new Random();
        final BufferedWriter bw = EIO.getBufferedWriter( sampleDataFile, 256 * 1024, charset );
        char[] line = new char[ LINESIZE - 1 ];
        for ( int i = 0; i < ( FILESIZE / LINESIZE ); i++ )
            {
            for ( int j = 0; j < line.length; j++ )
                {
                line[ j ] = ALLOWED.charAt( ( wheel.nextInt() & Integer.MAX_VALUE ) % ALLOWED.length() );
                }
            bw.write( line ); // encoding irrelevant here
            bw.write( "\n" );
            }
        bw.close();
        }

    /**
     * read test file with a withBufferedReader
     *
     * @param ratio    0.6 means give 60% of buffer space to the inputwriter and 40% to the inputStream
     * @param buffsize total buffer space to allocate in bytes.
     *
     * @throws java.io.IOException if trouble reading test file
     */
    private static void withBufferedReader( double ratio, int buffsize ) throws IOException
        {
        assert .1 <= ratio && ratio <= .9 : "ratio must be in range [0.1,0.9].";
        long prevTime = System.nanoTime();
        final FileInputStream fis = new FileInputStream( sampleDataFile );
        final BufferedInputStream bis = new BufferedInputStream( fis, ( int ) ( buffsize * ( 1 - ratio ) ) ); /* buffsize in bytes */
        final InputStreamReader isr = new InputStreamReader( bis, charset );
        final BufferedReader ibr = new BufferedReader( isr, ( int ) ( buffsize * ratio / 2 /* in chars */ ) );
        String line;
        //noinspection UnusedAssignment
        while ( ( line = ibr.readLine() ) != null )
            {
            // empty loop
            }
        ibr.close();
        out.println( "BufferedReader backed with BufferedInputStream ratio " +
                     df2.format( ratio ) + " buffsize " + buffsize + " bytes  " + asSeconds( System.nanoTime() - prevTime ) );
        }

    /**
     * read test file with a HunkIO
     *
     * @throws java.io.IOException if trouble reading test file
     */
    private static void withHunkIO() throws IOException
        {
        long prevTime = System.nanoTime();
        @SuppressWarnings( "UnusedAssignment" ) String contents = HunkIO.readEntireFile( sampleDataFile, charset );
        out.println( "HunkIO " + asSeconds( System.nanoTime() - prevTime ) );
        }

    /**
     * read test file with a withBufferedReader
     *
     * @param buffsize total buffer space to allocate in bytes.
     *
     * @throws java.io.IOException if trouble reading test file
     */
    private static void withPlainBufferedReader( int buffsize ) throws IOException
        {
        long prevTime = System.nanoTime();
        // cannot specify encoding.
        final BufferedReader ibr = new BufferedReader( new FileReader( sampleDataFile ), buffsize / 2 );
        String line;
        //noinspection UnusedAssignment
        while ( ( line = ibr.readLine() ) != null )
            {
            // empty loop
            }
        ibr.close();
        out.println( "Unbacked BufferedReader default encoding buffsize " + buffsize + " bytes " + asSeconds( System.nanoTime() - prevTime ) );
        }

    /**
     * Benchmark three ways of buffered writing and three ways of buffered reading.
     *
     * @param args not used
     *
     * @throws java.io.IOException if problem writing or reading the test file.
     */
    public static void main( String[] args ) throws IOException
        {
        generateSampleData();
        out.println( "Using a random sample data file of "
                     + df0.format( FILESIZE )
                     + " chars "
                     + df0.format( sampleDataFile.length() )
                     + " bytes." );
        out.println( "Using charset " + charset );
        int buffSize = 64 * 1024;
        out.println( "Using aggregate buffersize of " + df0.format( buffSize ) + " bytes." );
        for ( int i = 0; i < TRIALS; i++ )
            {
            withPlainBufferedReader( buffSize );
            withBufferedReader( .1, buffSize );
            withBufferedReader( .2, buffSize );
            withBufferedReader( .3, buffSize );
            withBufferedReader( .4, buffSize );
            withBufferedReader( .5, buffSize );
            withBufferedReader( .6, buffSize );
            withBufferedReader( .7, buffSize );
            withBufferedReader( .8, buffSize );
            withBufferedReader( .9, buffSize );
            withHunkIO();
            }
        //noinspection ResultOfMethodCallIgnored
        sampleDataFile.delete();
        // the results show the HunkIO is fastest.  Next is BufferedReader with a ratio of .5
        // and last is a Plain BufferedReader. The optimum ratio is the same no matter what the buffer size.
        }
    }