package com.wishtack.pysynthetic.impl;

import com.jetbrains.python.psi.PyCallable;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyParameter;
import com.jetbrains.python.psi.PyPossibleClassMember;
import com.jetbrains.python.psi.types.*;
import com.wishtack.pysynthetic.SyntheticTypeInfo;
import com.wishtack.pysynthetic.SyntheticTypeInfoReader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by Jean Hominal on 2016-11-05.
 */
public class SyntheticTypeProvider extends PyTypeProviderBase {

    @Nullable
    @Override
    public PyType getCallableType(@NotNull PyCallable callable, @NotNull TypeEvalContext context) {
        if (!(callable instanceof PyPossibleClassMember)) {
            return null;
        }
        PyClass pyClass = ((PyPossibleClassMember)callable).getContainingClass();
        if (pyClass == null) {
            return null;
        }

        SyntheticTypeInfo info = new SyntheticTypeInfoReader(pyClass).read();
        if (info.hasSyntheticConstructor() && callable.equals(pyClass.findInitOrNew(false, null))) {
            PyParameter[] originalParameters = callable.getParameterList().getParameters();

            List<PyCallableParameter> allInitParameters =
                Stream.concat(Arrays.stream(originalParameters), info.getSyntheticInitParameters().stream())
                    .map(PyCallableParameterImpl::new)
                    .collect(Collectors.toList());

            return new PyCallableTypeImpl(allInitParameters, context.getReturnType(callable));
        }

        return super.getCallableType(callable, context);
    }
}

@@ -69,159 +69,9 @@
 psi.
-PyCallable;%0Aimport com.jetbrains.python.psi.PyClass;%0Aimport com.jetbrains.python.psi.PyParameter;%0Aimport com.jetbrains.python.psi.PyPossibleClassMember
+*
 ;%0Aim
@@ -325,17 +325,20 @@
 il.Array
-s
+List
 ;%0Aimport
@@ -348,20 +348,27 @@
 va.util.
-List
+Collections
 ;%0Aimport
@@ -382,25 +382,15 @@
 til.
-stream.Collectors
+HashSet
 ;%0Aim
@@ -408,21 +408,12 @@
 til.
+Li
 st
-ream.Stream
 ;%0A%0A/
@@ -1169,17 +1169,16 @@
 ters();%0A
-%0A
         
@@ -1192,155 +1192,1390 @@
 t%3CPy
-CallableParameter%3E allInitParameters =%0A                Stream.concat(Arrays.stream(originalParameters), info.getSyntheticInitParameters().stream
+NamedParameter%3E syntheticParameters = info.getSyntheticInitParameters();%0A%0A            // Get the index of the **kwargs arguments, so that synthetic parameters%0A            // can be inserted before it.%0A            int originalKeywordContainerIndex = originalParameters.length;%0A            for (int i = 0; i %3C originalParameters.length; i++) %7B%0A                PyNamedParameter namedParameter = originalParameters%5Bi%5D.getAsNamed();%0A                if (namedParameter != null && namedParameter.isKeywordContainer()) %7B%0A                    originalKeywordContainerIndex = i;%0A                    break;%0A                %7D%0A            %7D%0A%0A            // Collect argument names in order to avoid producing duplicates.%0A            // Synthetic parameters lose to any parameter named in the original __init__.%0A            // This does not prevent duplicates if the original definition contains them, but that%0A            // does not really matter as the IDE will complain loudly in that case anyway.%0A            HashSet%3CString%3E unavailableParameterNames = new HashSet%3C%3E(originalParameters.length);%0A            for (PyParameter originalParameter : originalParameters) %7B%0A                unavailableParameterNames.add(originalParameter.getName());%0A            %7D%0A%0A            ArrayList%3CPyCallableParameter%3E allInitParameters = new ArrayList%3C%3E(originalParameters.length + syntheticParameters.size
 ())
+;%0A
 %0A   
@@ -2587,50 +2587,156 @@
     
-        .map(PyCallableParameterImpl::new)
+for (int i = 0; i %3C originalKeywordContainerIndex; i++) %7B%0A                allInitParameters.add(new PyCallableParameterImpl(originalParameters%5Bi%5D));
 %0A   
@@ -2732,32 +2732,39 @@
 s%5Bi%5D));%0A        
+    %7D%0A%0A
             .col
@@ -2763,38 +2763,480 @@
     
-.collect(Collectors.toList());
+for (PyNamedParameter syntheticParameter : syntheticParameters) %7B%0A                if (unavailableParameterNames.add(syntheticParameter.getName())) %7B%0A                    allInitParameters.add(new PyCallableParameterImpl(syntheticParameter));%0A                %7D%0A            %7D%0A%0A            for (int i = originalKeywordContainerIndex; i %3C originalParameters.length; i++) %7B%0A                allInitParameters.add(new PyCallableParameterImpl(originalParameters%5Bi%5D));%0A            %7D
 %0A%0A  
@@ -3275,16 +3275,45 @@
 ypeImpl(
+Collections.unmodifiableList(
 allInitP
@@ -3321,16 +3321,17 @@
 rameters
+)
 , contex

Fix parameter ordering in synthetic init when overriding parameters.