package net.jsunit;

import net.jsunit.configuration.Configuration;
import net.jsunit.model.*;
import org.jdom.Document;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

public class DistributedTestRunManager {

    private Logger logger = Logger.getLogger("net.jsunit");
    private RemoteServerHitter hitter;
    private Configuration configuration;
    private String overrideURL;
    private DistributedTestRunResult distributedTestRunResult = new DistributedTestRunResult();
    private Browser remoteBrowser;

    public static DistributedTestRunManager forConfiguration(Configuration configuration) {
        return new DistributedTestRunManager(new RemoteMachineServerHitter(), configuration, null);
    }

    public static DistributedTestRunManager forConfigurationAndURL(RemoteServerHitter hitter, Configuration configuration, String overrideURL) {
        return new DistributedTestRunManager(hitter, configuration, overrideURL);
    }

    protected DistributedTestRunManager(RemoteServerHitter hitter, Configuration configuration, String overrideURL) {
        this.hitter = hitter;
        this.configuration = configuration;
        this.overrideURL = overrideURL;
    }

    public void runTests() {
        List<Thread> threads = new ArrayList<Thread>();
        for (final URL baseURL : configuration.getRemoteMachineURLs())
            threads.add(new Thread("Run JsUnit tests on " + baseURL) {
                public void run() {
                    runTestsOnRemoteMachine(baseURL);
                }
            });
        for (Thread thread : threads)
            thread.start();
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                throw new RuntimeException("One of the test threads was interrupted.");
            }
        }
    }

    private void runTestsOnRemoteMachine(URL baseURL) {
        List<TestRunResult> results = new ArrayList<TestRunResult>();
        try {
            URL fullURL = buildURL(baseURL);
            logger.info("Requesting run on remove machine URL " + baseURL);
            Document documentFromRemoteMachine = hitter.hitURL(fullURL);
            logger.info("Received response from remove machine URL " + baseURL);
            if (isMultipleTestRunResultsResult(documentFromRemoteMachine)) {
                DistributedTestRunResult multiple = new DistributedTestRunResultBuilder(configuration).build(documentFromRemoteMachine);
                results.addAll(multiple.getTestRunResults());
            } else {
                TestRunResult single = new TestRunResultBuilder(configuration).build(documentFromRemoteMachine);
                results.add(single);
            }
        } catch (IOException e) {
            if (configuration.shouldIgnoreUnresponsiveRemoteMachines())
                logger.info("Ignoring unresponsive machine " + baseURL.toString());
            else {
                logger.info("Remote machine URL is unresponsive: " + baseURL.toString());
                TestRunResult unresponsiveResult = new TestRunResult(baseURL);
                unresponsiveResult.setUnresponsive();
                results.add(unresponsiveResult);
            }
        }
        for (TestRunResult result : results) {
            result.setURL(baseURL);
            //noinspection SynchronizeOnNonFinalField
            synchronized (distributedTestRunResult) {
                distributedTestRunResult.addTestRunResult(result);
            }
        }
    }

    private boolean isMultipleTestRunResultsResult(Document document) {
        return document.getRootElement().getName().equals(DistributedTestRunResult.NAME);
    }

    private URL buildURL(URL url) throws UnsupportedEncodingException, MalformedURLException {
        String fullURLString = url.toString();
        fullURLString += "/runner";
        boolean hasFirstParameter = false;
        if (overrideURL != null) {
            fullURLString += "?url=" + URLEncoder.encode(overrideURL, "UTF-8");
            hasFirstParameter = true;
        } else if (configuration.getTestURL() != null) {
            fullURLString += "?url=" + URLEncoder.encode(configuration.getTestURL().toString(), "UTF-8");
            hasFirstParameter = true;
        }
        if (remoteBrowser != null) {
            fullURLString += (hasFirstParameter ? "&" : "?");
            fullURLString += "browserId=" + remoteBrowser.getId();
        }
        return new URL(fullURLString);
    }

    public DistributedTestRunResult getDistributedTestRunResult() {
        return distributedTestRunResult;
    }

    public String getOverrideURL() {
        return overrideURL;
    }

    public void setOverrideURL(String overrideURL) {
        this.overrideURL = overrideURL;
    }

    public void limitToBrowser(Browser remoteBrowser) {
        this.remoteBrowser = remoteBrowser;
    }
}
