/*
 * 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.geode.session.tests;

import java.io.IOException;
import java.util.ArrayList;

import org.apache.logging.log4j.Logger;
import org.codehaus.cargo.container.State;

import org.apache.geode.logging.internal.log4j.api.LogService;

/**
 * Manages multiple J2EE containers using cargo.
 *
 * Provides methods to start and stop J2EE containers and obtain the http ports those containers are
 * listening on.
 */
public class ContainerManager {
  private static final Logger logger = LogService.getLogger();
  private String testName;

  private final ArrayList<ServerContainer> containers;

  public ContainerManager() {
    containers = new ArrayList<>();
  }

  /**
   * @return the number of containers managed
   */
  public int numContainers() {
    return containers.size();
  }

  public void cleanUp() throws IOException {
    for (int i = 0; i < numContainers(); i++) {
      getContainer(i).cleanUp();
    }
  }

  /*
   * Add a new container to manage using the specified installation
   *
   * The container will not be running until one of the start methods is called.
   */
  public ServerContainer addContainer(ContainerInstall install) throws IOException {
    return addContainer(install, containers.size());
  }

  /*
   * Add multiple containers to manage using the specified installation.
   *
   * The containers will not be running until one of the start methods is called.
   */
  public void addContainers(int numContainers, ContainerInstall install) throws IOException {
    for (int i = 0; i < numContainers; i++) {
      addContainer(install);
    }
  }

  /*
   * Start the given container
   */
  public void startContainer(int index) {
    try {
      getContainer(index).start();
    } catch (Exception e) {
      throw new RuntimeException("Something very bad happened when trying to start container "
          + getContainerDescription(index), e);
    }

    logger.info("Started container " + getContainerDescription(index));
  }

  /*
   * Start all containers specified by the given indexes
   */
  public void startContainers(ArrayList<Integer> indexes) {
    for (int index : indexes) {
      startContainer(index);
    }
  }

  /**
   * Start all containers that are not currently running.
   */
  public void startAllInactiveContainers() {
    startContainers(getInactiveContainerIndexes());
  }

  /*
   * Stop the given container
   */
  public void stopContainer(int index) {
    getContainer(index).stop();

    logger.info("Stopped container " + getContainerDescription(index));
  }

  /*
   * Stop all containers specified by the given indexes
   */
  public void stopContainers(ArrayList<Integer> indexes) {
    for (int index : indexes) {
      stopContainer(index);
    }
  }

  /**
   * Stop all containers that are currently running.
   */
  public void stopAllActiveContainers() {
    stopContainers(getActiveContainerIndexes());
  }

  public void dumpLogs() throws IOException {
    for (ServerContainer container : containers) {
      container.dumpLogs();
    }
  }

  /*
   * Set the name of the current test
   *
   * Used for debugging so that log files can be easily identified.
   */
  public void setTestName(String testName) {
    this.testName = testName;
  }

  /**
   * Get the positions of the containers with the given container state
   *
   * @param state A string representing the Cargo state a container is in. The possible states can
   *        be found in as static variables in the {@link State} class.
   * @return the positions of the containers with the given container state
   */
  public ArrayList<Integer> getContainerIndexesWithState(String state) {
    if (!(state.equals(State.STARTED.toString()) || state.equals(State.STOPPED.toString())
        || state.equals(State.STARTING.toString()) || state.equals(State.STOPPING.toString())
        || state.equals(State.UNKNOWN.toString()))) {
      throw new IllegalArgumentException(
          "State must be one of the 5 specified cargo state strings (stopped, started, starting, stopping, or unknown). State given was: "
              + state);
    }

    ArrayList<Integer> indexes = new ArrayList<>();
    for (int i = 0; i < numContainers(); i++) {
      if (getContainer(i).getState().toString().equals(state)) {
        indexes.add(i);
      }
    }
    return indexes;
  }

  /*
   * Return the cargo container of all of the containers in the given state
   */
  public ArrayList<ServerContainer> getContainersWithState(String state) {
    ArrayList<ServerContainer> statedContainers = new ArrayList<>();
    for (int index : getContainerIndexesWithState(state)) {
      statedContainers.add(getContainer(index));
    }
    return statedContainers;
  }

  /*
   * Get the port of the container at the given index
   */
  public String getContainerPort(int index) {
    return getContainer(index).getPort();
  }

  /*
   * Get the container at the given index
   */
  public ServerContainer getContainer(int index) {
    return containers.get(index);
  }

  /**
   * Get a human readable unique description for the container (calls
   * {@link ServerContainer#toString()})
   *
   * @param index the index of the container
   * @return a human readable unique description for the container
   */
  public String getContainerDescription(int index) {
    return getContainer(index).toString();
  }

  /*
   * Create a container to manage, given an installation.
   */
  private ServerContainer addContainer(ContainerInstall install, int index) throws IOException {
    ServerContainer container = install.generateContainer(testName + "_" + index);

    containers.add(index, container);

    logger.info("Setup container " + getContainerDescription(index));
    return container;
  }

  /*
   * Remove the container in the given index from the list
   */
  public ServerContainer removeContainer(int index) {
    return containers.remove(index);
  }

  /*
   * Remove the given container
   */
  public boolean removeContainer(ServerContainer container) {
    return containers.remove(container);
  }

  /**
   * Get the indexes of all active containers
   */
  private ArrayList<Integer> getActiveContainerIndexes() {
    return getContainerIndexesWithState(State.STARTED.toString());
  }

  /**
   * Get all active containers
   */
  private ArrayList<ServerContainer> getActiveContainers() {
    return getContainersWithState(State.STARTED.toString());
  }

  /**
   * Get the indexes of all inactive containers
   */
  private ArrayList<Integer> getInactiveContainerIndexes() {
    ArrayList<Integer> indexes = getContainerIndexesWithState(State.STOPPED.toString());
    indexes.addAll(getContainerIndexesWithState(State.UNKNOWN.toString()));
    return indexes;
  }

  /**
   * Get all inactive containers
   */
  private ArrayList<ServerContainer> getInactiveContainers() {
    ArrayList<ServerContainer> inactiveContainers =
        getContainersWithState(State.STOPPED.toString());
    inactiveContainers.addAll(getContainersWithState(State.UNKNOWN.toString()));
    return inactiveContainers;
  }
}
