package org.exist.test;

import org.exist.EXistException;
import org.exist.storage.BrokerPool;
import org.exist.storage.journal.Journal;
import org.exist.util.Configuration;
import org.exist.util.ConfigurationHelper;
import org.exist.util.DatabaseConfigurationException;
import org.exist.util.FileUtils;
import org.junit.rules.ExternalResource;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;

import static org.exist.repo.AutoDeploymentTrigger.AUTODEPLOY_PROPERTY;

/**
 * Exist embedded Server Rule for JUnit
 */
public class ExistEmbeddedServer extends ExternalResource {

    private final Optional<String> instanceName;
    private final Optional<Path> configFile;
    private final Optional<Properties> configProperties;
    private final boolean useTemporaryStorage;
    private final boolean disableAutoDeploy;
    private Optional<Path> temporaryStorage = Optional.empty();

    private String prevAutoDeploy = "off";
    private BrokerPool pool = null;

    public ExistEmbeddedServer() {
        this(null, null, null, false, false);
    }

    public ExistEmbeddedServer(final boolean useTemporaryStorage) {
        this(null, null, null, false, useTemporaryStorage);
    }

    public ExistEmbeddedServer(final boolean disableAutoDeploy, final boolean useTemporaryStorage) {
        this(null, null, null, disableAutoDeploy, useTemporaryStorage);
    }

    public ExistEmbeddedServer(final Properties configProperties) {
        this(null, null, configProperties, false, false);
    }

    public ExistEmbeddedServer(final Properties configProperties, final boolean disableAutoDeploy, final boolean useTemporaryStorage) {
        this(null, null, configProperties, disableAutoDeploy, useTemporaryStorage);
    }

    public ExistEmbeddedServer(final String instanceName, final Path configFile) {
        this(instanceName, configFile, null, false, false);
    }

    public ExistEmbeddedServer(final String instanceName, final Path configFile, final Properties configProperties) {
        this(instanceName, configFile, configProperties, false, false);
    }

    public ExistEmbeddedServer(final String instanceName, final Path configFile, final Properties configProperties, final boolean disableAutoDeploy) {
        this(instanceName, configFile, configProperties, false, false);
    }

    public ExistEmbeddedServer(final String instanceName, final Path configFile, final Properties configProperties, final boolean disableAutoDeploy, final boolean useTemporaryStorage) {
        this.instanceName = Optional.ofNullable(instanceName);
        this.configFile = Optional.ofNullable(configFile);
        this.configProperties = Optional.ofNullable(configProperties);
        this.disableAutoDeploy = disableAutoDeploy;
        this.useTemporaryStorage = useTemporaryStorage;
    }

    @Override
    protected void before() throws Throwable {
        startDb();
        super.before();
    }

    public void startDb() throws DatabaseConfigurationException, EXistException, IOException {
        if(pool == null) {

            if(disableAutoDeploy) {
                this.prevAutoDeploy = System.getProperty(AUTODEPLOY_PROPERTY, "off");
                System.setProperty(AUTODEPLOY_PROPERTY, "off");
            }

            final String name = instanceName.orElse(BrokerPool.DEFAULT_INSTANCE_NAME);

            final Optional<Path> home = Optional.ofNullable(System.getProperty("exist.home", System.getProperty("user.dir"))).map(Paths::get);
            final Path confFile = configFile.orElseGet(() -> ConfigurationHelper.lookup("conf.xml", home));

            final Configuration config;
            if(confFile.isAbsolute() && Files.exists(confFile)) {
                //TODO(AR) is this correct?
                config = new Configuration(confFile.toAbsolutePath().toString());
            } else {
                config = new Configuration(FileUtils.fileName(confFile), home);
            }

            // override any specified config properties
            configProperties.ifPresent(properties -> {
                for (final Map.Entry<Object, Object> configProperty : properties.entrySet()) {
                    config.setProperty(configProperty.getKey().toString(), configProperty.getValue());
                }
            });

            if(useTemporaryStorage) {
                this.temporaryStorage = Optional.of(Files.createTempDirectory("org.exist.test.ExistEmbeddedServer"));
                config.setProperty(BrokerPool.PROPERTY_DATA_DIR, temporaryStorage.get());
                config.setProperty(Journal.PROPERTY_RECOVERY_JOURNAL_DIR, temporaryStorage.get());
                System.out.println("Using temporary storage location: " + temporaryStorage.get().toAbsolutePath().toString());
            }

            BrokerPool.configure(name,1, 5, config, Optional.empty());
            this.pool = BrokerPool.getInstance(name);
        } else {
            throw new IllegalStateException("ExistEmbeddedServer already running");
        }
    }

    public BrokerPool getBrokerPool() {
        return pool;
    }

    public void restart() throws EXistException, DatabaseConfigurationException, IOException {
        if(pool != null) {
            stopDb();
            startDb();
        } else {
            throw new IllegalStateException("ExistEmbeddedServer already stopped");
        }
    }

    @Override
    protected void after() {
        stopDb();

        super.after();
    }

    public void stopDb() {
        if(pool != null) {
            pool.shutdown();

            // clear instance variables
            pool = null;

            if(useTemporaryStorage && temporaryStorage.isPresent()) {
                FileUtils.deleteQuietly(temporaryStorage.get());
                temporaryStorage = Optional.empty();
            }

            if(disableAutoDeploy) {
                //set the autodeploy trigger enablement back to how it was before this test class
                System.setProperty(AUTODEPLOY_PROPERTY, this.prevAutoDeploy);
            }

        } else {
            throw new IllegalStateException("ExistEmbeddedServer already stopped");
        }
    }
}

@@ -4406,32 +4406,33 @@
 %0A%0A            if
+ 
 (useTemporarySto
@@ -4431,32 +4431,89 @@
 oraryStorage) %7B%0A
+                if (!temporaryStorage.isPresent()) %7B%0A    
                 
@@ -4610,24 +4610,42 @@
 dServer%22));%0A
+                %7D%0A
             
@@ -5361,32 +5361,193 @@
 , IOException %7B%0A
+        restart(false);%0A    %7D%0A%0A    public void restart(final boolean clearTemporaryStorage) throws EXistException, DatabaseConfigurationException, IOException %7B%0A
         if(pool 
@@ -5576,16 +5576,37 @@
  stopDb(
+clearTemporaryStorage
 );%0A     
@@ -5856,24 +5856,116 @@
  stopDb() %7B%0A
+        stopDb(true);%0A    %7D%0A%0A    private void stopDb(final boolean clearTemporaryStorage) %7B%0A
         if(p
@@ -6133,32 +6133,57 @@
 rage.isPresent()
+ && clearTemporaryStorage
 ) %7B%0A            

Allow temporary storage to be preserved when calling ExistEmbeddedServer#restart()