/*
 * Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
 *
 * 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.jboss.errai.cdi.server.gwt;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.BindException;

import javax.xml.stream.XMLStreamException;

import org.apache.commons.io.IOUtils;
import org.jboss.errai.cdi.server.as.JBossServletContainerAdaptor;
import org.jboss.errai.cdi.server.gwt.util.SimpleTranslator;
import org.jboss.errai.cdi.server.gwt.util.SimpleTranslator.AttributeEntry;
import org.jboss.errai.cdi.server.gwt.util.SimpleTranslator.Tag;
import org.jboss.errai.cdi.server.gwt.util.StackTreeLogger;
import org.jboss.errai.cdi.server.gwt.util.JBossUtil;

import com.google.gwt.core.ext.ServletContainer;
import com.google.gwt.core.ext.ServletContainerLauncher;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;

/**
 * For starting a {@link JBossServletContainerAdaptor} controlling a standalone
 * Jboss/Wildfly AS.
 *
 * @author Max Barkley <mbarkley@redhat.com>
 * @deprecated The JBossLauncher is deprecated. Please use the {@link EmbeddedWildFlyLauncher} instead.
 */
@Deprecated
public class JBossLauncher extends ServletContainerLauncher {

  // Property names
  private final String JBOSS_DEBUG_PORT_PROPERTY = "errai.jboss.debug.port";
  private final String TEMPLATE_CONFIG_FILE_PROPERTY = "errai.jboss.config.file";
  private final String CLASS_HIDING_JAVA_AGENT_PROPERTY = "errai.jboss.javaagent.path";
  private final String JBOSS_JAVA_OPTS_PROPERTY = "errai.jboss.javaopts";
  private final String TMP_CONFIG_FILE = "standalone-errai-dev.xml";
  
  private final String JBOSS_HTTP_REMOTING_ADDRESS = "errai.jboss.httpremotingaddress";
  private final String JBOSS_NATIVE_REMOTING_ADDRESS = "errai.jboss.nativeremotingaddress";

  private StackTreeLogger logger;

  @Override
  public ServletContainer start(TreeLogger treeLogger, int port, File appRootDir) throws BindException, Exception {
    logger = new StackTreeLogger(treeLogger);

    logger.branch(Type.INFO, "Server launcher starting..");

    // Get properties
    final String DEBUG_PORT = System.getProperty(JBOSS_DEBUG_PORT_PROPERTY, "8001");
    final String TEMPLATE_CONFIG_FILE = System.getProperty(TEMPLATE_CONFIG_FILE_PROPERTY, "standalone-full.xml");
    final String CLASS_HIDING_JAVA_AGENT = System.getProperty(CLASS_HIDING_JAVA_AGENT_PROPERTY);
    String JAVA_OPTS = System.getProperty(JBOSS_JAVA_OPTS_PROPERTY, "");
    
    final String HTTP_REMOTING_ADDRESS = System.getProperty(JBOSS_HTTP_REMOTING_ADDRESS, "http-remoting://localhost:9990");
    final String NATIVE_REMOTING_ADDRESS = System.getProperty(JBOSS_NATIVE_REMOTING_ADDRESS, "remote://localhost:9999");

    final String jbossHome = JBossUtil.getJBossHome(logger);
    validateClassHidingJavaAgent(CLASS_HIDING_JAVA_AGENT);

    try {
      createTempConfigFile(TEMPLATE_CONFIG_FILE, TMP_CONFIG_FILE, jbossHome, port);
      logger.log(Type.INFO,
              String.format("Created temporary config file %s, copied from %s.", TMP_CONFIG_FILE, TEMPLATE_CONFIG_FILE));
    } 
    catch (IOException e) {
      logger.log(
              Type.ERROR,
              String.format("Unable to create temporary config file %s from %s", TMP_CONFIG_FILE, TEMPLATE_CONFIG_FILE),
              e);
    }

    final String JBOSS_START = JBossUtil.getStartScriptName(jbossHome);

    Process process;
    try {
      logger.branch(Type.INFO, String.format("Preparing JBoss AS instance (%s)", JBOSS_START));
      final File startScript = new File(JBOSS_START);
      if (!startScript.canExecute() && !startScript.setExecutable(true)) {
        throw new IllegalStateException("Can not execute " + JBOSS_START);
      }
      ProcessBuilder builder = new ProcessBuilder(JBOSS_START, "-c", TMP_CONFIG_FILE);

      logger.log(Type.INFO, String.format("Adding JBOSS_HOME=%s to instance environment", jbossHome));
      // Necessary for JBoss AS instance to startup
      builder.environment().put("JBOSS_HOME", jbossHome);

      // Allows JVM to be debugged
      builder.environment().put(
              "JAVA_OPTS",
              String.format("%s -Xrunjdwp:transport=dt_socket,address=%s,server=y,suspend=n -javaagent:%s", JAVA_OPTS,
                      DEBUG_PORT, CLASS_HIDING_JAVA_AGENT).trim());

      process = builder.start();

      logger.log(Type.INFO, "Redirecting stdout and stderr to share with this process");
      inheritIO(process.getInputStream(), System.out);
      inheritIO(process.getErrorStream(), System.err);

      logger.log(Type.INFO, "Executing AS instance...");
    }
    catch (IOException e) {
      logger.unbranch();
      throw new IllegalStateException("Failed to start JBoss AS process", e);
    }

    logger.unbranch();

    logger.branch(Type.INFO, "Creating servlet container controller...");

    try {
      
      JBossServletContainerAdaptor controller = new JBossServletContainerAdaptor(port, appRootDir, JBossUtil.getDeploymentContext(), logger.peek(), process, HTTP_REMOTING_ADDRESS, NATIVE_REMOTING_ADDRESS);
      
      logger.log(Type.INFO, "Controller created");
      logger.unbranch();
      return controller;
    }
    catch (IllegalStateException e) {
      throw new IllegalStateException("Could not start servlet container controller", e);
    }
  }

  private void validateClassHidingJavaAgent(final String CLASS_HIDING_JAVA_AGENT) {
    if (CLASS_HIDING_JAVA_AGENT == null) {
      throw new IllegalStateException(String.format(
              "The local path to the artifact errai.org.jboss:class-local-class-hider:jar must be given as the property %s",
              CLASS_HIDING_JAVA_AGENT_PROPERTY));
    }
  }

  private void createTempConfigFile(String fromName, String toName, String jBossHome, int port) throws IOException {
    File configDir = new File(jBossHome, JBossUtil.STANDALONE_CONFIGURATION);
    File from = new File(configDir, fromName);
    File to = new File(configDir, toName);

    if (!from.exists()) {
      throw new IllegalStateException(String.format(
              "Config file %s does not exit. It must be created or another one must be specified with the %s JVM property.",
              from.getAbsolutePath(), TEMPLATE_CONFIG_FILE_PROPERTY));
    }

    if (to.exists()) {
      logger.log(Type.WARN,
              String.format("Temporary config file %s already exists and will be deleted", to.getAbsolutePath()));
      to.delete();
    }

    to.createNewFile();
    to.deleteOnExit();

    InputStream inStream = new FileInputStream(from);
    OutputStream outStream = new FileOutputStream(to);

    // Replace default http port with provided port
    SimpleTranslator trans = new SimpleTranslator();
    trans.addFilter(new Tag("socket-binding", new AttributeEntry("name", "http")));
    trans.addNewTag("socket-binding-group", new Tag("socket-binding", new AttributeEntry("name", "http"),
            new AttributeEntry("port", String.valueOf(port))));

    try {
      trans.translate(inStream, outStream);
    }
    catch (XMLStreamException e) {
      throw new IllegalStateException("Could not create copy of configuration from " + from.getAbsolutePath(), e);
    }
    finally {
      inStream.close();
      outStream.close();
    }
  }

  private void inheritIO(final InputStream in, final OutputStream to) {
    new Thread() {
      @Override
      public void run() {
        try {
          IOUtils.copy(in, to);
        }
        catch (IOException e) {
          throw new RuntimeException(e);
        }
      }
    }.start();
  }
}
