package fitnesse.testsystems.slim;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.SocketException;
import java.util.concurrent.atomic.AtomicInteger;

import fitnesse.slim.JavaSlimFactory;
import fitnesse.slim.SlimCommandRunningClient;
import fitnesse.slim.SlimService;
import fitnesse.testsystems.ClientBuilder;
import fitnesse.testsystems.CommandRunner;
import fitnesse.testsystems.Descriptor;
import fitnesse.testsystems.MockCommandRunner;

public class SlimClientBuilder extends ClientBuilder<SlimCommandRunningClient> {

  public static final String SLIM_PORT = "SLIM_PORT";
  public static final String SLIM_HOST = "SLIM_HOST";
  public static final String SLIM_FLAGS = "SLIM_FLAGS";
  public static final String MANUALLY_START_TEST_RUNNER_ON_DEBUG = "MANUALLY_START_TEST_RUNNER_ON_DEBUG";

  private static final AtomicInteger slimPortOffset = new AtomicInteger(0);

  private final int slimPort;

  public SlimClientBuilder(Descriptor descriptor) {
    super(descriptor);
    slimPort = getNextSlimPort();
  }

  public static String defaultTestRunner() {
    return "fitnesse.slim.SlimService";
  }

  @Override
  public SlimCommandRunningClient build() throws IOException {
    CommandRunner commandRunner;

    if (useManualStartForTestSystem()) {
      commandRunner = new MockCommandRunner();
    } else {
      commandRunner = new CommandRunner(buildCommand(), "", descriptor.createClasspathEnvironment(descriptor.getClassPath()));
    }

    return new SlimCommandRunningClient(descriptor.getTestRunner(), commandRunner, determineSlimHost(), getSlimPort());
  }

  protected String buildCommand() {
    String slimArguments = buildArguments();
    String slimCommandPrefix = super.buildCommand(descriptor.getCommandPattern(), descriptor.getTestRunner(), descriptor.getClassPath());
    return String.format("%s %s", slimCommandPrefix, slimArguments);
  }

  protected String buildArguments() {
    int slimSocket = getSlimPort();
    String slimFlags = getSlimFlags();
    return String.format("%s %d", slimFlags, slimSocket);
  }

  //For testing only.  Makes responder faster.
  void createSlimService(String args) throws SocketException {
    while (!tryCreateSlimService(args))
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
  }

  // For testing only
  private boolean tryCreateSlimService(String args) throws SocketException {
    try {
      SlimService.parseCommandLine(args.trim().split(" "));
      SlimService.startWithFactoryAsync(new JavaSlimFactory());
      return true;
    } catch (SocketException e) {
      throw e;
    } catch (Exception e) {
      e.printStackTrace();
      return false;
    }
  }

  public int getSlimPort() {
    return slimPort;
  }

  private int findFreePort() {
    int port;
    try {
      ServerSocket socket = new ServerSocket(0);
      port = socket.getLocalPort();
      socket.close();
    } catch (Exception e) {
      port = -1;
    }
    return port;
  }

  private int getNextSlimPort() {
    int base;

    if (System.getProperty("slim.port") != null) {
      base = Integer.parseInt(System.getProperty("slim.port"));
    } else {
      base = getSlimPortBase();
    }

    if (base == 0) {
      return findFreePort();
    }

    synchronized (slimPortOffset) {
      int offset = slimPortOffset.get();
      offset = (offset + 1) % 10;
      slimPortOffset.set(offset);
      return offset + base;
    }
  }

  public static void clearSlimPortOffset() {
    slimPortOffset.set(0);
  }

  private int getSlimPortBase() {
    int base = 8085;
    try {
      String slimPort = descriptor.getVariable(SLIM_PORT);
      if (slimPort != null) {
        int slimPortInt = Integer.parseInt(slimPort);
        base = slimPortInt;
      }
    } catch (NumberFormatException e) {
      // stick with default
    }
    return base;
  }

  String determineSlimHost() {
    String slimHost = descriptor.getVariable(SLIM_HOST);
    return slimHost == null ? "localhost" : slimHost;
  }

  String getSlimFlags() {
    String slimFlags = descriptor.getVariable(SLIM_FLAGS);
    return slimFlags == null ? "" : slimFlags;
  }

  private boolean useManualStartForTestSystem() {
    if (descriptor.isDebug()) {
      String useManualStart = descriptor.getVariable(MANUALLY_START_TEST_RUNNER_ON_DEBUG);
      return (useManualStart != null && useManualStart.toLowerCase().equals("true"));
    }
    return false;
  }

}

@@ -3061,149 +3061,8 @@
 int 
-base;%0A%0A    if (System.getProperty(%22slim.port%22) != null) %7B%0A      base = Integer.parseInt(System.getProperty(%22slim.port%22));%0A    %7D else %7B%0A      
 base
@@ -3082,22 +3082,16 @@
 tBase();
-%0A    %7D
 %0A%0A    if
@@ -3440,29 +3440,8 @@
 ) %7B%0A
-    int base = 8085;%0A
     
@@ -3459,21 +3459,17 @@
  String 
-slimP
+p
 ort = de
@@ -3489,25 +3489,27 @@
 ariable(
-SLIM_PORT
+%22slim.port%22
 );%0A     
@@ -3513,21 +3513,17 @@
     if (
-slimP
+p
 ort != n
@@ -3541,25 +3541,14 @@
     
-int slimPortInt =
+return
  Int
@@ -3561,21 +3561,17 @@
 arseInt(
-slimP
+p
 ort);%0A  
@@ -3578,28 +3578,122 @@
     
-  base = slimPortInt
+%7D%0A%0A      port = descriptor.getVariable(SLIM_PORT);%0A      if (port != null) %7B%0A        return Integer.parseInt(port)
 ;%0A  
@@ -3783,20 +3783,20 @@
  return 
-base
+8085
 ;%0A  %7D%0A%0A 

Move slim port base calculation to one place.