package net.jsunit;

import junit.extensions.ActiveTestSuite;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import net.jsunit.configuration.Configuration;
import net.jsunit.configuration.ConfigurationSource;
import net.jsunit.model.Browser;
import net.jsunit.model.DistributedTestRunResult;
import net.jsunit.utility.XmlUtility;
import org.mortbay.util.MultiException;

import java.net.BindException;
import java.util.List;

public class DistributedTest extends TestCase {

    protected DistributedTestRunManager manager;
    private static JsUnitStandardServer temporaryStandardServer;
    private static Object blocker = new Object();
    private static int serverCount = 0;

    public DistributedTest(ConfigurationSource serverSource, ConfigurationSource farmSource) {
        super(farmSource.remoteMachineURLs());
        ensureTemporaryStandardServerIsCreated(serverSource);
        manager = DistributedTestRunManager.forConfiguration(new Configuration(farmSource));
    }

    private void ensureTemporaryStandardServerIsCreated(ConfigurationSource serverSource) {
        //noinspection SynchronizeOnNonFinalField
        synchronized (blocker) {
            if (temporaryStandardServer == null) {
                temporaryStandardServer = new JsUnitStandardServer(new Configuration(serverSource), true);
            }
        }
    }

    public void setUp() throws Exception {
        super.setUp();
        startServerIfNecessary();
    }

    private void startServerIfNecessary() throws Exception {
        serverCount ++;
        //noinspection SynchronizeOnNonFinalField
        synchronized (blocker) {
            if (!temporaryStandardServer.isAlive()) {
                try {
                    temporaryStandardServer.start();
                } catch (MultiException e) {
                    if (!isMultiExceptionASingleBindException(e))
                        throw e;
                    //if a temporaryStandardServer is already running, fine -
                    //we only need it to serve content to remove machines
                }
            }
        }
    }

    private boolean isMultiExceptionASingleBindException(MultiException e) {
        List exceptions = e.getExceptions();
        return exceptions.size() == 1 && exceptions.get(0) instanceof BindException;
    }

    public void tearDown() throws Exception {
        serverCount --;
        if (serverCount == 0) {
            if (temporaryStandardServer != null && temporaryStandardServer.isAlive()) {
                temporaryStandardServer.dispose();
                temporaryStandardServer = null;
            }
        }
        super.tearDown();
    }

    public static Test suite(ConfigurationSource source) {
        TestSuite suite = new ActiveTestSuite();
        new DistributedTestSuiteBuilder(source).addTestsTo(suite);
        return suite;
    }

    public static Test suite() {
        return suite(Configuration.resolveSource());
    }

    protected void runTest() throws Throwable {
        manager.runTests();
        DistributedTestRunResult result = manager.getDistributedTestRunResult();
        temporaryStandardServer.logStatus(XmlUtility.asPrettyString(result.asXml()));
        if (!result.wasSuccessful())
            fail(result.displayString());
    }

    public DistributedTestRunManager getDistributedTestRunManager() {
        return manager;
    }

    public JsUnitStandardServer getTemporaryStandardServer() {
        return temporaryStandardServer;
    }

    public void limitToBrowser(Browser remoteBrowser) {
        manager.limitToBrowser(remoteBrowser);
        setName(remoteBrowser.getFileName());
    }
}
