/*
 * Copyright (C) 2008 Google Inc.
 *
 * Licensed 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 com.google.inject.grapher;

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.ProviderMethod;
import com.google.inject.internal.util.StackTraceElements;
import com.google.inject.spi.ElementSource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.List;

/**
 * Reasonable implementation for {@link NameFactory}. Mostly takes various {@link
 * Object#toString()}s and strips package names out of them so that they'll fit on the graph.
 *
 * @author phopkins@gmail.com (Pete Hopkins)
 */
public class ShortNameFactory implements NameFactory {
  @Override
  public String getMemberName(Member member) {
    if (member instanceof Constructor) {
      return "<init>";
    } else if (member instanceof Method) {
      return "#" + member.getName() + "(...)";
    } else {
      return member.getName();
    }
  }

  @Override
  public String getAnnotationName(Key<?> key) {
    Annotation annotation = key.getAnnotation();
    Class<? extends Annotation> annotationType = key.getAnnotationType();
    if (annotation != null) {
      annotationType = annotation.annotationType();

      String annotationString = annotation.toString();
      String canonicalName =
          Annotations.annotationInstanceClassString(annotationType, /* includePackage = */ true)
              .substring(1);
      String simpleName = annotationType.getSimpleName();
      return annotationString.replace(canonicalName, simpleName).replace("()", "");
    } else if (annotationType != null) {
      return "@" + annotationType.getSimpleName();
    } else {
      return "";
    }
  }

  @Override
  public String getClassName(Key<?> key) {
    TypeLiteral<?> typeLiteral = key.getTypeLiteral();
    return stripPackages(typeLiteral.toString());
  }

  @Override
  public String getInstanceName(Object instance) {
    if (instance instanceof ProviderMethod) {
      return getMethodString(((ProviderMethod<?>) instance).getMethod());
    }

    if (instance instanceof CharSequence) {
      return "\"" + instance + "\"";
    }

    try {
      if (instance.getClass().getMethod("toString").getDeclaringClass().equals(Object.class)) {
        return stripPackages(instance.getClass().getName());
      }
    } catch (SecurityException e) {
      throw new AssertionError(e);
    } catch (NoSuchMethodException e) {
      throw new AssertionError(e);
    }

    return instance.toString();
  }

  /**
   * Returns a name for a Guice "source" object. This will typically be either a {@link
   * StackTraceElement} for when the binding is made to the instance, or a {@link Method} when a
   * provider method is used.
   */
  @Override
  public String getSourceName(Object source) {
    if (source instanceof ElementSource) {
      source = ((ElementSource) source).getDeclaringSource();
    }
    if (source instanceof Method) {
      source = StackTraceElements.forMember((Method) source);
    }

    if (source instanceof StackTraceElement) {
      return getFileString((StackTraceElement) source);
    }

    return stripPackages(source.toString());
  }

  protected String getFileString(StackTraceElement stackTraceElement) {
    return stackTraceElement.getFileName() + ":" + stackTraceElement.getLineNumber();
  }

  protected String getMethodString(Method method) {
    List<String> paramStrings = Lists.newArrayList();
    for (Class<?> paramType : method.getParameterTypes()) {
      paramStrings.add(paramType.getSimpleName());
    }

    String paramString = Joiner.on(", ").join(paramStrings);
    return "#" + method.getName() + "(" + paramString + ")";
  }

  /**
   * Eliminates runs of lowercase characters and numbers separated by periods. Seems to remove
   * packages from fully-qualified type names pretty well.
   */
  private String stripPackages(String str) {
    return str.replaceAll("(^|[< .\\(])([a-z0-9]+\\.)*", "$1");
  }
}
