//
// ExpectedOutput.java -- Java class ExpectedOutput
// Project OrcTests
//
// Copyright (c) 2017 The University of Texas at Austin. All rights reserved.
//
// Use and redistribution of this file is governed by the license terms in
// the LICENSE file found in the project's top-level directory and also found at
// URL: http://orc.csres.utexas.edu/license.shtml .
//

package orc.test;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

/**
 * We look for one or more comment blocks with a line starting with "OUTPUT:",
 * and take everything in the comment after the "OUTPUT:" line to be a possible
 * output of the program. Example:
 *
 * <pre>
 * {-
 * OUTPUT:
 * 1
 * 2
 * -}
 * 1 | 2
 * </pre>
 *
 * An output block starting with "OUTPUT:PERMUTABLE" specifies that any
 * permutation of the given values is a valid output. Example:
 *
 * <pre>
 * {-
 * OUTPUT:PERMUTABLE
 * 1
 * 2
 * 3
 * -}
 * </pre>
 *
 * says that the program may publish the values 1, 2 and 3 in any order. If none
 * of the expected outputs match the actual output, the test fails for that
 * example. The multiple output blocks and OUTPUT:PERMUTABLE feature let us cope
 * with limited non-determinism, but a better solution is needed for serious
 * testing of non-deterministic programs.
 *
 * @author quark, srosario
 */
public class ExpectedOutput {
    private final List<MaybePermutableOutput> outs = new LinkedList<MaybePermutableOutput>();
    private boolean shouldBenchmark = false;

    public ExpectedOutput(final File file) throws IOException {
        final BufferedReader r = new BufferedReader(new FileReader(file));

        boolean permutable = false;
        StringBuilder oneOutput = null;
        for (String line = r.readLine(); line != null; line = r.readLine()) {
            if (oneOutput != null) {
                if (line.startsWith("-}")) {
                    outs.add(new MaybePermutableOutput(permutable, oneOutput.toString()));
                    oneOutput = null;
                } else {
                    oneOutput.append(line);
                    oneOutput.append("\n");
                }
            } else if (line.startsWith("OUTPUT:PERMUTABLE")) {
                permutable = true;
                oneOutput = new StringBuilder();
            } else if (line.startsWith("OUTPUT:")) {
                permutable = false;
                oneOutput = new StringBuilder();
            } else if (line.startsWith("BENCHMARK")) {
                shouldBenchmark = true;
            }
        }
    }

    public boolean contains(final String actual) {
        for (final MaybePermutableOutput o : outs) {
            if (o.matches(actual)) {
                return true;
            }
        }
        return false;
    }

    public boolean isEmpty() {
        return outs.isEmpty();
    }

    public boolean shouldBenchmark() {
        return shouldBenchmark;
    }

    static class MaybePermutableOutput {
        private boolean permutable = false;
        String output;

        public MaybePermutableOutput(final boolean perm, final String out) {
            this.permutable = perm;
            this.output = out;
        }

        public boolean matches(final String actual) {
            if (!permutable) {
                return output.equals(actual);
            }

            // Check all if the actual output is a permutation of the expected
            // output.
            final String[] actualArr = actual.split("\\n");
            final String[] expectedArr = output.split("\\n");

            if (actualArr.length != expectedArr.length) {
                return false;
            }

            final LinkedList<String> actuals = new LinkedList<String>();
            for (final String s : actualArr) {
                actuals.add(s);
            }

            final LinkedList<String> expected = new LinkedList<String>();
            for (final String s : expectedArr) {
                expected.add(s);
            }

            for (final String s : actuals) {
                if (!expected.contains(s)) {
                    return false;
                }
                expected.remove(s);
            }

            return true;
        }
    }
}

@@ -1713,24 +1713,42 @@
 er(file));%0A%0A
+        try %7B%0A    
         bool
@@ -1775,24 +1775,28 @@
 se;%0A        
+ 
+   
 StringBuilde
@@ -1807,32 +1807,36 @@
 eOutput = null;%0A
+    
         for (Str
@@ -1905,24 +1905,28 @@
             
+   
+ 
 if (oneOutpu
@@ -1934,24 +1934,28 @@
  != null) %7B%0A
+    
             
@@ -1983,24 +1983,28 @@
 th(%22-%7D%22)) %7B%0A
+    
             
@@ -2094,32 +2094,36 @@
                 
+    
 oneOutput = null
@@ -2136,24 +2136,28 @@
             
+    
 %7D else %7B%0A   
@@ -2165,32 +2165,36 @@
                 
+  
+  
 oneOutput.append
@@ -2201,16 +2201,20 @@
 (line);%0A
+    
         
@@ -2257,34 +2257,42 @@
                 
+   
+ 
 %7D%0A
+    
             %7D el
@@ -2334,24 +2334,28 @@
 UTABLE%22)) %7B%0A
+    
             
@@ -2385,32 +2385,36 @@
                 
+    
 oneOutput = new 
@@ -2422,32 +2422,36 @@
 tringBuilder();%0A
+    
             %7D el
@@ -2495,32 +2495,36 @@
                 
+ 
+   
 permutable = fal
@@ -2519,32 +2519,36 @@
 utable = false;%0A
+    
                 
@@ -2584,32 +2584,36 @@
 ();%0A            
+   
+ 
 %7D else if (line.
@@ -2655,16 +2655,20 @@
         
+    
 shouldBe
@@ -2675,32 +2675,36 @@
 nchmark = true;%0A
+    
             %7D%0A  
@@ -2693,32 +2693,87 @@
               %7D%0A
+            %7D%0A        %7D finally %7B%0A          r.close();%0A
         %7D%0A    %7D%0A

Fix warning and potential file handle leak.