/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.tomee.arquillian.webapp;

import org.apache.openejb.arquillian.common.ArquillianFilterRunner;
import org.apache.openejb.arquillian.common.Files;
import org.apache.openejb.arquillian.common.IO;
import org.apache.openejb.arquillian.common.Setup;
import org.apache.openejb.arquillian.common.TomEEContainer;
import org.apache.openejb.arquillian.common.Zips;
import org.apache.openejb.config.RemoteServer;
import org.apache.tomee.installer.Installer;
import org.apache.tomee.installer.Paths;
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
import org.jboss.arquillian.protocol.servlet.ServletMethodExecutor;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TomEEWebappContainer extends TomEEContainer<TomEEWebappConfiguration> {

    private static final Logger logger = Logger.getLogger(TomEEWebappContainer.class.getName());

    private RemoteServer container;
    private boolean shutdown;
    private File openejbHome;
    private boolean wereOpenejbHomeSet = true;

    @Override
    public void start() throws LifecycleException {
        // see if TomEE is already running by checking the http port
        final int httpPort = configuration.getHttpPort();
        if (Setup.isRunning(configuration.getHost(), httpPort)) {

            logger.info(String.format("Tomcat found running on port %s", httpPort));

            return;
        }

        shutdown = true;

        final String s = File.separator;
        try {
            final File workingDirectory = new File(configuration.getDir());

            if (workingDirectory.exists()) {

                Files.assertDir(workingDirectory);

            } else {

                Files.mkdir(workingDirectory);
                Files.deleteOnExit(workingDirectory);
            }

            Files.readable(workingDirectory);
            Files.writable(workingDirectory);

            openejbHome = Setup.findHome(workingDirectory);
            Installer installer = null;

            if (openejbHome == null) {

                downloadTomcat(workingDirectory, configuration.getTomcatVersion(), configuration.getDir());

                openejbHome = Setup.findHome(workingDirectory);

                Files.deleteOnExit(openejbHome);

                final File webapp = new File(openejbHome, "webapps" + s + "tomee");

                Files.mkdir(webapp);
                downloadOpenEJBWebapp(webapp, configuration.getDir());

                System.setProperty("catalina.home", openejbHome.getAbsolutePath());
                System.setProperty("catalina.base", openejbHome.getAbsolutePath());

                System.setProperty("openejb.deploymentId.format", System.getProperty("openejb.deploymentId.format", "{appId}/{ejbJarId}/{ejbName}"));

                final Paths paths = new Paths(webapp);
                installer = new Installer(paths, true);
                if (!configuration.isUseInstallerServlet()) {
                    installer.installAll();

                }

                wereOpenejbHomeSet = false;
            }

            Files.assertDir(openejbHome);
            Files.readable(openejbHome);
            Files.writable(openejbHome);

            Setup.configureServerXml(openejbHome, configuration);
            Setup.configureSystemProperties(openejbHome, configuration);

            Setup.exportProperties(openejbHome, configuration, true);

            final URL logging = Thread.currentThread().getContextClassLoader().getResource("default.remote.logging.properties");
            if (logging != null) {
                write(logging, new File(openejbHome, "conf" + s + "logging.properties"));
            }

            if (configuration.isRemoveUnusedWebapps()) {
                Setup.removeUselessWebapps(openejbHome, "tomee");
            }

            if (logger.isLoggable(Level.FINE)) {
                final Map<Object, Object> map = new TreeMap<>(System.getProperties());
                for (final Map.Entry<Object, Object> entry : map.entrySet()) {
                    System.out.printf("%s = %s\n", entry.getKey(), entry.getValue());
                }
            }

            Setup.installArquillianBeanDiscoverer(openejbHome);

            if (!wereOpenejbHomeSet && configuration.isUseInstallerServlet()) {
                // instead of calling the Installer, let's just do like users do
                // call the servlet installer instead
                final String baseUrl = "http://" + configuration.getHost() + ":" + httpPort + "/tomee/installer";

                assert installer != null;
                installer.addTomEEAdminConfInTomcatUsers(true);

                final RemoteServer tmpContainer = new RemoteServer();
                tmpContainer.setPortStartup(httpPort);

                try {
                    tmpContainer.start();
                } catch (final Exception e) {
                    tmpContainer.destroy();
                    throw e;
                }

                final URL url = new URL(baseUrl);
                logger.info("Calling TomEE Installer Servlet on " + url);

                for (int i = 0; i < 3; i++) {
                    final URLConnection uc = url.openConnection();
                    // dG9tZWU6dG9tZWU= --> Base64 of tomee:tomee
                    final String authorizationString = "Basic dG9tZWU6dG9tZWU=";
                    uc.setRequestProperty("Authorization", authorizationString);
                    try {
                        final InputStream is = uc.getInputStream();
                        org.apache.openejb.loader.IO.slurp(is);
                        is.close();
                        break;
                    } catch (final Exception e) {
                        logger.warning(e.getMessage());
                        Thread.sleep(1000);
                    }
                }

                tmpContainer.stop();
                tmpContainer.getServer().waitFor();
            }

            container = new RemoteServer();
            container.setPortStartup(httpPort);
            container.start(Arrays.asList(
                    "-Dopenejb.system.apps=true",
                    "-Dtomee.remote.support=true",
                    "-Dorg.apache.openejb.servlet.filters=" + ArquillianFilterRunner.class.getName() + "=" + ServletMethodExecutor.ARQUILLIAN_SERVLET_MAPPING), "start", true);
            container.killOnExit();
        } catch (final Exception e) {
            if (null != container) {
                container.destroy();
            }
            throw new LifecycleException("Unable to start remote container on port: " + httpPort, e);
        }
    }

    private static void write(final URL resource, final File file) throws IOException {
        if (file.exists()) {
            Files.delete(file);
        }

        try (final InputStream is = org.apache.openejb.loader.IO.read(resource)) {
            IO.copy(is, file);
        }
    }

    protected void downloadOpenEJBWebapp(final File targetDirectory, final String defaultTempDir) throws LifecycleException {
        final String artifactName = configuration.getArtifactName();
        final File zipFile = Setup.downloadFile(artifactName, null, defaultTempDir);
        Zips.unzip(zipFile, targetDirectory);
    }

    protected void downloadTomcat(final File catalinaDirectory, final String tomcatVersion, final String defaultTempDir) throws LifecycleException {
        String source = null;

        try {
            int v = Integer.parseInt(tomcatVersion.substring(0, tomcatVersion.indexOf('.')));
            source = "http://archive.apache.org/dist/tomcat/tomcat-" + v + "/v" + v + "/bin/apache-tomcat-" + tomcatVersion + ".zip";
        } catch (final Exception e) {
            // no-op
        }

        if (source == null) {
            throw new LifecycleException("Unable to find URL for Tomcat " + tomcatVersion);
        }

        final File zipFile = Setup.downloadFile("org.apache.tomcat:tomcat:" + tomcatVersion + ":zip", source, defaultTempDir);
        Zips.unzip(zipFile, catalinaDirectory);
    }

    @Override
    public void stop() throws LifecycleException {
        // only stop the container if we started it
        if (shutdown) {
            try {
                Setup.removeArquillianBeanDiscoverer(openejbHome);
                container.destroy();
            } finally {
                resetSerialization();
            }
        }
    }

    @Override
    public Class<TomEEWebappConfiguration> getConfigurationClass() {
        return TomEEWebappConfiguration.class;
    }
}
