/**
 * 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.ambari.server.state.kerberos;

import org.apache.ambari.server.AmbariException;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Represents information required to configure Kerberos for a particular service.
 * <p/>
 * The data map is expected to have the following properties:
 * <ul>
 * <li>name</li>
 * <li>components</li>
 * <li>identities</li>
 * <li>configurations</li>
 * </ul>
 * Example:
 * <pre>
 *  "name" => "SERVICE",
 *  "identities" => Collection&lt;Map&lt;String, Object&gt;&gt;
 *  "components" => Collection&lt;Map&lt;String, Object&gt;&gt;
 *  "configurations" => Collection&lt;Map&lt;String, Object&gt;&gt;
 * </pre>
 */

/**
 * KerberosServiceDescriptor is an implementation of an AbstractKerberosDescriptorContainer that
 * encapsulates an Ambari service and it components.
 * <p/>
 * A KerberosServiceDescriptor has the following properties:
 * <ul>
 * <li>name</li>
 * <li>components</li>
 * <li>identities ({@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptorContainer})</li>
 * <li>configurations ({@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptorContainer})</li>
 * </ul>
 * <p/>
 * The following (pseudo) JSON Schema will yield a valid KerberosServiceDescriptor
 * <pre>
 *   {
 *      "$schema": "http://json-schema.org/draft-04/schema#",
 *      "title": "KerberosServiceDescriptor",
 *      "description": "Describes an Ambari service",
 *      "type": "object",
 *      "properties": {
 *        "name": {
 *          "description": "An identifying name for this service descriptor.",
 *          "type": "string"
 *        },
 *        "components": {
 *          "description": "A list of Ambari component descriptors",
 *          "type": "array",
 *          "items": {
 *            "title": "KerberosComponentDescriptor"
 *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosComponentDescriptor}"
 *          }
 *        },
 *        "identities": {
 *          "description": "A list of Kerberos identity descriptors",
 *          "type": "array",
 *          "items": {
 *            "title": "KerberosIdentityDescriptor"
 *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor}"
 *          }
 *        },
 *        "configurations": {
 *          "description": "A list of relevant configuration blocks",
 *          "type": "array",
 *          "items": {
 *            "title": "KerberosConfigurationDescriptor"
 *            "type": "{@link org.apache.ambari.server.state.kerberos.KerberosConfigurationDescriptor}"
 *          }
 *        }
 *      }
 *   }
 * </pre>
 * <p/>
 * In this implementation,
 * {@link org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptor#name} will hold the
 * KerberosServiceDescriptor#name value.
 */
public class KerberosServiceDescriptor extends AbstractKerberosDescriptorContainer {

  /**
   * A Map of the components contained within this KerberosServiceDescriptor
   */
  private Map<String, KerberosComponentDescriptor> components;

  /**
   * Creates a new KerberosServiceDescriptor
   * <p/>
   * See {@link org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor} for the JSON
   * Schema that may be used to generate this map.
   *
   * @param data a Map of values use to populate the data for the new instance
   * @see org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor
   */
  KerberosServiceDescriptor(Map<?, ?> data) {
    // The name for this KerberosServiceDescriptor is stored in the "name" entry in the map
    // This is not automatically set by the super classes.
    this(getStringValue(data, "name"), data);
  }

  /**
   * Creates a new KerberosServiceDescriptor
   * <p/>
   * See {@link org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor} for the JSON
   * Schema that may be used to generate this map.
   *
   * @param name a String declaring this service's name
   * @param data a Map of values use to populate the data for the new instance
   * @see org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor
   */
  KerberosServiceDescriptor(String name, Map<?, ?> data) {
    super(data);

    // This is not automatically set by the super classes.
    setName(name);

    if (data != null) {
      Object list = data.get(KerberosDescriptorType.COMPONENT.getDescriptorPluralName());
      if (list instanceof Collection) {
        // Assume list is Collection<Map<String, Object>>
        for (Object item : (Collection) list) {
          if (item instanceof Map) {
            putComponent(new KerberosComponentDescriptor((Map<?, ?>) item));
          }
        }
      }
    }
  }

  /**
   * Returns a Map of the KerberosComponentDescriptors related to this KerberosServiceDescriptor
   *
   * @return a Map of String to KerberosComponentDescriptor
   */
  public Map<String, KerberosComponentDescriptor> getComponents() {
    return components;
  }

  /**
   * Returns the KerberosComponentDescriptors with the specified name
   *
   * @param name the name of the component for which to retrieve a descriptor
   * @return the KerberosComponentDescriptor for the requested component or null if not found
   */
  public KerberosComponentDescriptor getComponent(String name) {
    return ((name == null) || (components == null)) ? null : components.get(name);
  }

  /**
   * Adds or replaces a KerberosComponentDescriptor
   * <p/>
   * If a KerberosComponentDescriptor with the same name already exists in the components Map, it
   * will be replaced; else a new entry will be made.
   *
   * @param component the KerberosComponentDescriptor to put
   */
  public void putComponent(KerberosComponentDescriptor component) {
    if (component != null) {
      String name = component.getName();

      if (name == null) {
        throw new IllegalArgumentException("The component name must not be null");
      }

      if (components == null) {
        components = new HashMap<String, KerberosComponentDescriptor>();
      }

      components.put(name, component);
      component.setParent(this);
    }
  }

  /**
   * Updates this KerberosServiceDescriptor with data from another KerberosServiceDescriptor
   * <p/>
   * Properties will be updated if the relevant updated values are not null.
   *
   * @param updates the KerberosServiceDescriptor containing the updated values
   */
  public void update(KerberosServiceDescriptor updates) {
    if (updates != null) {
      Map<String, KerberosComponentDescriptor> updatedComponents = updates.getComponents();
      if (updatedComponents != null) {
        for (Map.Entry<String, KerberosComponentDescriptor> entry : updatedComponents.entrySet()) {
          KerberosComponentDescriptor existing = getComponent(entry.getKey());
          if (existing == null) {
            putComponent(entry.getValue());
          } else {
            existing.update(entry.getValue());
          }
        }
      }
    }

    super.update(updates);
  }

  /**
   * Gets the requested AbstractKerberosDescriptor implementation using a type name and a relevant
   * descriptor name.
   * <p/>
   * This implementation handles component descriptors and relies on the
   * AbstractKerberosDescriptorContainer implementation to handle other types.
   *
   * @param type a String indicating the type of the requested descriptor
   * @param name a String indicating the name of the requested descriptor
   * @return a AbstractKerberosDescriptor representing the requested descriptor or null if not found
   */
  @Override
  protected AbstractKerberosDescriptor getDescriptor(KerberosDescriptorType type, String name) {
    if (KerberosDescriptorType.COMPONENT == type) {
      return getComponent(name);
    } else {
      return super.getDescriptor(type, name);
    }
  }

  /**
   * Creates a Map of values that can be used to create a copy of this KerberosServiceDescriptor
   * or generate the JSON structure described in
   * {@link org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor}
   *
   * @return a Map of values for this KerberosServiceDescriptor
   * @see org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor
   */
  @Override
  public Map<String, Object> toMap() {
    Map<String, Object> map = super.toMap();

    if (components != null) {
      List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
      for (KerberosComponentDescriptor component : components.values()) {
        list.add(component.toMap());
      }
      map.put(KerberosDescriptorType.COMPONENT.getDescriptorPluralName(), list);
    }

    return map;
  }

  @Override
  public int hashCode() {
    return super.hashCode() +
        ((getComponents() == null)
            ? 0
            : getComponents().hashCode());
  }

  @Override
  public boolean equals(Object object) {
    if (object == null) {
      return false;
    } else if (object == this) {
      return true;
    } else if (object.getClass() == KerberosServiceDescriptor.class) {
      KerberosServiceDescriptor descriptor = (KerberosServiceDescriptor) object;
      return super.equals(object) &&
          (
              (getComponents() == null)
                  ? (descriptor.getComponents() == null)
                  : getComponents().equals(descriptor.getComponents())
          );
    } else {
      return false;
    }
  }

}

