package water;

import hex.NNvsNeuralNet;
import hex.NeuralNetIrisTest;
import hex.NeuralNetSpiralsTest;
import org.apache.commons.lang.ArrayUtils;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import water.deploy.Node;
import water.deploy.NodeVM;
import water.parser.ParseFolderTestBig;
import water.util.Log;
import water.util.Utils;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class JUnitRunner {
  // TODO
  @Retention(RetentionPolicy.RUNTIME)
  public @interface Nightly {
  }

  private static void filter(List<Class> tests) {
    // Requires separate datasets project
    tests.remove(ParseFolderTestBig.class);
    // Too slow
    tests.remove(ConcurrentKeyTest.class);
    tests.remove(ValueArrayToFrameTestAll.class);
    tests.remove(NNvsNeuralNet.class);
    tests.remove(NeuralNetIrisTest.class); //old NeuralNet
    tests.remove(NeuralNetSpiralsTest.class); //old NeuralNet
    // getting failures on 3/7/14. kbn
    tests.remove(hex.NeuralNetSpiralsTest2.class);
    tests.remove(hex.NeuralNetProstateTest.class);

    // Pure JUnit test
//    tests.remove(CBSChunkTest.class);
    //tests.remove(GBMDomainTest.class);
  }

  public static void main(String[] args) throws Exception {
    // Can be necessary to run in parallel to other clouds, so find open ports
    int[] ports = new int[3];
    int port = 54321;
    for( int i = 0; i < ports.length; i++ ) {
      for( ;; ) {
        if( isOpen(port) && isOpen(port + 1) ) {
          ports[i] = port;
          port += 2;
          break;
        }
        port++;
      }
    }
    String flat = "";
    for( int i = 0; i < ports.length; i++ )
      flat += "127.0.0.1:" + ports[i] + "\n";
    // Force all IPs to local so that users can run with a firewall
    String[] a = new String[] { "-ip", "127.0.0.1", "-flatfile", Utils.writeFile(flat).getAbsolutePath() };
    H2O.OPT_ARGS.ip = "127.0.0.1";
    args = (String[]) ArrayUtils.addAll(a, args);

    ArrayList<Node> nodes = new ArrayList<Node>();
    for( int i = 1; i < ports.length; i++ )
      nodes.add(new NodeVM(Utils.append(args, "-port", "" + ports[i])));

    args = Utils.append(new String[] { "-mainClass", Master.class.getName() }, args);
    Node master = new NodeVM(Utils.append(args, "-port", "" + ports[0]));
    nodes.add(master);

    File out = null, err = null, sandbox = new File("sandbox");
    sandbox.mkdirs();
    Utils.clearFolder(sandbox);
    for( int i = 0; i < nodes.size(); i++ ) {
      out = File.createTempFile("junit-" + i + "-out-", null, sandbox);
      err = File.createTempFile("junit-" + i + "-err-", null, sandbox);
      nodes.get(i).persistIO(out.getAbsolutePath(), err.getAbsolutePath());
      nodes.get(i).start();
    }

    int exit = master.waitFor();
    if( exit != 0 ) {
      Log.log(out, System.out);
      Thread.sleep(100); // Or mixed (?)
      Log.log(err, System.err);
    }
    for( Node node : nodes )
      node.kill();
    if( exit == 0 )
      System.out.println("OK");
    System.exit(exit);
  }

  private static boolean isOpen(int port) throws Exception {
    ServerSocket s = null;
    try {
      s = new ServerSocket(port);
      return true;
    } catch( IOException ex ) {
      return false;
    } finally {
      if( s != null )
        s.close();
    }
  }

  static List<Class> all() {
    List<String> names = Boot.getClasses();
    names.remove("water.Boot"); // In case called from Boot loader
    names.remove("water.Weaver");
    Collections.sort(names); // For deterministic runs
    List<Class> tests = new ArrayList<Class>();
    Log._dontDie = true;
    for( String name : names ) {
      try {
        Class c = Class.forName(name);
        if( isTest(c) )
          tests.add(c);
      } catch( Throwable _ ) {
      }
    }
    if( tests.size() == 0 )
      throw new RuntimeException("Failed to find tests");
    filter(tests);
    return tests;
  }

  private static boolean isTest(Class c) {
    for( Annotation a : c.getAnnotations() )
      if( a instanceof Ignore )
        return false;
    for( Method m : c.getMethods() )
      for( Annotation a : m.getAnnotations() )
        if( a instanceof Test )
          return true;
    return false;
  }

  public static class Master {
    public static void main(String[] args) {
      try {
        H2O.main(args);
        TestUtil.stall_till_cloudsize(3);
        List<Class> tests = JUnitRunner.all();
        Result r = org.junit.runner.JUnitCore.runClasses(tests.toArray(new Class[0]));
        if( r.getFailureCount() == 0 ) {
          System.out.println("Successfully ran the following tests in " + (r.getRunTime() / 1000) + "s");
          for( Class c : tests )
            System.out.println(c.getName());
        } else {
          for( Failure f : r.getFailures() ) {
            System.err.println(f.getDescription());
            if( f.getException() != null )
              f.getException().printStackTrace();
          }
        }
        System.exit(r.getFailureCount());
      } catch( Throwable t ) {
        t.printStackTrace();
        System.exit(1);
      }
    }
  }
}

@@ -1247,150 +1247,8 @@
 Net%0A
-    // getting failures on 3/7/14. kbn%0A    tests.remove(hex.NeuralNetSpiralsTest2.class);%0A    tests.remove(hex.NeuralNetProstateTest.class);%0A%0A
     

Revert "getting failures in build.sh Junit, from two NN tests"