/*
 * [TestReadFileAtOnce.java]
 *
 * Summary: Discover best way te reay an entire file all at once.
 *
 * 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-16 initial version
 */
package com.mindprod.example;

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

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.Random;

import static java.lang.System.*;

/**
 * Discover best way te reay an entire file all at once.
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 1.0 2014-05-16 initial version
 * @since 2014-05-16
 */
public final class TestReadFileAtOnce
    {
    // declarations

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

    /**
     * how many benchmark trials
     */
    private static final int TRIALS = 5;

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

    /**
     * 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-8" );

    /**
     * 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" );

    /**
     * sample date file we read to test
     */
    private static File sampleDataFile = null;
    // /declarations
    // methods

    /**
     * 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";
        }// /method

    /**
     * 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();
        }// /method

    /**
     * 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." );
        for ( int i = 0; i < TRIALS; i++ )
            {
            readFileAtOnceWithRandomAccess( sampleDataFile );
            readFileAtOnceWithInputStream( sampleDataFile );
            readFileAtOnceWithNIO( sampleDataFile );
            }
        //noinspection ResultOfMethodCallIgnored
        sampleDataFile.delete();
        // results
        // Using a random sample data file of 419,430,400 chars 419,430,400 bytes.
        // RandomAccess 1.46 seconds
        // InputStream 1.48 seconds
        // NIO 1.56 seconds
        }// /method

    /**
     * read file of bytes in one I/O and convert to a String using an InputStream
     *
     * @param fromFile file to read
     *
     * @throws IOException when cannot access file.
     * @noinspection WeakerAccess
     */
    public static String readFileAtOnceWithInputStream( File fromFile ) throws IOException
        {
        final long prevTime = System.nanoTime();
        final int size = ( int ) fromFile.length();
        final FileInputStream fis = new FileInputStream( fromFile );
        // R E A D
        final byte[] rawContents = new byte[ size ];
        final int bytesRead = fis.read( rawContents );
        if ( bytesRead != size )
            {
            throw new IOException( "error: problems reading InputStream file " + fromFile );
            }
        // C L O S E
        fis.close();
        final String result = new String( rawContents, charset );
        out.println( "InputStream " + asSeconds( System.nanoTime() - prevTime ) );
        return result;
        }// /method

    /**
     * read file of bytes in one I/O and convert to a String using NIO
     *
     * @param fromFile file to read
     *
     * @return String that was in the file
     * @throws IOException when cannot access file.
     * @noinspection WeakerAccess
     */
    public static String readFileAtOnceWithNIO( File fromFile ) throws IOException
        {
        final long prevTime = System.nanoTime();
        final int size = ( int ) fromFile.length();
        final ByteBuffer b = ByteBuffer.allocateDirect( size );
        final RandomAccessFile raf = new RandomAccessFile( fromFile, "r" );
        final FileChannel fc = raf.getChannel();
        fc.read( b );
        if ( fc.position() != size )
            {
            throw new IOException( "error: problems reading NIO file " + fromFile );
            }
        b.flip(); // rewinds position back to 0, ready to process with get
        fc.close();
        // b.array is unsupported, so we have to use b.get
        final byte[] holder = new byte[ size ];
        // results in holder, copied in byte by byte.
        b.get( holder );
        final String result = new String( holder, charset );
        out.println( "NIO " + asSeconds( System.nanoTime() - prevTime ) );
        return result;
        }// /method

    /**
     * read file of bytes in one I/O and convert to a String, using a RAF.
     *
     * @param fromFile file to read
     *
     * @return String that was in the file
     * @throws IOException when cannot access file.
     * @noinspection WeakerAccess
     */
    public static String readFileAtOnceWithRandomAccess( File fromFile ) throws IOException
        {
        final long prevTime = System.nanoTime();
        final int size = ( int ) fromFile.length();
        final RandomAccessFile raf = new RandomAccessFile( fromFile, "r" );
        // R E A D
        final byte[] rawContents = new byte[ size ];
        final int bytesRead = raf.read( rawContents );
        if ( bytesRead != size )
            {
            throw new IOException( "error: problems reading RandomAccess file " + fromFile );
            }
        // C L O S E
        raf.close();
        final String result = new String( rawContents, charset );
        out.println( "RandomAccess " + asSeconds( System.nanoTime() - prevTime ) );
        return result;
        }// /method
    // /methods
    }