/*
 * 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.common;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
import org.apache.activemq.artemis.api.core.client.ClientMessage;
import org.apache.activemq.artemis.api.core.client.ClientRequestor;
import org.apache.activemq.artemis.api.core.client.ClientSession;
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
import org.apache.activemq.artemis.api.core.client.ServerLocator;
import org.apache.activemq.artemis.api.core.management.ManagementHelper;
import org.apache.activemq.artemis.api.core.management.ResourceNames;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.utils.SpawnedVMSupport;
import org.objectweb.jtests.jms.admin.Admin;

/**
 * AbstractAdmin.
 */
public class AbstractAdmin implements Admin {

   protected ClientSession clientSession;

   protected ClientRequestor requestor;

   protected boolean serverLifeCycleActive;

   protected Process serverProcess;

   protected ServerLocator serverLocator;

   protected ClientSessionFactory sf;

   // this is a constant to control if we should use a separate VM for the server.
   public static final boolean spawnServer = false;

   /**
    * Determines whether to act or 'no-op' on serverStart() and
    * serverStop(). This is used when testing combinations of client and
    * servers with different versions.
    */
   private static final String SERVER_LIVE_CYCLE_PROPERTY = "org.apache.activemq.artemis.jms.ActiveMQAMQPAdmin.serverLifeCycle";

   public AbstractAdmin() {
      serverLifeCycleActive = Boolean.valueOf(System.getProperty(SERVER_LIVE_CYCLE_PROPERTY, "true"));

   }

   @Override
   public String getName() {
      return getClass().getName();
   }

   @Override
   public void start() throws Exception {
      serverLocator = ActiveMQClient.createServerLocatorWithoutHA(new TransportConfiguration(NettyConnectorFactory.class.getName()));
      sf = serverLocator.createSessionFactory();
      clientSession = sf.createSession(ActiveMQDefaultConfiguration.getDefaultClusterUser(), ActiveMQDefaultConfiguration.getDefaultClusterPassword(), false, true, true, false, 1);
      requestor = new ClientRequestor(clientSession, ActiveMQDefaultConfiguration.getDefaultManagementAddress());
      clientSession.start();

   }

   @Override
   public void stop() throws Exception {
      requestor.close();

      if (sf != null) {
         sf.close();
      }

      if (serverLocator != null) {
         serverLocator.close();
      }

      sf = null;
      serverLocator = null;
   }

   @Override
   public Context createContext() throws NamingException {
      return new InitialContext();
   }

   @Override
   public void createConnectionFactory(final String name) {
      throw new RuntimeException("FIXME NYI createConnectionFactory");
   }

   @Override
   public void deleteConnectionFactory(final String name) {
      throw new RuntimeException("FIXME NYI deleteConnectionFactory");
   }

   @Override
   public void createQueue(final String name) {
      Boolean result;
      try {
         invokeSyncOperation(ResourceNames.BROKER, "createQueue", name, RoutingType.ANYCAST.toString(), name, null, true, ActiveMQDefaultConfiguration.getDefaultMaxQueueConsumers(), ActiveMQDefaultConfiguration.getDefaultPurgeOnNoConsumers(), true);
      } catch (Exception e) {
         throw new IllegalStateException(e);
      }
   }

   @Override
   public void deleteQueue(final String name) {
      Boolean result;
      try {
         invokeSyncOperation(ResourceNames.BROKER, "destroyQueue", name, true);
      } catch (Exception e) {
         throw new IllegalStateException(e);
      }
   }

   @Override
   public void createQueueConnectionFactory(final String name) {
      createConnectionFactory(name);
   }

   @Override
   public void deleteQueueConnectionFactory(final String name) {
      deleteConnectionFactory(name);
   }

   @Override
   public void createTopic(final String name) {
      try {
         invokeSyncOperation(ResourceNames.BROKER, "createAddress", name, "MULTICAST");
      } catch (Exception e) {
         throw new IllegalStateException(e);
      }
   }

   @Override
   public void deleteTopic(final String name) {
      Boolean result;
      try {
         invokeSyncOperation(ResourceNames.BROKER, "deleteAddress", name, Boolean.TRUE);
      } catch (Exception e) {
         throw new IllegalStateException(e);
      }
   }

   @Override
   public void createTopicConnectionFactory(final String name) {
      createConnectionFactory(name);
   }

   @Override
   public void deleteTopicConnectionFactory(final String name) {
      deleteConnectionFactory(name);
   }

   @Override
   public void startServer() throws Exception {
      if (!serverLifeCycleActive) {
         return;
      }

      if (spawnServer) {
         String[] vmArgs = new String[]{};
         serverProcess = SpawnedVMSupport.spawnVM(SpawnedJMSServer.class.getName(), vmArgs, false);
         InputStreamReader isr = new InputStreamReader(serverProcess.getInputStream());

         final BufferedReader br = new BufferedReader(isr);
         String line = null;
         while ((line = br.readLine()) != null) {
            System.out.println("SERVER: " + line);
            if ("OK".equals(line.trim())) {
               new Thread(() -> {
                  try {
                     String line1 = null;
                     while ((line1 = br.readLine()) != null) {
                        System.out.println("SERVER: " + line1);
                     }
                  } catch (Exception e) {
                     e.printStackTrace();
                  }
               }).start();
               return;
            } else if ("KO".equals(line.trim())) {
               // something went wrong with the server, destroy it:
               serverProcess.destroy();
               throw new IllegalStateException("Unable to start the spawned server :" + line);
            }
         }
      } else {
         SpawnedJMSServer.startServer();
      }
   }

   @Override
   public void stopServer() throws Exception {
      if (!serverLifeCycleActive) {
         return;
      }
      if (spawnServer) {
         OutputStreamWriter osw = new OutputStreamWriter(serverProcess.getOutputStream());
         osw.write("STOP\n");
         osw.flush();
         int exitValue = serverProcess.waitFor();
         if (exitValue != 0) {
            serverProcess.destroy();
         }
      } else {
         SpawnedJMSServer.stopServer();
      }
   }

   protected Object invokeSyncOperation(final String resourceName,
                                        final String operationName,
                                        final Object... parameters) throws Exception {
      ClientMessage message = clientSession.createMessage(false);
      ManagementHelper.putOperationInvocation(message, resourceName, operationName, parameters);
      ClientMessage reply;
      try {
         reply = requestor.request(message, 3000);
      } catch (Exception e) {
         throw new IllegalStateException("Exception while invoking " + operationName + " on " + resourceName, e);
      }
      if (reply == null) {
         throw new IllegalStateException("no reply received when invoking " + operationName + " on " + resourceName);
      }
      if (!ManagementHelper.hasOperationSucceeded(reply)) {
         throw new IllegalStateException("operation failed when invoking " + operationName +
                                            " on " +
                                            resourceName +
                                            ": " +
                                            ManagementHelper.getResult(reply));
      }
      return ManagementHelper.getResult(reply);
   }

}
