import java.io.*;

/**
 *  Benchmark #8 - Merge sort on disk.
 *   @author  Josef.Pelikan@mff.cuni.cz
 *   @version 0.007 (2001/06/20)
 *   @since   0.007
 */
public class BenchmarkMergeSort extends Benchmark {

    /** Sort array size. */
    protected final static long SORT_SIZE = 1L << 23;

    /** Memory bucket size. */
    protected final static long BUCKET_SIZE = 1024L;

    /** Sorting was successful. */
    protected boolean ok;

    /**
     *  Initializing constructor.
     */
    public BenchmarkMergeSort () {
    }

    /**
     *  Test (benchmark) ordinal number.
     *   @return Ordinal number of the test.
     */
    public int testNumber () {
        return 8;
    }

    /**
     *  Checks whether the given file is sorted (in ascending order).
     *   @param  f File to be checked.
     *   @return <code>true</code> if the given file is OK.
     */
    protected static boolean checkFile ( FileOfDouble f )
    {
        double prev, act;
        f.seek(0);
        act = f.get();
        while ( true ) {
            prev = act;
            act = f.get();
            if ( act < 0.0 ) return true;
            if ( act < prev ) return false;
            }
    }

    protected static void initialSplit ( FileOfDouble from,
                                         FileOfDouble to1, FileOfDouble to2, long seg[] )
    {
        to1.rewrite();
        seg[0] = seg[1] = 0L;
        to2.rewrite();
        from.seek(0);
        double prev, act;
        act = from.get();
        if ( act < 0.0 ) return;
        boolean one = true;
        to1.put(act);
        seg[0] = 1L;

        do {
            prev = act;
            act = from.get();
            if ( act < 0.0 ) return;
            if ( act < prev ) {
                one = !one;
                seg[ one ? 0 : 1 ]++;
                }
            if ( one ) to1.put(act);
            else       to2.put(act);
            } while ( true );
    }

    protected static void mergeAndSplit ( FileOfDouble from1, FileOfDouble from2,
                                          FileOfDouble to1, FileOfDouble to2, long seg[] )
    {
        to1.rewrite();
        seg[0] = seg[1] = 0L;
        to2.rewrite();
        from1.seek(0);
        from2.seek(0);
        double prev1, act1;
        double prev2, act2;
        prev1 = from1.get();
        act1 = from1.get();
        prev2 = from2.get();
        act2 = from2.get();
        boolean one = true;
        seg[0] = 1L;

        do {
                // min { prev1, prev2 } should be written out
            if ( prev1 < prev2 ) {
                if ( one ) to1.put(prev1);
                else       to2.put(prev1);
                if ( prev1 > act1 ) {           // copy the rest of from2 segment
                    // prev1 > act1, prev1 was processed
                    do {
                        if ( one ) to1.put(prev2);
                        else       to2.put(prev2);
                        if ( act2 < prev2 ) break;
                        prev2 = act2;
                        act2 = from2.get();
                        } while ( true );
                    // prev2 > act2, prev2 was processed
                    if ( act1 < 0.0 || act2 < 0.0 ) // from1 or from2 were exhausted
                        break;
                    one = !one;
                    seg[ one ? 0 : 1 ]++;
                    prev2 = act2;
                    act2 = from2.get();
                    }
                prev1 = act1;
                act1 = from1.get();
                }
            else {                              // prev2 < prev1
                if ( one ) to1.put(prev2);
                else       to2.put(prev2);
                if ( prev2 > act2 ) {           // copy the rest of from1 segment
                    // prev2 > act2, prev2 was processed
                    do {
                        if ( one ) to1.put(prev1);
                        else       to2.put(prev1);
                        if ( act1 < prev1 ) break;
                        prev1 = act1;
                        act1 = from1.get();
                        } while ( true );
                    // prev1 > act1, prev1 was processed
                    if ( act1 < 0.0 || act2 < 0.0 ) // from1 or from2 were exhausted
                        break;
                    one = !one;
                    seg[ one ? 0 : 1 ]++;
                    prev1 = act1;
                    act1 = from1.get();
                    }
                prev2 = act2;
                act2 = from2.get();
                }
            } while ( true );

        while ( act1 >= 0.0 ) {                 // from1 wasn't exhausted
            one = seg[0] < seg[1];
            if ( one ) {                        // put the 1st item
                seg[0]++;
                to1.put(act1);
                }
            else {
                seg[1]++;
                to2.put(act1);
                }
            do {
                prev1 = act1;
                act1 = from1.get();
                if ( prev1 > act1 ) break;
                if ( one ) to1.put(act1);
                else       to2.put(act1);
                } while ( true );
            }

        if ( act2 >= 0.0 ) {                    // from2 wasn't exhausted
            one = seg[0] < seg[1];
            if ( one ) {                        // put the 1st item
                seg[0]++;
                to1.put(act2);
                }
            else {
                seg[1]++;
                to2.put(act2);
                }
            do {
                prev2 = act2;
                act2 = from2.get();
                if ( prev2 > act2 ) break;
                if ( one ) to1.put(act2);
                else       to2.put(act2);
                } while ( true );
            }
    }

    /**
     *  The benchmark algorithm itself. Needs not be re-entrant but can be iterated.
     */
    protected void test () {

        ok = false;
        System.out.print("init: random generator, bucket-sort\r");
        RandomJames rnd = new RandomJames();
        FileOfDouble f1 = new FileOfDouble("sort1.bin",true);
        FileOfDouble f2 = new FileOfDouble("sort2.bin",true);
        FileOfDouble f3 = new FileOfDouble("sort3.bin",true);
        FileOfDouble f4 = new FileOfDouble("sort4.bin",true);
        long seg12[] = { 0L, 0L };
        long seg34[] = { 0L, 0L };
        long i, j;

            // file init:
        double bucket[] = new double[(int)BUCKET_SIZE];
        for ( i = 0; i++ < SORT_SIZE/BUCKET_SIZE; ) {
            rnd.uniformNumbers(bucket);
            QuickSortDouble.sort(bucket,0,(int)BUCKET_SIZE);
            for ( j = 0; j < BUCKET_SIZE; j++ )
                f3.put(bucket[(int)j]);
            }

            // merge-sort:
        initialSplit(f3,f1,f2,seg12);
        do {
            System.out.print( " " + seg12[0] + " " + seg12[1] + "                            \r" );
            System.out.flush();
            if ( seg12[1] == 0L ) {
                ok = checkFile(f1);
                break;
                }
            if ( seg12[0] == 0L ) {
                ok = checkFile(f2);
                break;
                }
            mergeAndSplit(f1,f2,f3,f4,seg34);
            System.out.print( " " + seg34[0] + " " + seg34[1] + "                            \r" );
            System.out.flush();
            if ( seg34[1] == 0L ) {
                ok = checkFile(f3);
                break;
                }
            if ( seg34[0] == 0L ) {
                ok = checkFile(f4);
                break;
                }
            mergeAndSplit(f3,f4,f1,f2,seg12);
            } while ( true );

            // the right order was still checked
        f1.close();
        f2.close();
        f3.close();
        f4.close();
        File f;
        try {
            f = new File("sort1.bin");
            f.delete();
            f = new File("sort2.bin");
            f.delete();
            f = new File("sort3.bin");
            f.delete();
            f = new File("sort4.bin");
            f.delete();
            }
        catch ( SecurityException e ) {
            }
    }

    /**
     *  Result string of the test. Should be called after the <code>runTest()</code>
     *  was finished.
     *   @return Result of the test.
     */
    public String testResult () {
        return( "Merge-sort on disk: " + (ok ? "OK" : "failed") +
                ", file size: " + (SORT_SIZE >> 17) + " MB" );
    }

}
