package jetbrains.mps.kotlin.api.declaration;

/*Generated by MPS */

import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SPropertyOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import jetbrains.mps.internal.collections.runtime.Sequence;
import java.util.Collections;
import java.util.Iterator;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SConceptOperations;
import org.jetbrains.mps.openapi.language.SInterfaceConcept;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import org.jetbrains.mps.openapi.language.SProperty;

/**
 * Abstraction of a function declaration usable by the kotlin type system.
 * 
 * The reason for this to exists is to allow external languages or concepts that
 * do not fit the IFunctionDeclaration to work within kotlin type system:
 * - new types can easily be implemented as IType and refer any kind of declarations
 * - declarations (such as methods or classes) may not be easily adaptable to kotlin
 * interfaces (eg. for baseLanguage, we would need to make BaseMethodDeclaration implement
 * IFunctionDeclaration OR rewrite the same logic for functions twice in the typesystem)
 * 
 * Since declarations cannot be altered easily, calls are responsible for providing implementation
 * of this interface for their callee.
 */
public interface FunctionDeclaration {
  /**
   * Returns the node declaring the function.
   */
  SNode getNode();

  /**
   * Returns the name of the function
   */
  default String getName() {
    return SPropertyOperations.getString(SNodeOperations.as(getNode(), CONCEPTS.INamedConcept$Kd), PROPS.name$MnvL);
  }

  /**
   * Returns the parameters of the function.
   * 
   * Any parameter in the list may be null (indicating error in declaration or unchecked parameter).
   * 
   * @see jetbrains.mps.kotlin.api.declaration.ParameterDeclaration 
   */
  @NotNull
  Iterable<ParameterDeclaration> getParameters();

  /**
   * Returns the type parameters of the function
   * 
   * @see jetbrains.mps.kotlin.api.declaration.TypeParameterDeclaration 
   */
  @NotNull
  Iterable<TypeParameterDeclaration> getTypeParameters();


  /**
   * Returns the return type of the method, if applicable.
   * 
   * @see jetbrains.mps.kotlin.api.declaration.InferrableFunctionDeclaration for inferring that return type from a node instead
   */
  SNode getReturnType();

  /**
   * Returns the declared receiver type of the function if applicable.
   */
  @Nullable
  SNode getReceiverType();

  /**
   * Returns the list of modifiers applied to this method.
   */
  @NotNull
  default Iterable<SAbstractConcept> getModifiers() {
    return Sequence.fromIterable(Collections.<SAbstractConcept>emptyList());
  }

  default String getFunctionPresentation(boolean erased) {
    StringBuilder builder = new StringBuilder();

    builder.append(getName());

    if (!(erased) && Sequence.fromIterable(getTypeParameters()).isNotEmpty()) {
      builder.append("<");
      Iterator<TypeParameterDeclaration> itr = Sequence.fromIterable(getTypeParameters()).iterator();
      if (itr.hasNext()) {
        builder.append(itr.next().getName());
      }
      while (itr.hasNext()) {
        builder.append(", ");
        builder.append(itr.next().getName());
      }
      builder.append(">");
    }

    builder.append("(");

    Iterator<ParameterDeclaration> itr = Sequence.fromIterable(getParameters()).iterator();
    if (itr.hasNext()) {
      builder.append(itr.next().getPresentationInFunction(erased));
    }
    while (itr.hasNext()) {
      builder.append(", ");
      builder.append(itr.next().getPresentationInFunction(erased));
    }

    builder.append(")");
    return builder.toString();
  }

  /**
   * Returns whether the method has the given modifier. If the provided modifier to test against is null, returns true.
   * 
   * In Kotlin, for instance, operator functions can be used as regular calls (a.plus(b)), so if null is provided there is
   * not reason to prevent it (an operator function is a function after all).
   */
  static boolean hasModifier(@NotNull FunctionDeclaration declaration, @Nullable final SAbstractConcept modifier) {
    return modifier == null || Sequence.fromIterable(declaration.getModifiers()).any((it) -> SConceptOperations.isSubConceptOf(SNodeOperations.asSConcept(it), SNodeOperations.asSConcept(modifier)));
  }

  final class CONCEPTS {
    /*package*/ static final SInterfaceConcept INamedConcept$Kd = MetaAdapterFactory.getInterfaceConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x110396eaaa4L, "jetbrains.mps.lang.core.structure.INamedConcept");
  }

  final class PROPS {
    /*package*/ static final SProperty name$MnvL = MetaAdapterFactory.getProperty(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x110396eaaa4L, 0x110396ec041L, "name");
  }
}
