/*
 * 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.
 *
 * Copyright 2012-2018 the original author or authors.
 */
package org.assertj.core.api;

import static net.bytebuddy.matcher.ElementMatchers.nameContainsIgnoreCase;
import static net.bytebuddy.matcher.ElementMatchers.named;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.TypeCache;
import net.bytebuddy.TypeCache.SimpleKey;
import net.bytebuddy.TypeCache.Sort;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatcher.Junction;
import net.bytebuddy.matcher.ElementMatchers;

class SoftProxies {

  private final ErrorCollector collector = new ErrorCollector();
  private final TypeCache<TypeCache.SimpleKey> cache = new TypeCache.WithInlineExpunction<>(Sort.SOFT);

  void collectError(Throwable error) {
    collector.addError(error);
  }

  List<Throwable> errorsCollected() {
    return collector.errors();
  }

  @SuppressWarnings("unchecked")
  <V, T> V create(final Class<V> assertClass, Class<T> actualClass, T actual) {

    try {
      ClassLoader classLoader = getClass().getClassLoader();
      SimpleKey key = new SimpleKey(assertClass);
      Class<V> proxyClass = (Class<V>) cache.findOrInsert(classLoader, key, () -> createProxy(assertClass, collector));

      Constructor<? extends V> constructor = proxyClass.getConstructor(actualClass);
      return constructor.newInstance(actual);
    } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
      throw new RuntimeException(e);
    }
  }

  Object createIterableSizeAssertProxy(IterableSizeAssert<?> iterableSizeAssert) {
    Class<?> proxyClass = createProxy(IterableSizeAssert.class, collector);
    try {
      Constructor<?> constructor = proxyClass.getConstructor(AbstractIterableAssert.class, Integer.class);
      return constructor.newInstance(iterableSizeAssert.returnToIterable(), iterableSizeAssert.actual);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  private <V> Class<?> createProxy(Class<V> assertClass, ErrorCollector collector) {
    // TODO use named
    Junction<MethodDescription> specialMethods = ElementMatchers.<MethodDescription> nameContainsIgnoreCase("extracting")
                                                                .or(nameContainsIgnoreCase("filteredOn"))
                                                                .or(nameContainsIgnoreCase("map"))
                                                                .or(nameContainsIgnoreCase("asString"))
                                                                .or(nameContainsIgnoreCase("asList"))
                                                                .or(named("size"))
                                                                .or(named("toAssert"))
                                                                .or(nameContainsIgnoreCase("flatMap"))
                                                                .or(nameContainsIgnoreCase("flatExtracting"));

    return new ByteBuddy().subclass(assertClass)
                          .method(specialMethods)
                          .intercept(MethodDelegation.to(new ProxifyMethodChangingTheObjectUnderTest(this)))
                          .method(ElementMatchers.<MethodDescription> any().and(ElementMatchers.not(specialMethods)))
                          .intercept(MethodDelegation.to(collector))
                          .make()
                          .load(getClass().getClassLoader())
                          .getLoaded();
  }

  public boolean wasSuccess() {
    return collector.wasSuccess();
  }


}

@@ -635,84 +635,8 @@
 i;%0A%0A
-import static net.bytebuddy.matcher.ElementMatchers.nameContainsIgnoreCase;%0A
 impo
@@ -2700,30 +2700,8 @@
 ) %7B%0A
-    // TODO use named%0A
     
@@ -2785,34 +2785,17 @@
 on%3E name
-ContainsIgnoreCase
+d
 (%22extrac
@@ -2873,34 +2873,106 @@
 .or(name
-ContainsIgnoreCase
+d(%22filteredOn%22))%0A                                                                .or(named
 (%22filter
@@ -2967,32 +2967,36 @@
 amed(%22filteredOn
+Null
 %22))%0A            
@@ -3055,34 +3055,17 @@
 .or(name
-ContainsIgnoreCase
+d
 (%22map%22))
@@ -3137,34 +3137,17 @@
 .or(name
-ContainsIgnoreCase
+d
 (%22asStri
@@ -3224,34 +3224,17 @@
 .or(name
-ContainsIgnoreCase
+d
 (%22asList
@@ -3479,34 +3479,17 @@
 .or(name
-ContainsIgnoreCase
+d
 (%22flatMa
@@ -3569,26 +3569,106 @@
 name
-ContainsIgnoreCase
+d(%22extractingResultOf%22))%0A                                                                .or(named
 (%22fl

Use exact method names to trigger ProxifyMethodChangingTheObjectUnderTest