/*
 * Copyright 2021 The Apache Software Foundation.
 *
 * Licensed 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.tomcat.vestige.launcher;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.catalina.Globals;
import org.apache.catalina.TomcatControllerHandler;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.startup.Catalina;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.tomcat.util.log.SystemLogHandler;
import org.apache.tomcat.util.scan.TomcatUtilScanControllerHandler;

import fr.gaellalire.vestige.spi.resolver.maven.VestigeMavenResolver;

/**
 * @author Gael Lalire
 */
public class TomcatVestigeLauncher implements Runnable {

    private static final Logger LOGGER = Logger.getLogger(TomcatVestigeLauncher.class.getName());

    private VestigeMavenResolver vestigeMavenResolver;

    private File conf;

    private File data;

    public File getConf() {
        return conf;
    }

    public File getData() {
        return data;
    }

    public VestigeMavenResolver getVestigeMavenResolver() {
        return vestigeMavenResolver;
    }

    public void setVestigeMavenResolver(final VestigeMavenResolver vestigeMavenResolver) {
        this.vestigeMavenResolver = vestigeMavenResolver;
    }

    public void setVestigeSystem(final TomcatVestigeSystem vestigeSystem) {
        TomcatURLStreamHandlerFactory.disable();
        TomcatURLStreamHandlerFactory tomcatURLStreamHandlerFactory = TomcatURLStreamHandlerFactory.getInstance();
        URLStreamHandlerFactory urlStreamHandlerFactory = vestigeSystem.getURLStreamHandlerFactory();
        vestigeSystem.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {

            @Override
            public URLStreamHandler createURLStreamHandler(String protocol) {
                URLStreamHandler urlStreamHandler = tomcatURLStreamHandlerFactory.createURLStreamHandler(protocol);
                if (urlStreamHandler != null) {
                    return urlStreamHandler;
                }
                if (urlStreamHandlerFactory != null) {
                    urlStreamHandler = urlStreamHandlerFactory.createURLStreamHandler(protocol);
                }
                return urlStreamHandler;
            }
        });
        vestigeSystem.setOut(new SystemLogHandler(vestigeSystem.getOut()));
        vestigeSystem.setErr(new SystemLogHandler(vestigeSystem.getErr()));
        if (System.getSecurityManager() != null) {
            // policy activated
            Policy policy = new Policy() {

                private Map<CodeSource, Permissions> permissionsByCodeSource = new HashMap<CodeSource, Permissions>();

                @Override
                public PermissionCollection getPermissions(final CodeSource codesource) {
                    Permissions permissions = permissionsByCodeSource.get(codesource);
                    if (permissions == null || permissions.isReadOnly()) {
                        permissions = new Permissions();
                        permissionsByCodeSource.put(codesource, permissions);
                    }
                    return permissions;
                }

                @Override
                public boolean implies(final ProtectionDomain domain, final Permission permission) {
                    // all permissions
                    return true;
                }

            };
            vestigeSystem.setPolicy(policy);
        }
    }

    public TomcatVestigeLauncher(final File conf, final File data) {
        this.conf = conf;
        this.data = data;
        // avoid call to URL.setURLStreamHandlerFactory
        try {
            Field declaredField = WebappLoader.class.getDeclaredField("first");
            declaredField.setAccessible(true);
            declaredField.set(null, false);
            declaredField.setAccessible(false);
        } catch (Exception e) {
        }
        System.setProperty(Globals.CATALINA_BASE_PROP, conf.getPath());
        System.setProperty(Globals.CATALINA_HOME_PROP, conf.getPath());
        System.setProperty("com.sun.xml.bind.v2.bytecode.ClassTailor.noOptimize", "true");
    }

    private volatile boolean started = false;

    public void run() {
        VestigeTomcatController vestigeTomcatController = new VestigeTomcatController();
        try {
            TomcatControllerHandler.setTomcatController(vestigeTomcatController);
            TomcatUtilScanControllerHandler.setTomcatController(vestigeTomcatController);
            VestigeWar.init(this);

            final Catalina catalina = new Catalina() {
                @Override
                protected void initStreams() {
                    // system.out & system.err are already hooked
                }
            };
            catalina.setUseShutdownHook(false);

            // clBinding for initial context
            Thread.currentThread().setContextClassLoader(Catalina.class.getClassLoader());

            Thread launcherThread = new Thread() {
                @Override
                public void run() {
                    catalina.start();
                    started = true;
                }
            };
            launcherThread.start();
            try {
                synchronized (this) {
                    wait();
                }
            } catch (InterruptedException e) {
                while (true) {
                    try {
                        launcherThread.join();
                        break;
                    } catch (InterruptedException e1) {
                        LOGGER.log(Level.FINE, "Ignore interrupt", e1);
                    }
                }
                if (started) {
                    catalina.stop();
                }

                Thread currentThread = Thread.currentThread();
                ThreadGroup threadGroup = currentThread.getThreadGroup();
                int activeCount = threadGroup.activeCount();
                while (activeCount != 1) {
                    Thread[] list = new Thread[activeCount];
                    int enumerate = threadGroup.enumerate(list);
                    for (int i = 0; i < enumerate; i++) {
                        Thread t = list[i];
                        if (t == currentThread) {
                            continue;
                        }
                        t.interrupt();
                    }
                    for (int i = 0; i < enumerate; i++) {
                        Thread t = list[i];
                        if (t == currentThread) {
                            continue;
                        }
                        try {
                            t.join();
                        } catch (InterruptedException e1) {
                            LOGGER.log(Level.FINE, "Interrupted", e1);
                            break;
                        }
                    }
                    activeCount = threadGroup.activeCount();
                }
            } finally {
                started = false;
            }
        } finally {
            try {
                vestigeTomcatController.close();
            } catch (IOException e) {
                // ignore
            }
        }
    }

}
