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

import org.apache.geode.CancelException;
import org.apache.geode.SystemFailure;
import org.apache.geode.admin.*;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.Role;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.distributed.internal.DistributionConfigImpl;
import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
import org.apache.geode.internal.Config;
import org.apache.geode.internal.ConfigSource;
import org.apache.geode.internal.admin.GemFireVM;
import org.apache.geode.internal.admin.StatResource;
import org.apache.geode.internal.i18n.LocalizedStrings;
import org.apache.geode.internal.logging.LogService;
import org.apache.logging.log4j.Logger;

import java.net.InetAddress;
import java.util.*;

/**
 * Member of a GemFire system.
 *
 * @since GemFire 3.5
 */
public class SystemMemberImpl implements org.apache.geode.admin.SystemMember,
    org.apache.geode.admin.internal.ConfigurationParameterListener {

  private static final Logger logger = LogService.getLogger();

  /**
   * Identifying name of this member. Note that by default this is the string form of internalId but
   * the ManagedSystemMemberImpl subclass resets it to getNewId()
   */
  protected String id;

  /** Unique internal id that the system impl identifies this member with */
  protected InternalDistributedMember internalId;

  /** The name of this system member */
  protected String name;

  /** Host name of the machine this member resides on */
  protected String host;

  /** The internal configuration this impl delegates to for runtime config */
  // private Config config;

  /**
   * The configuration parameters for this member. Maps the name of the ConfigurationParameter to
   * the ConfigurationParameter.
   */
  protected Map parms = new HashMap();

  /** The {@link AdminDistributedSystem} this is a member of */
  protected AdminDistributedSystem system;

  /** Internal GemFire vm to delegate to */
  private GemFireVM vm;

  // -------------------------------------------------------------------------
  // Constructor(s)
  // -------------------------------------------------------------------------

  /**
   * Constructs new <code>SystemMemberImpl</code> for a <code>ManagedEntity</code> that has yet to
   * be started.
   *
   * @param system the distributed system this member belongs to
   */
  protected SystemMemberImpl(AdminDistributedSystem system) throws AdminException {

    this.system = system;
    refreshConfig(getDefaultConfig());
  }

  /**
   * Constructs new <code>SystemMemberImpl</code> from the given <code>GemFireVM</code>. This
   * constructor is invoked when we discover a new member of the distributed system.
   *
   * @param system the distributed system this member belongs to
   * @param vm internal GemFire vm to delegate to
   */
  public SystemMemberImpl(AdminDistributedSystem system, GemFireVM vm) throws AdminException {

    this(system);
    setGemFireVM(vm);
  }

  /**
   * Constructs the instance of SystemMember using the corresponding InternalDistributedMember
   * instance of a DS member for the given AdminDistributedSystem.
   * 
   * @param system Current AdminDistributedSystem instance
   * @param member InternalDistributedMember instance for which a SystemMember instance is to be
   *        constructed.
   * @throws AdminException if construction of SystemMember fails
   * 
   * @since GemFire 6.5
   */
  protected SystemMemberImpl(AdminDistributedSystem system, InternalDistributedMember member)
      throws AdminException {
    this(system);
    updateByInternalDistributedMember(member);
  }

  // -------------------------------------------------------------------------
  // Attribute accessors and mutators
  // -------------------------------------------------------------------------

  /**
   * Returns a <code>Config</code> object with the appropriate default values for a newly-created
   * system member.
   */
  protected Config getDefaultConfig() {
    Properties props = new Properties();
    return new DistributionConfigImpl(props);
  }

  public AdminDistributedSystem getDistributedSystem() {
    return this.system;
  }

  public InternalDistributedMember getInternalId() {
    return internalId;
  }

  public String getId() {
    return this.id;
  }

  public String getName() {
    return this.name;
  }

  public String getHost() {
    return this.host;
  }

  public InetAddress getHostAddress() {
    return InetAddressUtil.toInetAddress(this.getHost());
  }

  // -------------------------------------------------------------------------
  // Operations
  // -------------------------------------------------------------------------

  public String getLog() {
    String childTail = null;
    String mainTail = null;
    GemFireVM vm = getGemFireVM();
    if (vm != null) {
      String[] log = vm.getSystemLogs();
      if (log != null && log.length > 0)
        mainTail = log[0];
      if (log != null && log.length > 1)
        childTail = log[1];
    }

    if (childTail == null && mainTail == null) {
      return LocalizedStrings.SystemMemberImpl_NO_LOG_FILE_CONFIGURED_LOG_MESSAGES_WILL_BE_DIRECTED_TO_STDOUT
          .toLocalizedString();
    } else {
      StringBuffer result = new StringBuffer();
      if (mainTail != null) {
        result.append(mainTail);
      }
      if (childTail != null) {
        result.append(
            "\n" + LocalizedStrings.SystemMemberImpl_TAIL_OF_CHILD_LOG.toLocalizedString() + "\n");
        result.append(childTail);
      }
      return result.toString();
    }
  }

  public java.util.Properties getLicense() {
    GemFireVM vm = getGemFireVM();
    if (vm == null)
      return null;
    return new Properties();
  }

  public String getVersion() {
    GemFireVM vm = getGemFireVM();
    if (vm == null)
      return null;
    return vm.getVersionInfo();
  }

  public StatisticResource[] getStat(String statisticsTypeName)
      throws org.apache.geode.admin.AdminException {
    StatisticResource[] res = new StatisticResource[0];
    if (this.vm != null) {
      res = getStatsImpl(this.vm.getStats(statisticsTypeName));
    }
    return res.length == 0 ? null : res;
  }

  public StatisticResource[] getStats() throws org.apache.geode.admin.AdminException {
    StatisticResource[] statsImpl = new StatisticResource[0];
    if (this.vm != null) {
      statsImpl = getStatsImpl(this.vm.getStats(null));
    }
    return statsImpl;
  }

  public boolean hasCache() {
    GemFireVM member = getGemFireVM();
    if (member == null) {
      return false;

    } else {
      return member.getCacheInfo() != null;
    }
  }

  public SystemMemberCache getCache() throws org.apache.geode.admin.AdminException {
    GemFireVM vm = getGemFireVM(); // fix for bug 33505
    if (vm == null)
      return null;
    try {
      return createSystemMemberCache(vm);

    } catch (CancelException ex) {
      return null;

    } catch (CacheDoesNotExistException ex) {
      return null;
    }
  }

  public void refreshConfig() throws org.apache.geode.admin.AdminException {
    GemFireVM vm = getGemFireVM();
    if (vm == null)
      return;
    refreshConfig(vm.getConfig());
  }

  /**
   * Sets the value of this system member's distribution-related configuration based on the given
   * <code>Config</code> object.
   */
  public void refreshConfig(Config config) throws org.apache.geode.admin.AdminException {
    if (config == null) {
      throw new AdminException(
          LocalizedStrings.SystemMemberImpl_FAILED_TO_REFRESH_CONFIGURATION_PARAMETERS_FOR_0
              .toLocalizedString(new Object[] {getId()}));
    }

    String[] names = config.getAttributeNames();
    if (names == null || names.length < 1) {
      throw new AdminException(
          LocalizedStrings.SystemMemberImpl_FAILED_TO_REFRESH_CONFIGURATION_PARAMETERS_FOR_0
              .toLocalizedString(new Object[] {getId()}));
    }

    for (int i = 0; i < names.length; i++) {
      String name = names[i];
      Object value = config.getAttributeObject(name);
      if (value != null) {
        ConfigurationParameter parm = createConfigurationParameter(name, // name
            config.getAttributeDescription(name), // description
            value, // value
            config.getAttributeType(name), // valueType
            config.isAttributeModifiable(name)); // isModifiable
        ((ConfigurationParameterImpl) parm).addConfigurationParameterListener(this);
        this.parms.put(name, parm);
      }
    }
  }

  public ConfigurationParameter[] getConfiguration() {
    ConfigurationParameter[] array = new ConfigurationParameter[this.parms.size()];
    this.parms.values().toArray(array);
    return array;
  }

  public ConfigurationParameter[] setConfiguration(ConfigurationParameter[] parms)
      throws AdminException {

    for (int i = 0; i < parms.length; i++) {
      ConfigurationParameter parm = parms[i];
      this.parms.put(parm.getName(), parm);
    }

    GemFireVM vm = getGemFireVM();
    if (vm != null) {
      // update internal vm's config...
      Config config = vm.getConfig();
      for (int i = 0; i < parms.length; i++) {
        config.setAttributeObject(parms[i].getName(), parms[i].getValue(), ConfigSource.runtime());
      }
      vm.setConfig(config);
    }

    return this.getConfiguration();
  }

  public SystemMemberType getType() {
    return SystemMemberType.APPLICATION;
  }

  // -------------------------------------------------------------------------
  // Listener callbacks
  // -------------------------------------------------------------------------

  // -- org.apache.geode.admin.internal.ConfigurationParameterListener ---
  public void configurationParameterValueChanged(ConfigurationParameter parm) {
    try {
      setConfiguration(new ConfigurationParameter[] {parm});
    } catch (org.apache.geode.admin.AdminException e) {
      // this shouldn't occur since this is a config listener method...
      logger.warn(e.getMessage(), e);
      throw new RuntimeAdminException(e);
    } catch (java.lang.Exception e) {
      logger.warn(e.getMessage(), e);
    }
    // catch (java.lang.RuntimeException e) {
    // logWriter.warning(e);
    // throw e;
    // }
    catch (VirtualMachineError err) {
      SystemFailure.initiateFailure(err);
      // If this ever returns, rethrow the error. We're poisoned
      // now, so don't let this thread continue.
      throw err;
    } catch (java.lang.Error e) {
      // Whenever you catch Error or Throwable, you must also
      // catch VirtualMachineError (see above). However, there is
      // _still_ a possibility that you are dealing with a cascading
      // error condition, so you also need to check to see if the JVM
      // is still usable:
      SystemFailure.checkFailure();
      logger.error(e.getMessage(), e);
      throw e;
    }
  }

  // -------------------------------------------------------------------------
  // Overridden method(s) from java.lang.Object
  // -------------------------------------------------------------------------

  @Override
  public String toString() {
    return getName();
  }

  // -------------------------------------------------------------------------
  // Template methods with default behavior impl'ed. Override if needed.
  // -------------------------------------------------------------------------

  /**
   * Returns the <code>GemFireVM</code> that underlies this <code>SystemMember</code>.
   */
  protected GemFireVM getGemFireVM() {
    return this.vm;
  }

  /**
   * Sets the <code>GemFireVM</code> that underlies this <code>SystemMember</code>. This method is
   * used when a member, such as a cache server, is started by the admin API.
   */
  void setGemFireVM(GemFireVM vm) throws AdminException {
    this.vm = vm;
    if (vm != null) {
      this.internalId = vm.getId();
      this.id = this.internalId.toString();
      this.name = vm.getName();
      this.host = InetAddressUtil.toString(vm.getHost());
    } else {
      this.internalId = null;
      this.id = null;
      // leave this.name set to what it is (how come?)
      this.host = this.getHost();
    }

    if (DistributionConfig.DEFAULT_NAME.equals(this.name)) {
      // Fix bug 32877
      this.name = this.id;
    }

    if (vm != null) {
      this.refreshConfig();
    }
  }

  /**
   * Updates this SystemMember instance using the corresponding InternalDistributedMember
   * 
   * @param member InternalDistributedMember instance to update this SystemMember
   * 
   * @since GemFire 6.5
   */
  private void updateByInternalDistributedMember(InternalDistributedMember member) {
    if (member != null) {
      this.internalId = member;
      this.id = this.internalId.toString();
      this.host = this.internalId.getHost();
      this.name = this.internalId.getName();
      if (this.name == null || DistributionConfig.DEFAULT_NAME.equals(this.name)) {
        /*
         * name could be null & referring to description of a fix for 32877
         */
        this.name = this.id;
      }
    }
  }

  /**
   * Template method for creating {@link StatisticResource}.
   *
   * @param stat the internal stat resource to wrap with {@link StatisticResource}
   * @return new impl instance of {@link StatisticResource}
   */
  protected StatisticResource createStatisticResource(StatResource stat)
      throws org.apache.geode.admin.AdminException {
    return new StatisticResourceImpl(stat, this);
  }

  /**
   * Template method for creating {@link ConfigurationParameter}.
   *
   * @param name the name of this parameter which cannot change
   * @param description full description to use
   * @param value the value of this parameter
   * @param type the class type of the value
   * @param userModifiable true if this is modifiable; false if read-only
   * @return new impl instance of {@link ConfigurationParameter}
   */
  protected ConfigurationParameter createConfigurationParameter(String name, String description,
      Object value, Class type, boolean userModifiable) {
    return new ConfigurationParameterImpl(name, description, value, type, userModifiable);
  }

  /**
   * Template method for creating {@link SystemMemberCache}.
   *
   * @param vm the GemFire vm to retrieve cache info from
   * @return new impl instance of {@link SystemMemberCache}
   */
  protected SystemMemberCache createSystemMemberCache(GemFireVM vm)
      throws org.apache.geode.admin.AdminException {
    return new SystemMemberCacheImpl(vm);
  }

  /** Wrap the internal stats with impls of {@link StatisticResource} */
  protected StatisticResource[] getStatsImpl(StatResource[] stats)
      throws org.apache.geode.admin.AdminException {
    List statList = new ArrayList();
    for (int i = 0; i < stats.length; i++) {
      statList.add(createStatisticResource(stats[i]));
    }
    return (StatisticResource[]) statList.toArray(new StatisticResource[0]);
  }

  public String[] getRoles() {
    Set roles = this.internalId.getRoles();
    String[] roleNames = new String[roles.size()];
    Iterator iter = roles.iterator();
    for (int i = 0; i < roleNames.length; i++) {
      Role role = (Role) iter.next();
      roleNames[i] = role.getName();
    }
    return roleNames;
  }

  public DistributedMember getDistributedMember() {
    return this.internalId;
  }
}

