/*
 * 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.collections.functors;

import java.util.Map;

import org.apache.commons.collections4.Transformer;

/**
 * {@link ContextTransformer} is a {@link Transformer} implementation that traverses a {@link Map}
 * of {@link Map}s to find the value related to the specified key.
 * <p>
 * This implementation first checks the context map for the explicit key. If the key exists, it
 * returns the value associated with it. If the key does not exist and appears to represent a path
 * via "/"-delimited keys (configurations/service-site/property_name), the path is traversed
 * recursively looking for the requested data.
 * <p>
 * Example context:
 * <pre>
 * |- services : [set of services]
 * |
 * |- configurations
 * |  |- service-site
 * |  |  |- property : [value]
 * |
 * |- key/looks/like/path : [value2]
 * </pre>
 * <ul>
 * <li>If the key was <code>services</code>, <code>[set of services]</code> would be returned</li>
 * <li>If the key was <code>configurations/service-site/property</code>, <code>value</code> would be returned</li>
 * <li>If the key was <code>configurations/service-site</code>, <code>[map of service-site properties]</code> would be returned</li>
 * <li>If the key was <code>key/looks/like/path</code>, <code>value2</code> would be returned</li>
 * </ul>
 */
public class ContextTransformer implements Transformer {

  /**
   * The key to search for
   */
  private final String key;

  /**
   * Constructor.
   *
   * @param key the key to search for
   */
  public ContextTransformer(String key) {
    this.key = key;
  }

  /**
   * Returns the key this transformer is using to search for data
   *
   * @return a string
   */
  public String getKey() {
    return key;
  }

  @Override
  public Object transform(Object o) {
    return transform(key, o);
  }

  /**
   * Traverses the input object (expected to be a {@link Map}) to find the data associated with
   * the specified key.
   * <p>
   * Note: This method is recursive in the even the key represents a path
   *
   * @param key the key to search for
   * @param o   the object containing data to process
   * @return the found data or null
   */
  private Object transform(String key, Object o) {
    Object transformedData = null;

    if (key != null) {
      if (o instanceof Map) {
        Map<?, ?> data = (Map) o;

        if (data.containsKey(key)) {
          transformedData = data.get(key);
        } else {
          // See if the key implies a tree that needs to be traversed...
          // For example:  configurations/service-conf/property_name
          //  A map of maps is expected such that the top level map has a map identified by the key
          //  of "configuration".  The "configuration" map has a map identified by the key of
          // "service-conf". The "service-conf" has a value identified by the key of "property_name".
          String[] parts = key.split("\\/", 2);

          // If only a single item is returned, than the key does not indicate a tree.
          if (parts.length == 2) {
            // If the first item is empty, a leading "/" was encountered... retry with the pruned key
            if (parts[0].isEmpty()) {
              transformedData = transform(parts[1], o);
            } else {
              transformedData = transform(parts[1], data.get(parts[0]));
            }
          }
        }
      }
    }

    return transformedData;
  }

  @Override
  public int hashCode() {
    return (37 * ((key == null) ? 0 : key.hashCode()));
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    } else if (obj == null) {
      return false;
    } else if ((obj instanceof ContextTransformer) && (hashCode() == obj.hashCode())) {
      ContextTransformer t = (ContextTransformer) obj;
      return (key == null) ? (t.key == null) : key.equals(t.key);
    } else {
      return false;
    }
  }
}
