/*
 * 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.tests.integration.cluster;

import java.util.ArrayList;
import java.util.List;

import org.apache.activemq.artemis.core.server.NodeManager;
import org.apache.activemq.artemis.core.server.impl.InVMNodeManager;
import org.apache.activemq.artemis.tests.util.SpawnedTestBase;
import org.junit.Test;

import static java.lang.management.ManagementFactory.getRuntimeMXBean;
import static org.apache.activemq.artemis.tests.integration.cluster.NodeManagerAction.AWAIT_PRIMARY;
import static org.apache.activemq.artemis.tests.integration.cluster.NodeManagerAction.CHECK_ID;
import static org.apache.activemq.artemis.tests.integration.cluster.NodeManagerAction.CRASH_PRIMARY;
import static org.apache.activemq.artemis.tests.integration.cluster.NodeManagerAction.DOESNT_HAVE_BACKUP;
import static org.apache.activemq.artemis.tests.integration.cluster.NodeManagerAction.DOESNT_HAVE_PRIMARY;
import static org.apache.activemq.artemis.tests.integration.cluster.NodeManagerAction.HAS_BACKUP;
import static org.apache.activemq.artemis.tests.integration.cluster.NodeManagerAction.HAS_PRIMARY;
import static org.apache.activemq.artemis.tests.integration.cluster.NodeManagerAction.PAUSE_PRIMARY;
import static org.apache.activemq.artemis.tests.integration.cluster.NodeManagerAction.RELEASE_BACKUP;
import static org.apache.activemq.artemis.tests.integration.cluster.NodeManagerAction.START_BACKUP;
import static org.apache.activemq.artemis.tests.integration.cluster.NodeManagerAction.START_PRIMARY;
import static org.apache.activemq.artemis.tests.integration.cluster.NodeManagerAction.STOP_BACKUP;

public class NodeManagerTest extends SpawnedTestBase {

   @Test
   public void testID() throws Exception {
      NodeManagerAction live1 = new NodeManagerAction(CHECK_ID);
      performWork(live1);
   }

   @Test
   public void testPrimary() throws Exception {
      NodeManagerAction live1 = new NodeManagerAction(START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      performWork(live1);
   }

   @Test
   public void testSimplePrimaryAndBackup() throws Exception {
      NodeManagerAction primary1 = new NodeManagerAction(START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup1 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, PAUSE_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      performWork(primary1, backup1);
   }

   @Test
   public void testSimpleBackupAndPrimary() throws Exception {
      NodeManagerAction live1 = new NodeManagerAction(START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup1 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, PAUSE_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      performWork(backup1, live1);
   }

   @Test
   public void testSimplePrimaryAnd2Backups() throws Exception {
      NodeManagerAction live1 = new NodeManagerAction(START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup1 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup2 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      performWork(live1, backup1, backup2);
   }

   @Test
   public void testSimple2BackupsAndPrimary() throws Exception {
      NodeManagerAction live1 = new NodeManagerAction(START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup1 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup2 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      performWork(backup1, backup2, live1);
   }

   @Test
   public void testSimplePrimaryAnd2BackupsPaused() throws Exception {
      NodeManagerAction primary1 = new NodeManagerAction(START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup1 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, PAUSE_PRIMARY, START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup2 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, PAUSE_PRIMARY, START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      performWork(primary1, backup1, backup2);
   }

   @Test
   public void testSimple2BackupsPausedAndPrimary() throws Exception {
      NodeManagerAction live1 = new NodeManagerAction(START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup1 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, PAUSE_PRIMARY, START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup2 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, PAUSE_PRIMARY, START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      performWork(backup1, backup2, live1);
   }

   @Test
   public void testBackupsOnly() throws Exception {
      NodeManagerAction backup1 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, STOP_BACKUP, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup2 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, STOP_BACKUP, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup3 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, STOP_BACKUP, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup4 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, STOP_BACKUP, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup5 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, STOP_BACKUP, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup6 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, STOP_BACKUP, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup7 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, STOP_BACKUP, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup8 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, STOP_BACKUP, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup9 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, STOP_BACKUP, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup10 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, STOP_BACKUP, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      NodeManagerAction backup11 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, STOP_BACKUP, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY);
      performWork(backup1, backup2, backup3, backup4, backup5, backup6, backup7, backup8, backup9, backup10, backup11);
   }

   @Test
   public void testPrimaryAndBackupActiveForcesFailback() throws Exception {
      NodeManagerAction primary1 = new NodeManagerAction(START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY);
      NodeManagerAction backup1 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, AWAIT_PRIMARY, HAS_PRIMARY, PAUSE_PRIMARY);
      performWork(primary1, backup1);
   }

   @Test
   public void testPrimaryAnd2BackupsActiveForcesFailback() throws Exception {
      NodeManagerAction primary1 = new NodeManagerAction(START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_PRIMARY, HAS_PRIMARY, DOESNT_HAVE_BACKUP, CRASH_PRIMARY);
      NodeManagerAction backup1 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, CRASH_PRIMARY);
      NodeManagerAction backup2 = new NodeManagerAction(DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, START_BACKUP, HAS_BACKUP, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, CRASH_PRIMARY, DOESNT_HAVE_BACKUP, DOESNT_HAVE_PRIMARY, AWAIT_PRIMARY, RELEASE_BACKUP, HAS_PRIMARY, CRASH_PRIMARY);
      performWork(primary1, backup1, backup2);
   }

   public void performWork(NodeManagerAction... actions) throws Exception {
      NodeManager nodeManager = new InVMNodeManager(false);
      List<NodeRunner> nodeRunners = new ArrayList<>();
      Thread[] threads = new Thread[actions.length];
      for (NodeManagerAction action : actions) {
         NodeRunner nodeRunner = new NodeRunner(nodeManager, action);
         nodeRunners.add(nodeRunner);
      }
      for (int i = 0, nodeRunnersSize = nodeRunners.size(); i < nodeRunnersSize; i++) {
         NodeRunner nodeRunner = nodeRunners.get(i);
         threads[i] = new Thread(nodeRunner);
         threads[i].start();
      }

      for (Thread thread : threads) {
         try {
            thread.join(5000);
         } catch (InterruptedException e) {
            //
         }
         if (thread.isAlive()) {
            thread.interrupt();
            fail("thread still running");
         }
      }

      for (NodeRunner nodeRunner : nodeRunners) {
         if (nodeRunner.e != null) {
            nodeRunner.e.printStackTrace();
            fail(nodeRunner.e.getMessage());
         }
      }
   }

   protected static boolean isDebug() {
      return getRuntimeMXBean().getInputArguments().toString().contains("jdwp");
   }

   static class NodeRunner implements Runnable {

      private NodeManagerAction action;
      private NodeManager manager;
      Throwable e;

      NodeRunner(NodeManager nodeManager, NodeManagerAction action) {
         this.manager = nodeManager;
         this.action = action;
      }

      @Override
      public void run() {
         try {
            action.performWork(manager);
         } catch (Throwable e) {
            this.e = e;
         }
      }
   }

}
