/*
 * 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.drill.exec.coord;

import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.drill.exec.coord.store.TransientStore;
import org.apache.drill.exec.coord.store.TransientStoreConfig;
import org.apache.drill.exec.proto.CoordinationProtos.DrillbitEndpoint;
import org.apache.drill.exec.proto.CoordinationProtos.DrillbitEndpoint.State;
import org.apache.drill.exec.work.foreman.DrillbitStatusListener;

/**
 * Pluggable interface built to manage cluster coordination. Allows Drillbit or DrillClient to register its capabilities
 * as well as understand other node's existence and capabilities.
 **/
public abstract class ClusterCoordinator implements AutoCloseable {
  static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(ClusterCoordinator.class);

  protected ConcurrentHashMap<DrillbitStatusListener, DrillbitStatusListener> listeners = new ConcurrentHashMap<>(
      16, 0.75f, 16);

  /**
   * Start the cluster coordinator. Millis to wait is
   *
   * @param millisToWait
   *          The maximum time to wait before throwing an exception if the
   *          cluster coordination service has not successfully started. Use 0
   *          to wait indefinitely.
   * @throws Exception
   */
  public abstract void start(long millisToWait) throws Exception;

  public abstract RegistrationHandle register(DrillbitEndpoint data);

  public abstract void unregister(RegistrationHandle handle);

  /**
   * Get a collection of available Drillbit endpoints, Thread-safe.
   * Could be slightly out of date depending on refresh policy.
   *
   * @return A collection of available endpoints.
   */
  public abstract Collection<DrillbitEndpoint> getAvailableEndpoints();

  /**
   * Get a collection of ONLINE drillbit endpoints by excluding the drillbits
   * that are in QUIESCENT state (drillbits that are shutting down). Primarily used by the planner
   * to plan queries only on ONLINE drillbits and used by the client during initial connection
   * phase to connect to a drillbit (foreman)
   * @return A collection of ONLINE endpoints
   */

  public abstract Collection<DrillbitEndpoint> getOnlineEndPoints();

  public abstract RegistrationHandle update(RegistrationHandle handle, State state);

  public interface RegistrationHandle {
    /**
     * Get the drillbit endpoint associated with the registration handle
     * @return drillbit endpoint
     */
    public abstract DrillbitEndpoint getEndPoint();

    public abstract void setEndPoint(DrillbitEndpoint endpoint);
  }

  public abstract DistributedSemaphore getSemaphore(String name, int maximumLeases);

  /**
   * Returns a {@link TransientStore store} instance with the given {@link TransientStoreConfig configuration}.
   *
   * Note that implementor might cache the instance so new instance creation is not guaranteed.
   *
   * @param config  store configuration
   * @param <V>  value type for this store
   */
  public abstract <V> TransientStore<V> getOrCreateTransientStore(TransientStoreConfig<V> config);

  /**
   * Actions to take when there are a set of new de-active drillbits.
   * @param unregisteredBits
   */
  protected void drillbitUnregistered(Set<DrillbitEndpoint> unregisteredBits) {
    for (DrillbitStatusListener listener : listeners.keySet()) {
      listener.drillbitUnregistered(unregisteredBits);
    }
  }

  protected void drillbitRegistered(Set<DrillbitEndpoint> registeredBits) {
    for (DrillbitStatusListener listener : listeners.keySet()) {
      listener.drillbitRegistered(registeredBits);
    }
  }

  /**
   * Register a DrillbitStatusListener.
   * Note : the listeners are not guaranteed to be called in the order in which they call this method, since all the listeners are in a ConcurrentHashMap.
   * @param listener
   */
  public void addDrillbitStatusListener(DrillbitStatusListener listener) {
    listeners.putIfAbsent(listener, listener);
  }

  /**
   * Unregister a DrillbitStatusListener.
   * @param listener
   */
  public void removeDrillbitStatusListener(DrillbitStatusListener listener) {
    listeners.remove(listener);
  }

  public boolean isDrillbitInState(DrillbitEndpoint endpoint, DrillbitEndpoint.State state) {
    return (!endpoint.hasState() || endpoint.getState().equals(state));
  }

}
