/**
 * Copyright © 2006-2016 Web Cohesion (info@webcohesion.com)
 *
 * 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.webcohesion.enunciate.util.freemarker;

import com.webcohesion.enunciate.EnunciateContext;
import com.webcohesion.enunciate.util.HasClientConvertibleType;
import freemarker.template.TemplateModelException;

import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.*;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Used to output a reference for an accessor or parameter or declaration.
 *
 * @author Ryan Heaton
 */
public class ClientClassnameForMethod extends ClientPackageForMethod {

  protected final LinkedList<String> typeParameterDeclarationStack = new LinkedList<String>();

  public ClientClassnameForMethod(Map<String, String> conversions, EnunciateContext context) {
    super(conversions, context);
  }

  @Override
  public String convertUnwrappedObject(Object unwrapped) throws TemplateModelException {
    if (unwrapped instanceof HasClientConvertibleType) {
      return convert((HasClientConvertibleType) unwrapped);
    }
    else if (unwrapped instanceof TypeParameterElement) {
      return convert((TypeParameterElement) unwrapped);
    }
    else {
      return super.convertUnwrappedObject(unwrapped);
    }
  }

  /**
   * Converts the specified type parameter declaration.
   *
   * @param typeParameterElement The type parameter declaration.
   * @return The type parameter declaration.
   */
  public String convert(TypeParameterElement typeParameterElement) throws TemplateModelException {
    String conversion = typeParameterElement.getSimpleName().toString();
    if (typeParameterElement.getBounds() != null && !typeParameterElement.getBounds().isEmpty() && !typeParameterDeclarationStack.contains(conversion)) {
      typeParameterDeclarationStack.addFirst(conversion);
      try {
        conversion += " extends " + convert(typeParameterElement.getBounds().iterator().next());
      }
      finally {
        typeParameterDeclarationStack.removeFirst();
      }
    }

    return conversion;
  }

  public String convert(HasClientConvertibleType element) throws TemplateModelException {
    return convert(element.getClientConvertibleType());
  }

  @Override
  public String convert(TypeMirror typeMirror) throws TemplateModelException {
    String conversion;

    if (typeMirror instanceof WildcardType) {
      conversion = convert((WildcardType) typeMirror);
    }
    else if (typeMirror instanceof TypeVariable) {
      return convert((TypeVariable) typeMirror);
    }
    else {
      conversion = super.convert(typeMirror);
      boolean isArray = typeMirror.getKind() == TypeKind.ARRAY;

      if (typeMirror instanceof DeclaredType && !"java.lang.Object".equals(conversion)) {
        conversion += convertDeclaredTypeArguments(((DeclaredType) typeMirror).getTypeArguments());
      }

      if (isArray) {
        conversion += "[]";
      }
    }

    return conversion;

  }

  public String convert(WildcardType wildCard) throws TemplateModelException {
    String conversion;
    if (wildCard.getSuperBound() != null) {
      conversion = "? super " + convert(wildCard.getSuperBound());
    }
    else if (wildCard.getExtendsBound() != null) {
      conversion = "? extends " + convert(wildCard.getExtendsBound());
    }
    else {
      conversion = "?";
    }
    return conversion;
  }

  public String convertDeclaredTypeArguments(List<? extends TypeMirror> actualTypeArguments) throws TemplateModelException {
    StringBuilder typeArgs = new StringBuilder();
    if (actualTypeArguments.size() > 0) {
      typeArgs.append("<");
      Iterator<? extends TypeMirror> it = actualTypeArguments.iterator();
      while (it.hasNext()) {
        TypeMirror mirror = it.next();
        typeArgs.append(convert(mirror));
        if (it.hasNext()) {
          typeArgs.append(", ");
        }
      }
      typeArgs.append(">");
    }
    return typeArgs.toString();
  }

  public String convert(TypeVariable variableMirror) throws TemplateModelException {
    TypeParameterElement parameterElement = (TypeParameterElement) variableMirror.asElement();
    return parameterElement.getSimpleName().toString();
  }

  @Override
  public String convert(TypeElement declaration) throws TemplateModelException {
    PackageElement pkg = this.context.getProcessingEnvironment().getElementUtils().getPackageOf(declaration);
    String convertedPackage = convertPackage(pkg);
    String simpleName = declaration.getSimpleName().toString();
    return convertedPackage + getPackageSeparator() + simpleName;
  }

  protected String convertPackage(PackageElement pkg) {
    return super.convert(pkg);
  }

  protected String getPackageSeparator() {
    return ".";
  }

  @Override
  public String convert(PackageElement packageDeclaration) {
    throw new UnsupportedOperationException("packages don't have a classname.");
  }
}