/*
 * 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 java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.geode.admin.ConfigurationParameter;
import org.apache.geode.admin.UnmodifiableConfigurationException;
import org.apache.geode.internal.i18n.LocalizedStrings;

/**
 * A single configuration parameter of a system member.
 *
 * @since GemFire 3.5
 *
 */
public class ConfigurationParameterImpl implements org.apache.geode.admin.ConfigurationParameter {

  /** Identifying name of this configuration parameter */
  protected String name;
  /** Full description of this configuration parameter */
  protected String description;
  /** The current value */
  protected Object value;
  /** Class type of the value */
  protected Class type;
  /** True if this is modifiable; false if read-only */
  protected boolean userModifiable;
  /** List of listeners to notify when value changes */
  private final List listeners = new ArrayList();

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

  /**
   * Constructs new <code>ConfigurationParameterImpl</code>.
   *
   * @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
   */
  protected ConfigurationParameterImpl(String name, String description, Object value, Class type,
      boolean userModifiable) {
    if (name == null || name.length() == 0) {
      throw new IllegalArgumentException(
          LocalizedStrings.ConfigurationParameterImpl_CONFIGURATIONPARAMETER_NAME_MUST_BE_SPECIFIED
              .toLocalizedString());
    }

    this.name = name;
    setInternalState(description, value, type, userModifiable);
  }

  /**
   * Constructs new <code>ConfigurationParameterImpl</code>.
   *
   * @param name the name of this parameter which cannot change
   * @param value the value of this parameter
   */
  protected ConfigurationParameterImpl(String name, Object value) {
    if (name == null || name.length() == 0) {
      throw new IllegalArgumentException(
          LocalizedStrings.ConfigurationParameterImpl_CONFIGURATIONPARAMETER_NAME_MUST_BE_SPECIFIED
              .toLocalizedString());
    }

    this.name = name;
    setInternalState(name, value, value.getClass(), true);
  }

  /** Constructor to allow serialization by subclass */
  protected ConfigurationParameterImpl() {}

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

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

  public String getDescription() {
    return this.description;
  }

  public Object getValue() {
    return this.value;
  }

  public String getValueAsString() {
    if (isString()) {
      return (String) this.value;
    } else if (isInetAddress()) {
      return InetAddressUtil.toString(this.value);
    } else if (isFile()) {
      return this.value.toString();
    } else if (isOctal()) {
      String strVal = Integer.toOctalString(((Integer) this.value).intValue());
      if (!strVal.startsWith("0")) {
        strVal = "0" + strVal;
      }
      return strVal;
    } else if (isArray()) {
      List list = Arrays.asList((Object[]) this.value);
      return list.toString();
    } else {
      return this.value.toString();
    }
  }

  public Class getValueType() {
    return this.type;
  }

  public boolean isModifiable() {
    return this.userModifiable;
  }

  public boolean isArray() {
    return "manager-parameters".equals(this.name) || "manager-classpaths".equals(this.name);
  }

  public boolean isInetAddress() {
    return java.net.InetAddress.class.isAssignableFrom(this.type);
  }

  public boolean isFile() {
    return java.io.File.class.equals(this.type);
  }

  public boolean isOctal() {
    return "shared-memory-permissions".equals(this.name);
  }

  public boolean isString() {
    return java.lang.String.class.equals(this.type);
  }

  public void setValue(Object value) throws UnmodifiableConfigurationException {
    if (!isModifiable()) {
      throw new UnmodifiableConfigurationException(
          LocalizedStrings.ConfigurationParameterImpl_0_IS_NOT_A_MODIFIABLE_CONFIGURATION_PARAMETER
              .toLocalizedString(getName()));
    }
    if (value == null) {
      throw new IllegalArgumentException(
          LocalizedStrings.ConfigurationParameterImpl_UNABLE_TO_SET_0_TO_NULL_VALUE
              .toLocalizedString(getName()));
    }
    if (!getValueType().equals(value.getClass())) {
      throw new IllegalArgumentException(
          LocalizedStrings.ConfigurationParameterImpl_UNABLE_TO_SET_TYPE_0_WITH_TYPE_1
              .toLocalizedString(
                  new Object[] {getValueType().getName(), value.getClass().getName()}));
    }

    if (value instanceof String && !isString()) {
      // we need to check what the type should be and convert to it...
      setValueFromString((String) value);
    } else {
      this.value = value;
    }
    fireConfigurationParameterValueChanged(this);
  }

  // -------------------------------------------------------------------------
  // Operations for handling the registration of listeners
  // Note: this is only for use within impl pkg and subclass pkgs
  // -------------------------------------------------------------------------

  /** Adds the listener for any changes to this configuration parameter. */
  public void addConfigurationParameterListener(ConfigurationParameterListener listener) {
    if (!this.listeners.contains(listener)) {
      this.listeners.add(listener);
    }
  }

  /** Removes the listener if it's currently registered. */
  public void removeConfigurationParameterListener(ConfigurationParameterListener listener) {
    if (this.listeners.contains(listener)) {
      this.listeners.remove(listener);
    }
  }

  // -------------------------------------------------------------------------
  // Implementation methods
  // -------------------------------------------------------------------------

  protected void setValueFromString(String newValue) {
    if (newValue == null) {
      throw new IllegalArgumentException(
          LocalizedStrings.ConfigurationParameterImpl_UNABLE_TO_SET_0_TO_NULL_VALUE
              .toLocalizedString(getName()));
    }

    if (isInetAddress()) {
      this.value = InetAddressUtil.toInetAddress(newValue);
    } else if (isFile()) {
      this.value = new File(newValue);
    } else if (isOctal()) {
      if (!newValue.startsWith("0")) {
        newValue = "0" + newValue;
      }
      this.value = Integer.valueOf(Integer.parseInt(newValue, 8));
    } else if (isArray()) {
      // parse it TODO
      throw new IllegalArgumentException(
          LocalizedStrings.ConfigurationParameterImpl_SETTING_ARRAY_VALUE_FROM_DELIMITED_STRING_IS_NOT_SUPPORTED
              .toLocalizedString());
    } else {
      this.value = newValue;
    }
  }

  /**
   * Fires changed configuration parameter to registered listeners.
   *
   * @param parm the configuration parameter the changed
   */
  protected void fireConfigurationParameterValueChanged(ConfigurationParameter parm) {
    ConfigurationParameterListener[] listeners = (ConfigurationParameterListener[]) this.listeners
        .toArray(new ConfigurationParameterListener[0]);
    for (int i = 0; i < listeners.length; i++) {
      listeners[i].configurationParameterValueChanged(parm);
    }
  }

  /**
   * Sets the internal state of this configuration parameter.
   *
   * @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
   */
  protected void setInternalState(String description, Object value, Class type,
      boolean userModifiable) {
    this.description = description;
    this.type = type;
    this.userModifiable = userModifiable;
    this.value = value;
  }

  @Override
  public String toString() {
    return this.name;
  }

}
