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

import org.apache.geode.internal.concurrent.ConcurrentHashSet;

import java.util.List;

/**
 * TODO: define another addStatistic for StatisticDescriptor which will enable static monitoring
 * that will fire for all instances even ones that may not yet be created at the time this monitor
 * is defined
 * 
 * @since GemFire 7.0
 */
public abstract class StatisticsMonitor {

  private final Object mutex = new Object();

  private final ConcurrentHashSet<StatisticsListener> listeners = new ConcurrentHashSet<>();

  private final ConcurrentHashSet<StatisticId> statisticIds = new ConcurrentHashSet<>();

  public StatisticsMonitor() {}

  public StatisticsMonitor addStatistic(final StatisticId statId) {
    if (statId == null) {
      throw new NullPointerException("StatisticId is null");
    }
    if (!this.statisticIds.contains(statId)) {
      this.statisticIds.add(statId);
    }
    return this;
  }

  public StatisticsMonitor removeStatistic(final StatisticId statId) {
    if (statId == null) {
      throw new NullPointerException("StatisticId is null");
    }
    if (this.statisticIds.contains(statId)) {
      this.statisticIds.remove(statId);
    }
    return this;
  }

  public void addListener(final StatisticsListener listener) {
    if (listener == null) {
      throw new NullPointerException("StatisticsListener is null");
    }
    synchronized (this.mutex) {
      if (!this.listeners.contains(listener)) {
        this.listeners.add(listener);
        getStatMonitorHandler().addMonitor(this);
      }
    }
  }

  public void removeListener(final StatisticsListener listener) {
    if (listener == null) {
      throw new NullPointerException("StatisticsListener is null");
    }
    synchronized (this.mutex) {
      if (this.listeners.contains(listener)) {
        this.listeners.remove(listener);
        if (this.listeners.isEmpty()) {
          try {
            getStatMonitorHandler().removeMonitor(this);
          } catch (IllegalStateException ignore) {
            // sample collector and handlers were closed (ok on removal)
          }
        }
      }
    }
  }

  /**
   * This method may be overridden but please ensure that you invoke super.monitor(long, List) from
   * this method in the subclass.
   * 
   * @param millisTimeStamp the real time in millis of the sample
   * @param resourceInstances resources with one or more updated values
   */
  protected void monitor(final long millisTimeStamp,
      final List<ResourceInstance> resourceInstances) {
    monitorStatisticIds(millisTimeStamp, resourceInstances);
  }

  private void monitorStatisticIds(final long millisTimeStamp,
      final List<ResourceInstance> resourceInstances) {
    if (!this.statisticIds.isEmpty()) {
      // TODO:
    }
  }

  protected void notifyListeners(final StatisticsNotification notification) {
    for (StatisticsListener listener : this.listeners) {
      listener.handleNotification(notification);
    }
  }

  protected Object mutex() {
    return this.mutex;
  }

  StatMonitorHandler getStatMonitorHandler() {
    return SampleCollector.getStatMonitorHandler();
  }

  /** For testing only */
  ConcurrentHashSet<StatisticsListener> getStatisticsListenersSnapshot() {
    return this.listeners;
  }

  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder(getClass().getName());
    sb.append("@").append(System.identityHashCode(this)).append("{");
    sb.append("statisticIds=").append(this.statisticIds);
    sb.append(", listeners=").append(this.listeners);
    final StringBuilder toAppend = appendToString();
    if (toAppend == null) {
      sb.append(", ").append(toAppend);
    }
    sb.append("}");
    return sb.toString();
  }

  /**
   * Override to append to toString()
   */
  protected StringBuilder appendToString() {
    return null;
  }
}
