/*
 * Copyright (c) 2014. Real Time Genomics Limited.
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.rtg.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

import junit.framework.TestCase;

/**
 * Tests for <code>SpawnJvm</code>.
 *
 */
public final class SpawnJvmTest extends TestCase {

  private static final String LS = System.lineSeparator();

  static final class MaxMem {
    private MaxMem() {
    }
    /**
   * Main to run from tests from command line.
   * @param args ignored.
   */
  public static void main(final String[] args) {
      System.out.println(Runtime.getRuntime().maxMemory());
    }
  }

  static final class ArgsPrinter {
    private ArgsPrinter() {
    }
    /**
   * Main to run from tests from command line.
   * @param args ignored.
   */
  public static void main(final String[] args) {
      System.out.println(args.length);
    for (String arg : args) {
      System.out.println(arg);
    }
    }
  }

  static final class Rot13 {
    private Rot13() {
    }

    /**
   * Main to run from tests from command line.
   * @param args ignored.
   */
  public static void main(final String[] args) throws IOException {
      final InputStreamReader input = new InputStreamReader(System.in);
      int c;
      while ((c = input.read()) != -1) {
        if (c >= 'A' && c <= 'M' || c >= 'a' && c <= 'm') {
          c += 'N' - 'A';
        } else if (c >= 'N' && c <= 'Z' || c >= 'n' && c <= 'z') {
          c -= 'N' - 'A';
        }
        System.out.print((char) c);
      }
    }
  }

  static final class Interactive {
    private Interactive() {
    }
    /**
   * Main to run from tests from command line.
   * @param args ignored.
   */
  public static void main(final String[] args) throws IOException {
      final BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
      String line;
      while ((line = input.readLine()) != null) {
        System.out.println(line.length());
      }
    }
  }

  public void testOutput() throws IOException {
    assertEquals("0" + LS, runClass(null, ArgsPrinter.class.getName()));

    assertEquals("3" + LS + "1" + LS + "2" + LS + "3" + LS, runClass(null, ArgsPrinter.class.getName(), "1", "2", "3"));
  }

  public void testInput() throws IOException {
    assertEquals("nOPQrSTUvWXYZAbCDEFGhIJKlM" + LS, runClass("aBCDeFGHiJKLMNoPQRSTuVWXyZ" + LS, Rot13.class.getName()));
  }

  public void testMemory1() throws IOException {
    final int bits = Integer.parseInt(System.getProperty("sun.arch.data.model"));

    ProcessOutput streams = new ProcessOutput(null, 134217728, MaxMem.class.getName());
    if (streams.mRetCode == 0) {
      return; //doesn't fail reliably on some platforms (MacOSX succeeds)
    }
    assertTrue(streams.mRetCode != 0); //this was > 0 changed to see if windows 2008 return negative return code

    streams = new ProcessOutput(null, 4096, MaxMem.class.getName());
    switch (bits) {
      case 32:
        assertEquals("", streams.mStdOut);
        System.out.println(streams.mStdErr);
        assertTrue(streams.mStdErr.contains("Invalid maximum heap size")
            || streams.mStdErr.contains("Malformed option: '-Xmx4294967296'"));
        break;
      case 64:
        assertEquals("", streams.mStdErr);
        final long mem = Long.parseLong(streams.mStdOut.trim());
        assertTrue(mem > 4096 * 1048576L * 0.87);
        break;
      default:
        throw new RuntimeException("Computer not 32 or 64-bit");
    }
  }

  public void testMemory2() throws IOException {
    for (int mb = 32; mb <= 1024; mb <<= 1) {
      final long mem = Long.parseLong(runClass(null, mb, MaxMem.class.getName()).trim());
      assertTrue(mem > mb * 1048576L * 0.87);
    }

    final String osWindows = "Windows";
    if (System.getProperty("os.name").startsWith(osWindows) && Environment.is64BitVM()) {
    final long mem = Long.parseLong(runClass(null, 1436, MaxMem.class.getName()).trim());
    assertTrue(mem > 1436 * 1048576L * 0.87);
    }
  }

  public void testInteraction() throws IOException {
    final Process p = SpawnJvm.spawn(Interactive.class.getName());
    try {
      try (OutputStreamWriter out = new OutputStreamWriter(p.getOutputStream())) {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
          out.write(LS);
          out.flush();
          assertEquals("0", in.readLine());
          out.write("abcdef" + LS);
          out.flush();
          assertEquals("6", in.readLine());
        }
      }
      try {
        assertEquals(0, p.waitFor());
      } catch (final InterruptedException e) {
        fail(e.toString());
      }
      assertEquals("", readStream(p.getErrorStream()));
    } finally {
      p.getInputStream().close();
      p.getOutputStream().close();
      p.getErrorStream().close();
    }
  }


  private static String readStream(final InputStream is) throws IOException {
    try {
      final InputStreamReader isr = new InputStreamReader(is);

      final StringBuilder sb = new StringBuilder();
      final char[] buffer = new char[4096];
      int length;
      while ((length = isr.read(buffer)) != -1) {
        sb.append(buffer, 0, length);
      }
      return sb.toString();
    } finally {
      is.close();
    }
  }

  public static String runClass(final String input, final String className, final String... args) throws IOException {
    return runClass(input, 64, className, args);
  }

  public static String runClass(final String input, final int maxMem, final String className, final String... args) throws IOException {
    final ProcessOutput streams = new ProcessOutput(input, maxMem, className, args);
    assertEquals("", streams.mStdErr);
    return streams.mStdOut;
  }

  /**
   * Used for holding output of a JVM run.
   */
  static class ProcessOutput {
    String mStdOut;
    private final String mStdErr;
    private int mRetCode;

    ProcessOutput(final String input, final int maxMem, final String className, final String... args) throws IOException {
      final Process p = SpawnJvm.spawn(maxMem * 1024L * 1024L, className, args);
      try {
        if (input != null) {
          try (OutputStreamWriter out = new OutputStreamWriter(p.getOutputStream())) {
            out.write(input);
          }
        }
        final String[] streams = {readStream(p.getInputStream()), readStream(p.getErrorStream())};
        try {
          mRetCode = p.waitFor();
        } catch (final InterruptedException e) {
          fail(e.toString());
        }
        mStdOut = streams[0];
        mStdErr = streams[1];
      } finally {
        p.getOutputStream().close();
        p.getErrorStream().close();
        p.getInputStream().close();
      }
    }
  }
}