@Override
  protected boolean specificValidate(SymbolResolver resolver, ErrorCollector errorCollector) {
    if (baseType.isPresent()) {
      if (!baseType.get().isReferenceTypeUsage()
          || !baseType.get().asReferenceTypeUsage().isClass(resolver)) {
        errorCollector.recordSemanticError(
            baseType.get().getPosition(), "Only classes can be extended");
        return false;
      }
    }

    for (TypeUsageNode typeUsage : interfaces) {
      if (!typeUsage.isReferenceTypeUsage()
          || !typeUsage.asReferenceTypeUsage().isInterface(resolver)) {
        errorCollector.recordSemanticError(
            typeUsage.getPosition(), "Only interfaces can be implemented");
        return false;
      }
    }

    if (getExplicitConstructors().size() > 1) {
      for (TurinTypeContructorDefinitionNode contructorDefinition : getExplicitConstructors()) {
        errorCollector.recordSemanticError(
            contructorDefinition.getPosition(), "At most one explicit constructor can be defined");
      }
      return false;
    }

    return super.specificValidate(resolver, errorCollector);
  }
 @Override
 public Optional<InternalConstructorDefinition> findConstructor(List<ActualParam> actualParams) {
   ensureIsInitialized(symbolResolver());
   for (InternalConstructorDefinition constructor : constructors) {
     if (constructor.match(symbolResolver(), actualParams)) {
       return Optional.of(constructor);
     }
   }
   return Optional.empty();
 }
 @Override
 public Iterable<Node> getChildren() {
   List<Node> children = new LinkedList<>();
   children.addAll(members);
   children.addAll(annotations);
   if (baseType.isPresent()) {
     children.add(baseType.get());
   }
   children.addAll(interfaces);
   return children;
 }
  @Override
  public JvmConstructorDefinition resolveConstructorCall(List<ActualParam> actualParams) {
    // all named parameters should be after the named ones
    if (!ParamUtils.verifyOrder(actualParams)) {
      throw new IllegalArgumentException(
          "Named params should all be grouped after the positional ones");
    }

    ensureIsInitialized(symbolResolver());
    Optional<InternalConstructorDefinition> constructor =
        constructors.stream().filter((c) -> c.match(symbolResolver(), actualParams)).findFirst();

    if (!constructor.isPresent()) {
      throw new UnsolvedConstructorException(getQualifiedName(), actualParams);
    }

    return constructor.get().getJvmConstructorDefinition();
  }
 @Override
 public Optional<Invokable> getMethod(
     String method, boolean staticContext, Map<String, TypeUsage> typeParams) {
   ensureIsInitialized(symbolResolver());
   Set<InternalMethodDefinition> methods = Collections.emptySet();
   if (methodsByName.containsKey(method)) {
     methods =
         methodsByName
             .get(method)
             .stream()
             .filter((m) -> m.getJvmMethodDefinition().isStatic() == staticContext)
             .collect(Collectors.toSet());
   }
   if (methods.isEmpty()) {
     return Optional.empty();
   } else {
     return Optional.of(new MethodSetAsInvokableType(methods, typeParams));
   }
 }
  @Override
  public Optional<Symbol> findSymbol(String name, SymbolResolver resolver) {
    // TODO support references to methods
    for (Property property : this.getAllProperties(resolver)) {
      if (property.getName().equals(name)) {
        return Optional.of(property);
      }
    }

    return super.findSymbol(name, resolver);
  }
  @Override
  public Optional<InternalMethodDefinition> findMethod(
      String methodName, List<ActualParam> actualParams, boolean staticContext) {
    // all named parameters should be after the named ones
    if (!ParamUtils.verifyOrder(actualParams)) {
      throw new IllegalArgumentException(
          "Named params should all be grouped after the positional ones");
    }

    ensureIsInitialized(symbolResolver());
    if (!methodsByName.containsKey(methodName)) {
      return Optional.empty();
    }
    Optional<InternalMethodDefinition> method =
        methodsByName
            .get(methodName)
            .stream()
            .filter((m) -> m.match(symbolResolver(), actualParams))
            .findFirst();
    return method;
  }
 public void setBaseType(TypeUsageNode baseType) {
   baseType.setParent(this);
   this.baseType = Optional.of(baseType);
 }
/** Type defined in Turin. */
public class TurinTypeDefinition extends TypeDefinitionNode {
  private List<Node> members = new ArrayList<>();
  private List<TypeUsageNode> interfaces = new ArrayList<>();
  private Optional<TypeUsageNode> baseType = Optional.empty();

  private List<AnnotationUsage> annotations = new ArrayList<>();

  public List<TurinTypeContructorDefinitionNode> getExplicitConstructors() {
    return members
        .stream()
        .filter((m) -> m instanceof TurinTypeContructorDefinitionNode)
        .map((m) -> (TurinTypeContructorDefinitionNode) m)
        .collect(Collectors.toList());
  }

  @Override
  protected boolean specificValidate(SymbolResolver resolver, ErrorCollector errorCollector) {
    if (baseType.isPresent()) {
      if (!baseType.get().isReferenceTypeUsage()
          || !baseType.get().asReferenceTypeUsage().isClass(resolver)) {
        errorCollector.recordSemanticError(
            baseType.get().getPosition(), "Only classes can be extended");
        return false;
      }
    }

    for (TypeUsageNode typeUsage : interfaces) {
      if (!typeUsage.isReferenceTypeUsage()
          || !typeUsage.asReferenceTypeUsage().isInterface(resolver)) {
        errorCollector.recordSemanticError(
            typeUsage.getPosition(), "Only interfaces can be implemented");
        return false;
      }
    }

    if (getExplicitConstructors().size() > 1) {
      for (TurinTypeContructorDefinitionNode contructorDefinition : getExplicitConstructors()) {
        errorCollector.recordSemanticError(
            contructorDefinition.getPosition(), "At most one explicit constructor can be defined");
      }
      return false;
    }

    return super.specificValidate(resolver, errorCollector);
  }

  public void setBaseType(TypeUsageNode baseType) {
    baseType.setParent(this);
    this.baseType = Optional.of(baseType);
  }

  public void addInterface(TypeUsageNode interfaze) {
    interfaze.setParent(this);
    interfaces.add(interfaze);
  }

  public void addAnnotation(AnnotationUsage annotation) {
    annotation.setParent(this);
    annotations.add(annotation);
  }

  public String getQualifiedName() {
    String contextName = contextName();
    if (contextName.isEmpty()) {
      return name;
    } else {
      return contextName + "." + name;
    }
  }

  private Map<String, List<InternalMethodDefinition>> methodsByName;
  private List<InternalConstructorDefinition> constructors;

  private void registerMethod(InternalMethodDefinition method) {
    if (!methodsByName.containsKey(method.getMethodName())) {
      methodsByName.put(method.getMethodName(), new ArrayList<>());
    }
    methodsByName.get(method.getMethodName()).add(method);
  }

  private void initializeMethodsByName(SymbolResolver resolver) {
    methodsByName = new HashMap<>();
    // TODO methods inherited by Object
    // TODO if we implement inheritance also other methods inherited from classes or interfaces
    for (Property property : getDirectProperties(resolver)) {
      {
        String descriptor = "()" + property.getTypeUsage().jvmType().getDescriptor();
        JvmMethodDefinition jvmMethodDefinition =
            new JvmMethodDefinition(
                getInternalName(), property.getterName(resolver), descriptor, false, false);
        InternalMethodDefinition getter =
            new InternalMethodDefinition(
                property.getterName(resolver),
                Collections.emptyList(),
                property.getTypeUsage(),
                jvmMethodDefinition);
        registerMethod(getter);
      }
      {
        String descriptor = "(" + property.getTypeUsage().jvmType().getDescriptor() + ")V";
        JvmMethodDefinition jvmMethodDefinition =
            new JvmMethodDefinition(
                getInternalName(), property.setterName(), descriptor, false, false);
        FormalParameterNode param =
            new FormalParameterNode(property.getTypeUsage().copy(), property.getName());
        param.setParent(this);
        InternalMethodDefinition setter =
            new InternalMethodDefinition(
                property.setterName(),
                ImmutableList.of(param),
                new VoidTypeUsageNode(),
                jvmMethodDefinition);
        registerMethod(setter);
      }
    }
  }

  public List<InternalConstructorDefinition> getConstructors() {
    if (constructors == null) {
      initializeConstructors(symbolResolver());
    }
    return constructors;
  }

  public InternalConstructorDefinition getOnlyConstructor(SymbolResolver resolver) {
    if (constructors == null) {
      initializeConstructors(resolver);
    }
    if (constructors.size() != 1) {
      throw new IllegalStateException();
    }
    return constructors.get(0);
  }

  private void initializeImplicitConstructor(SymbolResolver resolver) {
    List<? extends FormalParameter> inheritedParams = Collections.emptyList();
    if (getBaseType().isPresent()) {
      List<InternalConstructorDefinition> constructors =
          getBaseType().get().asReferenceTypeUsage().getTypeDefinition().getConstructors();
      if (constructors.size() != 1) {
        throw new UnsupportedOperationException();
      }
      inheritedParams = constructors.get(0).getFormalParameters();
    }

    List<FormalParameterNode> newParams =
        this.assignableProperties(resolver)
            .stream()
            .map(
                (p) ->
                    new FormalParameterNode(
                        p.getTypeUsage().copy(), p.getName(), p.getDefaultValue()))
            .collect(Collectors.toList());
    List<FormalParameter> allParams = new LinkedList<>();
    allParams.addAll(inheritedParams);
    allParams.addAll(newParams);
    allParams.sort(
        new Comparator<FormalParameter>() {
          @Override
          public int compare(FormalParameter o1, FormalParameter o2) {
            return Boolean.compare(o1.hasDefaultValue(), o2.hasDefaultValue());
          }
        });
    for (FormalParameter p : allParams) {
      // needed to solve symbols
      if (p.isNode()) {
        p.asNode().setParent(this);
      }
    }
    addConstructorWithParams(allParams, resolver);
  }

  private void initializeConstructors(SymbolResolver resolver) {
    constructors = new ArrayList<>();
    if (getExplicitConstructors().isEmpty()) {
      initializeImplicitConstructor(resolver);
    } else {
      if (getExplicitConstructors().size() > 1) {
        throw new IllegalStateException();
      }
      getExplicitConstructors().forEach((c) -> initializeExplicitConstructor(c, resolver));
    }
  }

  private void addConstructorWithParams(
      List<? extends FormalParameter> allParams, SymbolResolver resolver) {
    List<FormalParameter> paramsWithoutDefaultValues =
        allParams
            .stream()
            .filter((p) -> !p.hasDefaultValue())
            .collect(Collectors.<FormalParameter>toList());
    List<String> paramSignatures =
        paramsWithoutDefaultValues
            .stream()
            .map((p) -> p.getType().jvmType().getSignature())
            .collect(Collectors.toList());
    boolean hasDefaultParameters =
        allParams.stream().filter((p) -> p.hasDefaultValue()).findFirst().isPresent();
    if (hasDefaultParameters) {
      paramSignatures.add("Ljava/util/Map;");
    }
    JvmConstructorDefinition constructorDefinition =
        new JvmConstructorDefinition(
            jvmType().getInternalName(), "(" + String.join("", paramSignatures) + ")V");
    constructors.add(
        new InternalConstructorDefinition(
            new ReferenceTypeUsage(this), allParams, constructorDefinition));
  }

  private void initializeExplicitConstructor(
      TurinTypeContructorDefinitionNode constructor, SymbolResolver resolver) {
    List<? extends FormalParameter> allParams = constructor.getParameters();
    List<FormalParameter> paramsWithoutDefaultValues =
        allParams
            .stream()
            .filter((p) -> !p.hasDefaultValue())
            .collect(Collectors.<FormalParameter>toList());
    List<String> paramSignatures =
        paramsWithoutDefaultValues
            .stream()
            .map((p) -> p.getType().jvmType().getSignature())
            .collect(Collectors.toList());
    boolean hasDefaultParameters =
        allParams.stream().filter((p) -> p.hasDefaultValue()).findFirst().isPresent();
    if (hasDefaultParameters) {
      paramSignatures.add("Ljava/util/Map;");
    }
    JvmConstructorDefinition constructorDefinition =
        new JvmConstructorDefinition(
            jvmType().getInternalName(), "(" + String.join("", paramSignatures) + ")V");
    constructors.add(
        new InternalConstructorDefinition(
            new ReferenceTypeUsage(this), allParams, constructorDefinition));
  }

  private void ensureIsInitialized(SymbolResolver resolver) {
    if (constructors == null) {
      initializeConstructors(resolver);
    }
    if (methodsByName == null) {
      initializeMethodsByName(resolver);
    }
  }

  @Override
  public JvmMethodDefinition findMethodFor(
      String methodName, List<JvmType> actualParams, boolean staticContext) {
    ensureIsInitialized(symbolResolver());
    List<InternalMethodDefinition> methods = methodsByName.get(methodName);
    if (methods.size() == 0) {
      throw new IllegalArgumentException("No method found with name " + methodName);
    } else if (methods.size() == 1) {
      if (methods.get(0).matchJvmTypes(symbolResolver(), actualParams)) {
        return methods.get(0).getJvmMethodDefinition();
      } else {
        throw new IllegalArgumentException(
            "No method found with name " + methodName + " which matches " + actualParams);
      }
    } else {
      throw new IllegalStateException("No overloaded methods should be present in Turin types");
    }
  }

  private String getInternalName() {
    return JvmNameUtils.canonicalToInternal(getQualifiedName());
  }

  public void add(PropertyDefinition propertyDefinition) {
    if (propertyDefinition.getType().getParent() != propertyDefinition
        && propertyDefinition.getType().getParent().getParent() == null) {
      throw new IllegalArgumentException();
    }
    members.add(propertyDefinition);
    propertyDefinition.parent = this;
  }

  public TurinTypeDefinition(String name) {
    super(name);
  }

  /** Properties which can be referred to in the constructor */
  public List<Property> assignableProperties(SymbolResolver resolver) {
    return getDirectProperties(resolver)
        .stream()
        .filter((p) -> !p.hasInitialValue())
        .collect(Collectors.toList());
  }

  public List<Property> propertiesAppearingInDefaultConstructor(SymbolResolver resolver) {
    return getDirectProperties(resolver)
        .stream()
        .filter((p) -> !p.hasInitialValue() && !p.hasDefaultValue())
        .collect(Collectors.toList());
  }

  public List<Property> defaultPropeties(SymbolResolver resolver) {
    return getDirectProperties(resolver)
        .stream()
        .filter((p) -> p.hasDefaultValue())
        .collect(Collectors.toList());
  }

  public boolean hasDefaultProperties(SymbolResolver resolver) {
    return getDirectProperties(resolver)
        .stream()
        .filter((p) -> p.hasDefaultValue())
        .findFirst()
        .isPresent();
  }

  @Override
  public JvmConstructorDefinition resolveConstructorCall(List<ActualParam> actualParams) {
    // all named parameters should be after the named ones
    if (!ParamUtils.verifyOrder(actualParams)) {
      throw new IllegalArgumentException(
          "Named params should all be grouped after the positional ones");
    }

    ensureIsInitialized(symbolResolver());
    Optional<InternalConstructorDefinition> constructor =
        constructors.stream().filter((c) -> c.match(symbolResolver(), actualParams)).findFirst();

    if (!constructor.isPresent()) {
      throw new UnsolvedConstructorException(getQualifiedName(), actualParams);
    }

    return constructor.get().getJvmConstructorDefinition();
  }

  @Override
  public TypeUsageNode getFieldType(String fieldName, boolean staticContext) {
    for (Property property : getAllProperties(symbolResolver())) {
      if (property.getName().equals(fieldName)) {
        return property.getTypeUsage();
      }
    }
    throw new IllegalArgumentException(fieldName);
  }

  @Override
  public List<ReferenceTypeUsage> getAllAncestors() {
    if (getBaseType().isPresent()) {
      List<ReferenceTypeUsage> res = new ArrayList<>();
      res.add(getBaseType().get().asReferenceTypeUsage());
      res.addAll(getBaseType().get().asReferenceTypeUsage().getAllAncestors());
      return res;
    }
    return ImmutableList.of(ReferenceTypeUsage.OBJECT(symbolResolver()));
  }

  @Override
  public boolean isInterface() {
    // TODO when it will be possible to declare interface fix this
    return false;
  }

  @Override
  public boolean isClass() {
    // TODO when it will be possible to declare interface fix this
    return true;
  }

  @Override
  public Optional<InternalMethodDefinition> findMethod(
      String methodName, List<ActualParam> actualParams, boolean staticContext) {
    // all named parameters should be after the named ones
    if (!ParamUtils.verifyOrder(actualParams)) {
      throw new IllegalArgumentException(
          "Named params should all be grouped after the positional ones");
    }

    ensureIsInitialized(symbolResolver());
    if (!methodsByName.containsKey(methodName)) {
      return Optional.empty();
    }
    Optional<InternalMethodDefinition> method =
        methodsByName
            .get(methodName)
            .stream()
            .filter((m) -> m.match(symbolResolver(), actualParams))
            .findFirst();
    return method;
  }

  @Override
  public boolean hasField(String name, boolean staticContext) {
    throw new UnsupportedOperationException();
  }

  public void add(PropertyReference propertyReference) {
    members.add(propertyReference);
    propertyReference.parent = this;
  }

  @Override
  public String toString() {
    return "TypeDefinition{" + "name='" + name + '\'' + ", members=" + members + '}';
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    TurinTypeDefinition that = (TurinTypeDefinition) o;

    if (!members.equals(that.members)) return false;
    if (!name.equals(that.name)) return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = name.hashCode();
    result = 31 * result + members.hashCode();
    return result;
  }

  @Override
  public Optional<Symbol> findSymbol(String name, SymbolResolver resolver) {
    // TODO support references to methods
    for (Property property : this.getAllProperties(resolver)) {
      if (property.getName().equals(name)) {
        return Optional.of(property);
      }
    }

    return super.findSymbol(name, resolver);
  }

  /** Does it override the toString method defined in Object? */
  public boolean defineMethodToString(SymbolResolver resolver) {
    return isDefiningMethod("toString", Collections.emptyList(), resolver);
  }

  /** Does it override the hashCode method defined in Object? */
  public boolean defineMethodHashCode(SymbolResolver resolver) {
    return isDefiningMethod("hashCode", Collections.emptyList(), resolver);
  }

  /** Does it override the equals method defined in Object? */
  public boolean defineMethodEquals(SymbolResolver resolver) {
    return isDefiningMethod(
        "equals", ImmutableList.of(ReferenceTypeUsage.OBJECT(resolver)), resolver);
  }

  private boolean isDefiningMethod(
      String name, List<TypeUsage> paramTypes, SymbolResolver resolver) {
    return getDirectMethods()
            .stream()
            .filter((m) -> m.getName().equals(name))
            .filter(
                (m) ->
                    m.getParameters()
                        .stream()
                        .map((p) -> p.calcType().jvmType())
                        .collect(Collectors.toList())
                        .equals(
                            paramTypes
                                .stream()
                                .map((p) -> p.jvmType())
                                .collect(Collectors.toList())))
            .count()
        > 0;
  }

  public List<AnnotationUsage> getAnnotations() {
    return annotations;
  }

  @Override
  public Iterable<Node> getChildren() {
    List<Node> children = new LinkedList<>();
    children.addAll(members);
    children.addAll(annotations);
    if (baseType.isPresent()) {
      children.add(baseType.get());
    }
    children.addAll(interfaces);
    return children;
  }

  public List<Property> getDirectProperties(SymbolResolver resolver) {
    List<Property> properties = new ArrayList<>();
    for (Node member : members) {
      if (member instanceof PropertyDefinition) {
        properties.add(Property.fromDefinition((PropertyDefinition) member));
      } else if (member instanceof PropertyReference) {
        properties.add(Property.fromReference((PropertyReference) member, resolver));
      }
    }
    return properties;
  }

  public List<TurinTypeMethodDefinitionNode> getDirectMethods() {
    List<TurinTypeMethodDefinitionNode> methods = new ArrayList<>();
    for (Node member : members) {
      if (member instanceof TurinTypeMethodDefinitionNode) {
        methods.add((TurinTypeMethodDefinitionNode) member);
      }
    }
    return methods;
  }

  public List<TypeUsageNode> getInterfaces() {
    return interfaces;
  }

  public Optional<TypeUsageNode> getBaseType() {
    return baseType;
  }

  /** Get direct and inherited properties. */
  public List<Property> getAllProperties(SymbolResolver resolver) {
    // TODO consider also inherited properties
    return getDirectProperties(resolver);
  }

  public void add(TurinTypeMethodDefinitionNode methodDefinition) {
    members.add(methodDefinition);
    methodDefinition.parent = this;
  }

  public void add(TurinTypeContructorDefinitionNode contructorDefinition) {
    members.add(contructorDefinition);
    contructorDefinition.parent = this;
  }

  @Override
  public boolean canFieldBeAssigned(String field) {
    return true;
  }

  public boolean defineExplicitConstructor(SymbolResolver resolver) {
    return !getExplicitConstructors().isEmpty();
  }

  @Override
  public TypeDefinition getSuperclass() {
    if (this.baseType.isPresent()) {
      return this.baseType.get().asReferenceTypeUsage().getTypeDefinition();
    }
    return ReflectionTypeDefinitionFactory.getInstance()
        .getTypeDefinition(Object.class, symbolResolver());
  }

  @Override
  public <T extends TypeUsage> Map<String, TypeUsage> associatedTypeParametersToName(
      List<T> typeParams) {
    return Collections.emptyMap();
  }

  @Override
  public Optional<InternalConstructorDefinition> findConstructor(List<ActualParam> actualParams) {
    ensureIsInitialized(symbolResolver());
    for (InternalConstructorDefinition constructor : constructors) {
      if (constructor.match(symbolResolver(), actualParams)) {
        return Optional.of(constructor);
      }
    }
    return Optional.empty();
  }

  @Override
  public Optional<Invokable> getMethod(
      String method, boolean staticContext, Map<String, TypeUsage> typeParams) {
    ensureIsInitialized(symbolResolver());
    Set<InternalMethodDefinition> methods = Collections.emptySet();
    if (methodsByName.containsKey(method)) {
      methods =
          methodsByName
              .get(method)
              .stream()
              .filter((m) -> m.getJvmMethodDefinition().isStatic() == staticContext)
              .collect(Collectors.toSet());
    }
    if (methods.isEmpty()) {
      return Optional.empty();
    } else {
      return Optional.of(new MethodSetAsInvokableType(methods, typeParams));
    }
  }
}