/*
 * 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.gobblin.metrics;

import java.util.AbstractMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;

import javax.annotation.Nullable;


/**
 * A class representing a dimension or property associated with a {@link Taggable}.
 *
 * @param <T> type of the tag value
 *
 * @author Yinan Li
 */
public class Tag<T> extends AbstractMap.SimpleEntry<String, T> {

  private static final long serialVersionUID = -5083709915031933607L;
  private static final char KEY_VALUE_SEPARATOR = ':';

  /**
   * Reverse of Tag.toString(). Parses a string of the form "key:value" into a {@link Tag}.
   *
   * <p>
   *   If there are multiple ":" in the input string, the key will be the substring up to the first ":", and the
   *   value will be the substring after the first ":".
   * </p>
   *
   * @param tagKeyValue String of the form "key:value".
   * @return {@link org.apache.gobblin.metrics.Tag} parsed from input.
   */
  public static Tag<String> fromString(String tagKeyValue) {
    List<String> splitKeyValue = Splitter.on(KEY_VALUE_SEPARATOR).limit(2).omitEmptyStrings().splitToList(tagKeyValue);
    if(splitKeyValue.size() == 2) {
      return new Tag<>(splitKeyValue.get(0), splitKeyValue.get(1));
    } else {
      return null;
    }
  }

  public Tag(String key, T value) {
    super(key, value);
  }

  @JsonCreator
  public Tag(Map.Entry<? extends String, ? extends T> entry) {
    super(entry);
  }

  /**
   * Converts a {@link Map} of key, value pairs to a {@link List} of {@link Tag}s. Each key, value pair will be used to
   * create a new {@link Tag}.
   *
   * @param tagsMap a {@link Map} of key, value pairs that should be converted into {@link Tag}s
   *
   * @return a {@link List} of {@link Tag}s
   */
  public static <T> List<Tag<T>> fromMap(Map<? extends String, T> tagsMap) {
    ImmutableList.Builder<Tag<T>> tagsBuilder = ImmutableList.builder();
    for (Map.Entry<? extends String, T> entry : tagsMap.entrySet()) {
      tagsBuilder.add(new Tag<>(entry));
    }
    return tagsBuilder.build();
  }

  /**
   * Converts a {@link List} of {@link Tag}s to a {@link Map} of key, value pairs.
   *
   * @param tags a {@link List} of {@link Tag}s that should be converted to key, value pairs
   *
   * @return a {@link Map} of key, value pairs
   */
  public static <T> Map<? extends String, T> toMap(List<Tag<T>> tags) {
    ImmutableMap.Builder<String, T> tagsMapBuilder = ImmutableMap.builder();
    for (Tag<T> tag : tags) {
      tagsMapBuilder.put(tag.getKey(), tag.getValue());
    }
    return tagsMapBuilder.build();
  }

  /**
   * Converts a {@link List} of wildcard {@link Tag}s to a {@link List} of {@link String} {@link Tag}s.
   *
   * @param tags a {@link List} of {@link Tag}s that should be converted to {@link Tag}s with value of type {@link String}
   *
   * @return a {@link List} of {@link Tag}s
   *
   * @see #tagValueToString(Tag)
   */
  public static List<Tag<String>> tagValuesToString(List<? extends Tag<?>> tags) {
    return Lists.transform(tags, new Function<Tag<?>, Tag<String>>() {
      @Nullable @Override public Tag<String> apply(Tag<?> input) {
        return input == null ? null : Tag.tagValueToString(input);
      }
    });
  }

  /**
   * Converts a wildcard {@link Tag} to a {@link String} {@link Tag}. This method uses the {@link Object#toString()}
   * method to convert the wildcard type to a {@link String}.
   *
   * @param tag a {@link Tag} that should be converted to a {@link Tag} with value of type {@link String}
   *
   * @return a {@link Tag} with a {@link String} value
   */
  public static Tag<String> tagValueToString(Tag<?> tag) {
    return new Tag<>(tag.getKey(), tag.getValue().toString());
  }

  @Override
  public String toString() {
    return getKey() + KEY_VALUE_SEPARATOR + getValue();
  }
}
