/*
 * 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.cache.extension.mock;

import java.util.List;
import java.util.Set;

import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;

import org.apache.geode.cache.Region;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.ResultCollector;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.internal.InternalConfigurationPersistenceService;
import org.apache.geode.distributed.internal.InternalLocator;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.management.cli.Result;
import org.apache.geode.management.internal.cli.result.CommandResult;
import org.apache.geode.management.internal.cli.result.model.ResultModel;
import org.apache.geode.management.internal.configuration.domain.XmlEntity;
import org.apache.geode.management.internal.functions.CliFunctionResult;
import org.apache.geode.management.internal.security.ResourceOperation;
import org.apache.geode.management.internal.util.ManagementUtils;
import org.apache.geode.security.ResourcePermission.Operation;
import org.apache.geode.security.ResourcePermission.Resource;

/**
 * Mock Extension gfsh commands.
 *
 * @since GemFire 8.1
 */
public class MockExtensionCommands {

  public static final String OPTION_VALUE = "value";

  public static final String OPTION_REGION_NAME = "region-name";

  public static final String CREATE_MOCK_REGION_EXTENSION = "create mock region extension";

  public static final String ALTER_MOCK_REGION_EXTENSION = "alter mock region extension";

  public static final String DESTROY_MOCK_REGION_EXTENSION = "destroy mock region extension";

  public static final String CREATE_MOCK_CACHE_EXTENSION = "create mock cache extension";

  public static final String ALTER_MOCK_CACHE_EXTENSION = "alter mock cache extension";

  public static final String DESTROY_MOCK_CACHE_EXTENSION = "destroy mock cache extension";

  /**
   * Creates a {@link MockRegionExtension} on the given <code>regionName</code>.
   *
   * @param regionName {@link Region} name on which to create {@link MockRegionExtension} .
   * @param value {@link String} value to set on {@link MockRegionExtension#setValue(String)}.
   * @return {@link Result}
   * @since GemFire 8.1
   */
  @ShellMethod(value = "Creates a mock region extension", key = CREATE_MOCK_REGION_EXTENSION)
  @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ)
  public Result createMockRegionExtension(
      @ShellOption(value = OPTION_REGION_NAME) final String regionName,
      @ShellOption(value = OPTION_VALUE) final String value) {
    return executeFunctionOnAllMembersTabulateResultPersist(
        CreateMockRegionExtensionFunction.INSTANCE, true,
        CreateMockRegionExtensionFunction.toArgs(regionName, value));
  }

  /**
   * Alters a {@link MockRegionExtension} on the given <code>regionName</code>.
   *
   * @param regionName {@link Region} name on which to create {@link MockRegionExtension} .
   * @param value {@link String} value to set on {@link MockRegionExtension#setValue(String)}.
   * @return {@link Result}
   * @since GemFire 8.1
   */
  @ShellMethod(value = "Alters a mock region extension", key = ALTER_MOCK_REGION_EXTENSION)
  @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ)
  public Result alterMockRegionExtension(
      @ShellOption(value = OPTION_REGION_NAME) final String regionName,
      @ShellOption(value = OPTION_VALUE) final String value) {
    return executeFunctionOnAllMembersTabulateResultPersist(
        AlterMockRegionExtensionFunction.INSTANCE, true,
        AlterMockRegionExtensionFunction.toArgs(regionName, value));
  }

  /**
   * Destroy the {@link MockRegionExtension} on the given <code>regionName</code>.
   *
   * @param regionName {@link Region} name on which to create {@link MockRegionExtension} .
   * @return {@link Result}
   * @since GemFire 8.1
   */
  @ShellMethod(value = "Destroys a mock region extension", key = DESTROY_MOCK_REGION_EXTENSION)
  @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ)
  public Result destroyMockRegionExtension(
      @ShellOption(value = OPTION_REGION_NAME) final String regionName) {
    return executeFunctionOnAllMembersTabulateResultPersist(
        DestroyMockRegionExtensionFunction.INSTANCE, true,
        DestroyMockRegionExtensionFunction.toArgs(regionName));
  }

  /**
   * Creates a {@link MockCacheExtension}.
   *
   * @param value {@link String} value to set on {@link MockCacheExtension#setValue(String)}.
   * @return {@link Result}
   * @since GemFire 8.1
   */
  @ShellMethod(value = "Creates a mock cache extension", key = CREATE_MOCK_CACHE_EXTENSION)
  @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ)
  public Result createMockCacheExtension(
      @ShellOption(value = OPTION_VALUE) final String value) {
    return executeFunctionOnAllMembersTabulateResultPersist(
        CreateMockCacheExtensionFunction.INSTANCE, true,
        CreateMockCacheExtensionFunction.toArgs(value));
  }

  /**
   * Alter a {@link MockCacheExtension}.
   *
   * @param value {@link String} value to set on {@link MockCacheExtension#setValue(String)}.
   * @return {@link Result}
   * @since GemFire 8.1
   */
  @ShellMethod(value = "Alters a mock cache extension", key = ALTER_MOCK_CACHE_EXTENSION)
  @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ)
  public Result alterMockCacheExtension(
      @ShellOption(value = OPTION_VALUE) final String value) {
    return executeFunctionOnAllMembersTabulateResultPersist(
        AlterMockCacheExtensionFunction.INSTANCE, true,
        AlterMockCacheExtensionFunction.toArgs(value));
  }

  /**
   * Destroy a {@link MockCacheExtension}.
   *
   * @return {@link Result}
   * @since GemFire 8.1
   */
  @ShellMethod(value = "Destroys a mock cache extension", key = DESTROY_MOCK_CACHE_EXTENSION)
  @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ)
  public Result destroyMockCacheExtension() {
    return executeFunctionOnAllMembersTabulateResultPersist(
        DestroyMockCacheExtensionFunction.INSTANCE, false);
  }

  /**
   * Call <code>function</code> with <code>args</code> on all members, tabulate results and persist
   * shared config if changed.
   *
   * @param function {@link Function} to execute.
   * @param addXmlElement If <code>true</code> then add result {@link XmlEntity} to the config,
   *        otherwise delete it.
   * @param args Arguments to pass to function.
   * @since GemFire 8.1
   */
  protected Result executeFunctionOnAllMembersTabulateResultPersist(final Function function,
      final boolean addXmlElement, final Object... args) {
    InternalCache cache = GemFireCacheImpl.getInstance();
    final Set<DistributedMember> members = ManagementUtils.getAllNormalMembers(cache);

    @SuppressWarnings("unchecked")
    final ResultCollector<CliFunctionResult, List<CliFunctionResult>> resultCollector =
        (ResultCollector<CliFunctionResult, List<CliFunctionResult>>) ManagementUtils
            .executeFunction(function, args, members);
    final List<CliFunctionResult> functionResults = resultCollector.getResult();

    ResultModel resultModel = ResultModel.createMemberStatusResult(functionResults);

    XmlEntity xmlEntity = functionResults.stream().filter(CliFunctionResult::isSuccessful)
        .map(CliFunctionResult::getXmlEntity)
        .findFirst().orElse(null);

    InternalConfigurationPersistenceService ccService =
        InternalLocator.getLocator().getConfigurationPersistenceService();
    System.out.println("MockExtensionCommands: persisting xmlEntity=" + xmlEntity);
    if (xmlEntity != null) {
      if (addXmlElement) {
        ccService.addXmlEntity(xmlEntity, null);
      } else {
        ccService.deleteXmlEntity(xmlEntity, null);
      }
    }

    return new CommandResult(resultModel);
  }

}
