/*
 * 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.activemq.artemis.cli.commands;

import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.Pair;
import org.apache.activemq.artemis.cli.Artemis;
import org.apache.activemq.artemis.cli.commands.tools.LockAbstract;
import org.apache.activemq.artemis.cli.factory.BrokerFactory;
import org.apache.activemq.artemis.cli.factory.jmx.ManagementFactory;
import org.apache.activemq.artemis.cli.factory.security.SecurityManagerFactory;
import org.apache.activemq.artemis.components.ExternalComponent;
import org.apache.activemq.artemis.core.server.ActivateCallback;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.core.server.management.ManagementContext;
import org.apache.activemq.artemis.dto.BrokerDTO;
import org.apache.activemq.artemis.dto.ComponentDTO;
import org.apache.activemq.artemis.dto.ManagementContextDTO;
import org.apache.activemq.artemis.integration.Broker;
import org.apache.activemq.artemis.integration.bootstrap.ActiveMQBootstrapLogger;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
import org.apache.activemq.artemis.utils.ClassloadingUtil;
import org.apache.activemq.artemis.utils.ReusableLatch;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;

@Command(name = "run", description = "Run the broker.")
public class Run extends LockAbstract {

   @Option(names = "--allow-kill", description = "This will allow the server to kill itself. Useful for tests (e.g. failover tests).")
   boolean allowKill;

   @Option(names = "--properties", description = "URL to a properties file that is applied to the server's configuration.")
   String properties;

   private static boolean embedded = false;

   public static final ReusableLatch latchRunning = new ReusableLatch(0);

   private ManagementContext managementContext;

   private Timer shutdownTimer;

   /**
    * This will disable the System.exit at the end of the server.stop, as that means there are other things
    * happening on the same VM.
    *
    * @param embedded
    */
   public static void setEmbedded(boolean embedded) {
      Run.embedded = true;
   }

   public static boolean getEmbedded() {
      return Run.embedded;
   }

   private Broker server;

   @Override
   public Object execute(ActionContext context) throws Exception {
      super.execute(context);

      verifyOlderLogging(new File(getBrokerEtc()));

      AtomicReference<Throwable> serverActivationFailed = new AtomicReference<>();
      try {
         BrokerDTO broker = getBrokerDTO();
         ActiveMQSecurityManager securityManager = SecurityManagerFactory.create(broker.security);
         ManagementContextDTO managementDTO = getManagementDTO();
         managementContext = ManagementFactory.create(managementDTO, securityManager);

         Artemis.printBanner(getActionContext().out);

         addShutdownHook(broker.server.getConfigurationFile().getParentFile());

         ActivateCallback activateCallback = new ActivateCallback() {
            @Override
            public void preActivate() {
               try {
                  managementContext.start();
               } catch (Exception e) {
                  ActiveMQServerLogger.LOGGER.unableStartManagementContext(e);
                  return;
               }
               try {
                  server.getServer().getManagementService().registerHawtioSecurity(managementContext.getArtemisMBeanServerGuard());
               } catch (Throwable e) {
                  ActiveMQServerLogger.LOGGER.unableToDeployHawtioMBean(e);
               }
            }

            @Override
            public void deActivate() {
               try {
                  server.getServer().getManagementService().unregisterHawtioSecurity();
               } catch (Exception e) {
                  //ok to ignore
               }
            }
         };

         server = BrokerFactory.createServer(broker.server, securityManager, activateCallback);

         server.createComponents();
         server.getServer().registerActivationFailureListener(exception -> serverActivationFailed.set(exception));

         if (properties == null) {
            File propertiesFileFromEtc = new File(getBrokerEtc(), ActiveMQDefaultConfiguration.BROKER_PROPERTIES_SYSTEM_PROPERTY_NAME);
            if (propertiesFileFromEtc.exists()) {
               properties = propertiesFileFromEtc.getAbsolutePath();
            }
         }
         server.getServer().setProperties(properties);
         server.start();
         server.getServer().addExternalComponent(managementContext, false);

         if (broker.web != null) {
            broker.components.add(broker.web);
         }

         String systemWebPropertyPrefix = ActiveMQDefaultConfiguration.getDefaultSystemWebPropertyPrefix();
         server.getServer().getConfiguration().parsePrefixedProperties(broker.web,
            "system-" + systemWebPropertyPrefix, System.getProperties(), systemWebPropertyPrefix);

         for (ComponentDTO componentDTO : broker.components) {
            ExternalComponent component = (ExternalComponent) ClassloadingUtil.getInstanceWithTypeCheck(componentDTO.componentClassName, ExternalComponent.class, this.getClass().getClassLoader());
            component.configure(componentDTO, getBrokerInstance(), getBrokerHome());
            server.getServer().addExternalComponent(component, true);
            assert component.isStarted();
         }
      } catch (Throwable t) {
         t.printStackTrace();
         serverActivationFailed.set(t);
         latchRunning.countDown();
      }

      if (serverActivationFailed.get() != null) {
         stop();
         return serverActivationFailed.get();
      }

      return new Pair<>(managementContext, server.getServer());
   }

   /**
    * Add a simple shutdown hook to stop the server.
    *
    * @param configurationDir
    */
   private void addShutdownHook(File configurationDir) {

      latchRunning.countUp();
      final File file = new File(configurationDir, "STOP_ME");
      if (file.exists()) {
         if (!file.delete()) {
            ActiveMQBootstrapLogger.LOGGER.errorDeletingFile(file.getAbsolutePath());
         }
      }
      final File fileKill = new File(configurationDir, "KILL_ME");
      if (fileKill.exists()) {
         if (!fileKill.delete()) {
            ActiveMQBootstrapLogger.LOGGER.errorDeletingFile(fileKill.getAbsolutePath());
         }
      }

      shutdownTimer = new Timer("ActiveMQ Artemis Server Shutdown Timer", true);
      shutdownTimer.scheduleAtFixedRate(new TimerTask() {
         @Override
         public void run() {
            if (allowKill && fileKill.exists()) {
               try {
                  getActionContext().err.println("Halting by user request");
                  fileKill.delete();
               } catch (Throwable ignored) {
               }
               Runtime.getRuntime().halt(0);
            }
            if (file.exists()) {
               try {
                  stop();
                  shutdownTimer.cancel();
               } finally {
                  getActionContext().out.println("Server stopped!");
                  getActionContext().out.flush();
                  latchRunning.countDown();
                  if (!embedded) {
                     Runtime.getRuntime().exit(0);
                  }
               }
            }
         }
      }, 500, 500);

      Runtime.getRuntime().addShutdownHook(new Thread("shutdown-hook") {
         @Override
         public void run() {
            Run.this.stop();
         }
      });

   }

   protected void stop() {
      try {
         if (server != null) {
            server.stop(true);
         }
         if (managementContext != null) {
            managementContext.stop();
         }
         if (shutdownTimer != null) {
            shutdownTimer.cancel();
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }


   public void verifyOlderLogging(File etc) {
      File newLogging = new File(etc, Create.ETC_LOG4J2_PROPERTIES);
      File oldLogging = new File(etc, Upgrade.OLD_LOGGING_PROPERTIES);

      if (oldLogging.exists() && !newLogging.exists()) {
         getActionContext().out.println("******************************************************************************************************************************************************************************");
         getActionContext().out.println("Your system has the older logging file " + Upgrade.OLD_LOGGING_PROPERTIES + ", but not the new " + Create.ETC_LOG4J2_PROPERTIES);
         getActionContext().out.println("It appears you did not complete the migration on this artemis instance properly. Please check all the settings or run the './artemis upgrade' command from the new artemis home");
         getActionContext().out.println("******************************************************************************************************************************************************************************");
      }
   }

}
