package org.tmatesoft.svn.test;

import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.admin.SVNAdminClient;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Sandbox {

    public static Sandbox createWithoutCleanup(String testName, TestOptions testOptions) throws SVNException {
        return create(testName, testOptions, false);
    }

    public static Sandbox createWithCleanup(String testName, TestOptions testOptions) throws SVNException {
        return create(testName, testOptions, true);
    }

    private final String testName;

    private final TestOptions testOptions;
    private final List<WorkingCopy> workingCopies;
    private File testDirectory;
    private final List<ApacheProcess> apacheProcesses;
    private final List<SvnserveProcess> svnserveProcesses;
    private final Map<SVNURL, File> urlToRepositoryRoot;

    private Sandbox(String testName, TestOptions testOptions) {
        this.testName = testName;
        this.testOptions = testOptions;
        this.workingCopies = new ArrayList<WorkingCopy>();
        this.apacheProcesses = new ArrayList<ApacheProcess>();
        this.svnserveProcesses = new ArrayList<SvnserveProcess>();
        this.urlToRepositoryRoot = new HashMap<SVNURL, File>();
    }

    public void dispose() {
        for (WorkingCopy workingCopy : workingCopies) {
            workingCopy.dispose();
        }
        for (ApacheProcess apacheProcess : apacheProcesses) {
            ApacheProcess.shutdown(apacheProcess);
        }
        for (SvnserveProcess svnserveProcess : svnserveProcesses) {
            SvnserveProcess.shutdown(svnserveProcess);
        }
    }

    public WorkingCopy checkoutWorkingCopy() throws SVNException {
        return checkoutWorkingCopyOrUpdateTo(SVNRepository.INVALID_REVISION);
    }

    public WorkingCopy checkoutWorkingCopyOrUpdateTo(long revision) throws SVNException {
        if (getWorkingCopyDirectory().exists()) {
            final WorkingCopy workingCopy = openExistingWorkingCopy();
            workingCopy.updateToRevision(revision);
            return workingCopy;
        } else {
            return checkoutOrUpdateExistingWorkingCopy(getRepositoryUrl(), revision);
        }
    }

    private WorkingCopy openExistingWorkingCopy() {
        final WorkingCopy workingCopy = new WorkingCopy(getTestOptions(), getWorkingCopyDirectory());
        workingCopy.setRepositoryUrl(getRepositoryUrl());
        workingCopies.add(workingCopy);
        return workingCopy;
    }

    public WorkingCopy checkoutOrUpdateExistingWorkingCopy(SVNURL repositoryUrl, long revision) throws SVNException {
        return checkoutOrUpdateExistingWorkingCopy(repositoryUrl, revision, SvnWcGeneration.V17);
    }

    public WorkingCopy checkoutOrUpdateExistingWorkingCopy(SVNURL repositoryUrl, long revision, SvnWcGeneration wcGeneration) throws SVNException {
        final WorkingCopy workingCopy = new WorkingCopy(getTestOptions(), getWorkingCopyDirectory());
        workingCopy.setWcGeneration(wcGeneration);
        workingCopy.checkoutRevision(repositoryUrl, revision);
        workingCopies.add(workingCopy);
        return workingCopy;
    }

    public WorkingCopy checkoutNewWorkingCopy(SVNURL url) throws SVNException {
        return checkoutNewWorkingCopy(url, SVNRevision.HEAD.getNumber());
    }

    public WorkingCopy checkoutNewWorkingCopy(SVNURL repositoryUrl, long revision) throws SVNException {
        return checkoutNewWorkingCopy(repositoryUrl, revision, true, TestUtil.getDefaultWcGeneration());
    }

    public WorkingCopy checkoutNewWorkingCopy(SVNURL repositoryUrl, long revision, boolean ignoreExternals, SvnWcGeneration wcGeneration) throws SVNException {
        return checkoutNewWorkingCopy(repositoryUrl, revision, ignoreExternals, wcGeneration, createWorkingCopyDirectory());
    }

    public WorkingCopy checkoutNewWorkingCopy(SVNURL repositoryUrl, long revision, boolean ignoreExternals, SvnWcGeneration wcGeneration, File wcDirectory) throws SVNException {
        final WorkingCopy workingCopy = new WorkingCopy(getTestOptions(), wcDirectory);
        workingCopy.setWcGeneration(wcGeneration);
        workingCopy.checkoutRevision(repositoryUrl, revision, ignoreExternals);
        workingCopies.add(workingCopy);
        return workingCopy;
    }

    public SVNURL getFSFSAccessUrl(SVNURL url) {
        if ("file".equals(url.getProtocol())) {
            return url;
        }
        final File repositoryRoot = urlToRepositoryRoot.get(url);
        try {
            return SVNURL.fromFile(repositoryRoot);
        } catch (SVNException e) {
            return null;
        }
    }

    private SVNURL getRepositoryUrl() {
        final SVNURL repositoryUrl = getTestOptions().getRepositoryUrl();

        if (repositoryUrl == null) {
            throw new RuntimeException("Unable to start the test: repository URL is not specified.");
        }

        return repositoryUrl;
    }

    private String getTestName() {
        return testName;
    }

    private File getTestDirectory() {
        if (testDirectory == null) {
            testDirectory = createTestDirectory();
        }
        return testDirectory;
    }

    public SVNURL createSvnRepository() throws SVNException {
        final File repositoryDirectory = createDirectory("svn.repo");
        createSvnRepository(repositoryDirectory);
        SVNURL url = SVNURL.fromFile(repositoryDirectory);
        urlToRepositoryRoot.put(url, repositoryDirectory);
        return url;
    }

    public SVNURL createSvnRepositoryWithDavAccess() throws SVNException {
        return createSvnRepositoryWithDavAccess(null);
    }

    public SVNURL createSvnRepositoryWithSvnAccess() throws SVNException {
        return createSvnRepositoryWithSvnAccess(null);
    }

    public SVNURL createSvnRepositoryWithDavAccess(Map<String, String> loginToPassword) throws SVNException {
        final File repositoryDirectory = createDirectory("svn.repo");
        createSvnRepository(repositoryDirectory);
        final ApacheProcess apacheProcess = runApacheForSvnRepository(repositoryDirectory, loginToPassword);
        SVNURL url = apacheProcess.getUrl();
        urlToRepositoryRoot.put(url, repositoryDirectory);
        return url;
    }

    public SVNURL createSvnRepositoryWithSvnAccess(Map<String, String> loginToPassword) throws SVNException {
        final File repositoryDirectory = createDirectory("svn.repo");
        createSvnRepository(repositoryDirectory);
        final SvnserveProcess svnserveProcess = runSvnserveForSvnRepository(repositoryDirectory, loginToPassword);
        SVNURL url = svnserveProcess.getUrl();
        urlToRepositoryRoot.put(url, repositoryDirectory);
        return url;
    }

    public File createFailingHook(SVNURL url, String hookName) throws SVNException {
        final String failingHookContents = TestUtil.getFailingHookContents();
        return createHook(url, hookName, failingHookContents);
    }

    public File createHook(SVNURL url, String hookName, String failingHookContents) throws SVNException {
        final File repositoryRoot = urlToRepositoryRoot.get(url);
        final File hookFile = TestUtil.getHookFile(repositoryRoot, hookName);
        TestUtil.writeFileContentsString(hookFile, failingHookContents);
        SVNFileUtil.setExecutable(hookFile, true);
        return hookFile;
    }

    public File writeActiveAuthzContents(SVNURL url, String contents) throws SVNException {
        //maybe url is served by apache?
        final ApacheProcess apacheProcess = findApacheProcess(url);
        if (apacheProcess != null) {

            final File activeAuthzFile = apacheProcess.getAuthzFile();
            if (activeAuthzFile == null) {
                return null;
            }
            TestUtil.writeFileContentsString(activeAuthzFile, contents);
            apacheProcess.reload(); //reload apache configuration
            return activeAuthzFile;
        }

        //maybe url is served by svnserve?
        final SvnserveProcess svnserveProcess = findSvnserveProcess(url);
        if (svnserveProcess != null) {

            final File activeAuthzFile = svnserveProcess.getAuthzFile();
            if (activeAuthzFile == null) {
                return null;
            }

            TestUtil.writeFileContentsString(activeAuthzFile, contents);
            svnserveProcess.reload();
            return activeAuthzFile;
        }

        //authz for FSFS is useless
        return null;
    }

    public File writeHookContents(SVNURL url, String hookName, String hookContents) throws SVNException {
        final File repositoryRoot = urlToRepositoryRoot.get(url);

        final File hookFile = TestUtil.getHookFile(repositoryRoot, hookName);
        TestUtil.writeFileContentsString(hookFile, hookContents);

        SVNFileUtil.setExecutable(hookFile, true);

        return hookFile;
    }

    private ApacheProcess findApacheProcess(SVNURL url) {
        for (ApacheProcess apacheProcess : apacheProcesses) {
            if (apacheProcess.getUrl().equals(url)) {
                return apacheProcess;
            }
        }
        return null;
    }

    private SvnserveProcess findSvnserveProcess(SVNURL url) {
        for (SvnserveProcess svnserveProcess : svnserveProcesses) {
            if (svnserveProcess.getUrl().equals(url)) {
                return svnserveProcess;
            }
        }
        return null;
    }

    private File getWorkingCopyDirectory() {
        return new File(getTestDirectory(), "wc");
    }

    private File createWorkingCopyDirectory() {
        return createDirectory("wc");
    }

    public File createDirectory(String suggestedName) {
        return TestUtil.createDirectory(getTestDirectory(), suggestedName).getAbsoluteFile();
    }

    private File createTestDirectory() {
        final File testDirectory = new File(getTempDirectory(), getTestName());
        testDirectory.mkdirs();
        return testDirectory;
    }

    private TestOptions getTestOptions() {
        return testOptions;
    }

    private File getTempDirectory() {
        return getTestOptions().getTempDirectory();
    }

    private void cleanup() throws SVNException {
        SVNFileUtil.deleteAll(getTestDirectory(), null);
    }

    private static Sandbox create(String testName, TestOptions testOptions, boolean cleanup) throws SVNException {
        final Sandbox sandbox = new Sandbox(testName, testOptions);
        if (cleanup) {
            sandbox.cleanup();
        }
        return sandbox;
    }

    private ApacheProcess runApacheForSvnRepository(File repositoryDirectory, Map<String, String> loginToPassword) throws SVNException {
        final ApacheProcess apacheProcess = ApacheProcess.run(getTestOptions(), repositoryDirectory, loginToPassword);
        apacheProcesses.add(apacheProcess);
        return apacheProcess;
    }

    private SvnserveProcess runSvnserveForSvnRepository(File repositoryDirectory, Map<String, String> loginToPassword) throws SVNException {
        final SvnserveProcess svnserveProcess = SvnserveProcess.run(getTestOptions(), repositoryDirectory, loginToPassword);
        svnserveProcesses.add(svnserveProcess);
        return svnserveProcess;
    }

    private void createSvnRepository(File repositoryDirectory) throws SVNException {
        final SVNClientManager clientManager = SVNClientManager.newInstance();
        try {
            SVNAdminClient adminClient = clientManager.getAdminClient();
            adminClient.doCreateRepository(repositoryDirectory, null, true, false);

        } finally {
            clientManager.dispose();
        }
    }
}
