/*
 * Configurate
 * Copyright (C) zml and Configurate contributors
 *
 * 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 ninja.leaping.configurate.objectmapping.serialize;

import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeToken;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * A calculated collection of {@link TypeSerializer}s
 */
public class TypeSerializerCollection {
    private final TypeSerializerCollection parent;
    private final SerializerList serializers = new SerializerList();
    private final Map<TypeToken<?>, TypeSerializer<?>> typeMatches = new ConcurrentHashMap<>();

    TypeSerializerCollection(TypeSerializerCollection parent) {
        this.parent = parent;
    }

    @SuppressWarnings("unchecked")
    public <T> TypeSerializer<T> get(TypeToken<T> type) {
        Preconditions.checkNotNull(type, "type");
        type = type.wrap();

        TypeSerializer<?> serial = typeMatches.computeIfAbsent(type, serializers);
        if (serial == null && parent != null) {
            serial = parent.get(type);
        }

        return (TypeSerializer) serial;
    }

    /**
     * Register a type serializer for a given type. Serializers registered will match all subclasses of the provided
     * type, as well as unwrapped primitive equivalents of the type.
     *
     * @param type The type to accept
     * @param serializer The serializer that will be serialized with
     * @param <T> The type to generify around
     * @return this
     */
    public <T> TypeSerializerCollection registerType(TypeToken<T> type, TypeSerializer<? super T> serializer) {
        Preconditions.checkNotNull(type, "type");
        Preconditions.checkNotNull(serializer, "serializer");
        serializers.add(new RegisteredSerializer(type, serializer));
        typeMatches.clear();
        return this;
    }

    /**
     * Register a type serializer matching against a given predicate.
     *
     * @param test The predicate to match types against
     * @param serializer The serializer to serialize matching types with
     * @param <T> The type parameter
     * @return this
     */
    @SuppressWarnings("unchecked")
    public <T> TypeSerializerCollection registerPredicate(Predicate<TypeToken<T>> test, TypeSerializer<? super T>
            serializer) {
        Preconditions.checkNotNull(test, "test");
        Preconditions.checkNotNull(serializer, "serializer");
        serializers.add(new RegisteredSerializer((Predicate) test, serializer));
        typeMatches.clear();
        return this;
    }

    public TypeSerializerCollection newChild() {
        return new TypeSerializerCollection(this);
    }

    private static final class RegisteredSerializer {
        private final Predicate<TypeToken<?>> predicate;
        private final TypeSerializer<?> serializer;

        private RegisteredSerializer(Predicate<TypeToken<?>> predicate, TypeSerializer<?> serializer) {
            this.predicate = predicate;
            this.serializer = serializer;
        }

        private RegisteredSerializer(TypeToken<?> type, TypeSerializer<?> serializer) {
            this(type::isSupertypeOf, serializer);
        }
    }

    private final class SerializerList extends CopyOnWriteArrayList<RegisteredSerializer>
            implements Function<TypeToken<?>, TypeSerializer<?>> {

        @Override
        public TypeSerializer<?> apply(TypeToken<?> type) {
            for (RegisteredSerializer ent : this) {
                if (ent.predicate.test(type)) {
                    return ent.serializer;
                }
            }
            return null;
        }
    }

}

@@ -788,16 +788,75 @@
 ava.
-util.Map
+lang.reflect.Method;%0Aimport java.util.Map;%0Aimport java.util.Objects
 ;%0Aim
@@ -3003,28 +3003,16 @@
 super T%3E
-%0A           
  seriali
@@ -3845,39 +3845,582 @@
 his(
-type::isSupertypeOf, serializer
+new SuperTypePredicate(type), serializer);%0A        %7D%0A    %7D%0A%0A    /**%0A     * Effectively a predicate which is %3Ccode%3Etype::isSupertypeOf%3C/code%3E.%0A     *%0A     * %3Cp%3EThe isSupertypeOf method was only added in Guava 19.0, and was previously named%0A     * isAssignableFrom.%3C/p%3E%0A     */%0A    private static final class SuperTypePredicate implements Predicate%3CTypeToken%3C?%3E%3E %7B%0A        private static final Method SUPERTYPE_TEST;%0A        static %7B%0A            Method supertypeTest;%0A            try %7B%0A                supertypeTest = TypeToken.class.getMethod(%22isSupertypeOf%22, TypeToken.class
 );%0A 
@@ -4430,28 +4430,867 @@
     
+ 
-%7D%0A    %7D%0A%0A    private
+   %7D catch (NoSuchMethodException e1) %7B%0A                try %7B%0A                    supertypeTest = TypeToken.class.getMethod(%22isAssignableFrom%22, TypeToken.class);%0A                %7D catch (NoSuchMethodException e2) %7B%0A                    throw new RuntimeException(%22Unable to get TypeToken#isSupertypeOf or TypeToken#isAssignableFrom method%22);%0A                %7D%0A            %7D%0A            SUPERTYPE_TEST = supertypeTest;%0A        %7D%0A%0A        private final TypeToken%3C?%3E type;%0A%0A        SuperTypePredicate(TypeToken%3C?%3E type) %7B%0A            this.type = type;%0A        %7D%0A%0A        @Override%0A        public boolean test(TypeToken%3C?%3E t) %7B%0A            try %7B%0A                return (boolean) SUPERTYPE_TEST.invoke(type, t);%0A            %7D catch (Exception e) %7B%0A                e.printStackTrace();%0A                return false;%0A            %7D%0A        %7D%0A    %7D%0A%0A    private static
  fin
@@ -5363,28 +5363,16 @@
 ializer%3E
-%0A           
  impleme

Fix compatibility with older versions of guava