/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.master;

import alluxio.conf.Configuration;
import alluxio.conf.PropertyKey;
import alluxio.grpc.JournalDomain;
import alluxio.grpc.NodeState;
import alluxio.master.journal.JournalSystem;
import alluxio.master.service.SimpleService;
import alluxio.util.network.NetworkAddressUtils;
import alluxio.util.network.NetworkAddressUtils.ServiceType;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * This class is responsible for initializing the different masters that are configured to run.
 */
@NotThreadSafe
public abstract class AlluxioSimpleMasterProcess extends MasterProcess {
  private static final Logger LOG = LoggerFactory.getLogger(AlluxioSimpleMasterProcess.class);

  /** The master name. */
  final String mMasterName;

  /** The master journal domain. */
  final JournalDomain mJournalDomain;

  /**
   * The base class for running a master process that runs a single master implementing
   * the {@link AbstractMaster} interface.
   * @param masterName the name of the master (used when printing log messages)
   * @param journalDomain the journal domain of the master
   * @param journalSystem the journal system
   * @param leaderSelector the leader selector
   * @param webService the web service information
   * @param rpcService the rpc service information
   * @param hostNameKey the property key used to get the host name of this master
   */
  AlluxioSimpleMasterProcess(String masterName, JournalDomain journalDomain,
      JournalSystem journalSystem, PrimarySelector leaderSelector, ServiceType webService,
      ServiceType rpcService, PropertyKey hostNameKey) {
    super(journalSystem, leaderSelector, webService, rpcService);
    mMasterName = masterName;
    mJournalDomain = journalDomain;
    if (!Configuration.isSet(hostNameKey)) {
      Configuration.set(hostNameKey,
          NetworkAddressUtils.getLocalHostName(
              (int) Configuration.getMs(PropertyKey.NETWORK_HOST_RESOLUTION_TIMEOUT_MS)));
    }
    try {
      if (!mJournalSystem.isFormatted()) {
        mJournalSystem.format();
      }
    } catch (Exception e) {
      LOG.error("Failed to create {} master", mMasterName, e);
      throw new RuntimeException(String.format("Failed to create %s master", mMasterName), e);
    }
  }

  @Override
  public void start() throws Exception {
    mServices.forEach(SimpleService::start);
    mJournalSystem.start();
    // the leader selector is created in state STANDBY. Once mLeaderSelector.start is called, it
    // can transition to PRIMARY at any point.
    mLeaderSelector.start(getRpcAddress());

    while (!Thread.interrupted()) {
      // Start the master components in standby mode
      // Eg. for job master they are the JobMaster and JournalMaster
      startMasterComponents(false);
      LOG.info("Standby started");
      // We are in standby mode. Nothing to do until we become the primary.
      mLeaderSelector.waitForState(NodeState.PRIMARY);
      LOG.info("Transitioning from standby to primary");
      mJournalSystem.gainPrimacy();
      stopMasterComponents();
      LOG.info("Standby stopped");
      startMasterComponents(true);
      mServices.forEach(SimpleService::promote);
      LOG.info("Primary started");
      // We are in primary mode. Nothing to do until we become the standby.
      mLeaderSelector.waitForState(NodeState.STANDBY);
      LOG.info("Transitioning from primary to standby");
      mServices.forEach(SimpleService::demote);
      stopMasterComponents();
      mJournalSystem.losePrimacy();
      LOG.info("Primary stopped");
    }
  }

  /**
   * Stops the Alluxio master server.
   *
   * @throws Exception if stopping the master fails
   */
  @Override
  public void stop() throws Exception {
    mServices.forEach(SimpleService::stop);
    mJournalSystem.stop();
    stopMasterComponents();
    mLeaderSelector.stop();
  }

  protected void startMasterComponents(boolean isLeader) {
    try {
      mRegistry.start(isLeader);
    } catch (IOException e) {
      LOG.error(e.getMessage(), e);
      throw new RuntimeException(e.getMessage(), e);
    }
  }

  protected void stopMasterComponents() {
    try {
      mRegistry.stop();
    } catch (IOException e) {
      LOG.error("Failed to stop {} master", mMasterName, e);
      throw new RuntimeException(String.format("Failed to stop %s master", mMasterName), e);
    }
  }

  @Override
  public String toString() {
    return String.format("Alluxio %s master @ %s", mMasterName, mRpcConnectAddress);
  }
}
