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

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.geode.CancelCriterion;
import org.apache.geode.annotations.VisibleForTesting;
import org.apache.geode.cache.DiskStore;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.asyncqueue.AsyncEventQueue;
import org.apache.geode.cache.server.CacheServer;
import org.apache.geode.cache.wan.GatewayReceiver;
import org.apache.geode.cache.wan.GatewaySender;
import org.apache.geode.distributed.Locator;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.distributed.internal.ResourceEvent;
import org.apache.geode.distributed.internal.ResourceEventsListener;
import org.apache.geode.distributed.internal.locks.DLockService;
import org.apache.geode.internal.cache.CacheService;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.management.internal.AlertDetails;

/**
 * This Listener listens on various resource creation in GemFire and create/destroys GemFire
 * specific MBeans accordingly
 */
public class ManagementListener implements ResourceEventsListener {

  private final InternalDistributedSystem system;

  private final CancelCriterion cancelCriterion;

  /**
   * Adapter to co-ordinate between GemFire and Federation framework
   */
  private final ManagementAdapter adapter;

  /**
   * ReadWriteLock to protect between handling cache creation/removal vs other notifications
   */
  private final ReadWriteLock readWriteLock;

  public ManagementListener(InternalDistributedSystem system) {
    this(system.getCancelCriterion(), system, new ManagementAdapter(),
        new ReentrantReadWriteLock());
  }

  @VisibleForTesting
  ManagementListener(CancelCriterion cancelCriterion, InternalDistributedSystem system,
      ManagementAdapter adapter,
      ReadWriteLock readWriteLock) {
    this.cancelCriterion = cancelCriterion;
    this.system = system;
    this.adapter = adapter;
    this.readWriteLock = readWriteLock;
  }

  /**
   * Checks various conditions which might arise due to race condition for lock of
   * GemFireCacheImpl.class which is obtained while GemFireCacheImpl constructor, cache.close(),
   * DistributedSystem.disconnect().
   *
   * As ManagementService creation logic is called in cache.init() method it leaves a small window
   * of loosing the lock of GemFireCacheImpl.class
   *
   * These checks ensures that something unwanted has not happened during that small window
   *
   * @return true or false depending on the status of Cache and System
   */
  boolean shouldProceed(ResourceEvent event) {
    InternalDistributedSystem.getConnectedInstance();

    // CACHE_REMOVE is a special event. ForcedDisconnectException may raise this event.
    if (!system.isConnected() && !event.equals(ResourceEvent.CACHE_REMOVE)) {
      return false;
    }

    InternalCache currentCache = system.getCache();
    if (currentCache == null) {
      return false;
    }
    return !currentCache.isClosed();
  }

  /**
   * Handles various GFE resource life-cycle methods vis-a-vis Management and Monitoring
   *
   * It checks for race conditions cases by calling shouldProceed();
   *
   * @param event Management event for which invocation has happened
   * @param resource the GFE resource type
   */
  @Override
  public void handleEvent(ResourceEvent event, Object resource) {
    if (!shouldProceed(event)) {
      return;
    }
    try {
      if (event == ResourceEvent.CACHE_CREATE || event == ResourceEvent.CACHE_REMOVE) {
        readWriteLock.writeLock().lockInterruptibly();
      } else if (event != ResourceEvent.SYSTEM_ALERT) {
        readWriteLock.readLock().lockInterruptibly();
      }
    } catch (InterruptedException e) {
      // prefer CancelException if shutting down
      cancelCriterion.checkCancelInProgress(e);
      throw new RuntimeException(e);
    }
    try {
      switch (event) {
        case CACHE_CREATE:
          InternalCache createdCache = (InternalCache) resource;
          adapter.handleCacheCreation(createdCache);
          break;
        case CACHE_REMOVE:
          InternalCache removedCache = (InternalCache) resource;
          adapter.handleCacheRemoval();
          break;
        case REGION_CREATE:
          Region createdRegion = (Region) resource;
          adapter.handleRegionCreation(createdRegion);
          break;
        case REGION_REMOVE:
          Region removedRegion = (Region) resource;
          adapter.handleRegionRemoval(removedRegion);
          break;
        case DISKSTORE_CREATE:
          DiskStore createdDisk = (DiskStore) resource;
          adapter.handleDiskCreation(createdDisk);
          break;
        case DISKSTORE_REMOVE:
          DiskStore removedDisk = (DiskStore) resource;
          adapter.handleDiskRemoval(removedDisk);
          break;
        case GATEWAYRECEIVER_CREATE:
          GatewayReceiver createdRecv = (GatewayReceiver) resource;
          adapter.handleGatewayReceiverCreate(createdRecv);
          break;
        case GATEWAYRECEIVER_DESTROY:
          GatewayReceiver destroyedRecv = (GatewayReceiver) resource;
          adapter.handleGatewayReceiverDestroy();
          break;
        case GATEWAYRECEIVER_START:
          GatewayReceiver startedRecv = (GatewayReceiver) resource;
          adapter.handleGatewayReceiverStart(startedRecv);
          break;
        case GATEWAYRECEIVER_STOP:
          GatewayReceiver stoppededRecv = (GatewayReceiver) resource;
          adapter.handleGatewayReceiverStop();
          break;
        case GATEWAYSENDER_CREATE:
          GatewaySender sender = (GatewaySender) resource;
          adapter.handleGatewaySenderCreation(sender);
          break;
        case GATEWAYSENDER_START:
          GatewaySender startedSender = (GatewaySender) resource;
          adapter.handleGatewaySenderStart(startedSender);
          break;
        case GATEWAYSENDER_STOP:
          GatewaySender stoppedSender = (GatewaySender) resource;
          adapter.handleGatewaySenderStop(stoppedSender);
          break;
        case GATEWAYSENDER_PAUSE:
          GatewaySender pausedSender = (GatewaySender) resource;
          adapter.handleGatewaySenderPaused(pausedSender);
          break;
        case GATEWAYSENDER_RESUME:
          GatewaySender resumedSender = (GatewaySender) resource;
          adapter.handleGatewaySenderResumed(resumedSender);
          break;
        case GATEWAYSENDER_REMOVE:
          GatewaySender removedSender = (GatewaySender) resource;
          adapter.handleGatewaySenderRemoved(removedSender);
          break;
        case LOCKSERVICE_CREATE:
          DLockService createdLockService = (DLockService) resource;
          adapter.handleLockServiceCreation(createdLockService);
          break;
        case LOCKSERVICE_REMOVE:
          DLockService removedLockService = (DLockService) resource;
          adapter.handleLockServiceRemoval(removedLockService);
          break;
        case MANAGER_CREATE:
          adapter.handleManagerCreation();
          break;
        case MANAGER_START:
          adapter.handleManagerStart();
          break;
        case MANAGER_STOP:
          adapter.handleManagerStop();
          break;
        case ASYNCEVENTQUEUE_CREATE:
          AsyncEventQueue queue = (AsyncEventQueue) resource;
          adapter.handleAsyncEventQueueCreation(queue);
          break;
        case ASYNCEVENTQUEUE_REMOVE:
          AsyncEventQueue removedQueue = (AsyncEventQueue) resource;
          adapter.handleAsyncEventQueueRemoval(removedQueue);
          break;
        case SYSTEM_ALERT:
          AlertDetails details = (AlertDetails) resource;
          adapter.handleSystemNotification(details);
          break;
        case CACHE_SERVER_START:
          CacheServer startedServer = (CacheServer) resource;
          adapter.handleCacheServerStart(startedServer);
          break;
        case CACHE_SERVER_STOP:
          CacheServer stoppedServer = (CacheServer) resource;
          adapter.handleCacheServerStop(stoppedServer);
          break;
        case LOCATOR_START:
          Locator loc = (Locator) resource;
          adapter.handleLocatorStart(loc);
          break;
        case CACHE_SERVICE_CREATE:
          CacheService service = (CacheService) resource;
          adapter.handleCacheServiceCreation(service);
          break;
        default:
          break;
      }
    } finally {
      if (event == ResourceEvent.CACHE_CREATE || event == ResourceEvent.CACHE_REMOVE) {
        readWriteLock.writeLock().unlock();
      } else if (event != ResourceEvent.SYSTEM_ALERT) {
        readWriteLock.readLock().unlock();
      }
    }
  }

}
