/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * 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 android.hardware.camera2.marshal;

import android.annotation.Nullable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.utils.TypeReference;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * Registry of supported marshalers; add new query-able marshalers or lookup existing ones.</p>
 */
public class MarshalRegistry {

    /**
     * Register a marshal queryable for the managed type {@code T}.
     *
     * <p>Multiple marshal queryables for the same managed type {@code T} may be registered;
     * this is desirable if they support different native types (e.g. marshaler 1 supports
     * {@code Integer <-> TYPE_INT32}, marshaler 2 supports {@code Integer <-> TYPE_BYTE}.</p>
     *
     * @param queryable a non-{@code null} marshal queryable that supports marshaling {@code T}
     */
    public static <T> void registerMarshalQueryable(MarshalQueryable<T> queryable) {
        synchronized(sMarshalLock) {
            sRegisteredMarshalQueryables.add(queryable);
        }
    }

    /**
     * Lookup a marshaler between {@code T} and {@code nativeType}.
     *
     * <p>Marshalers are looked up in the order they were registered; earlier registered
     * marshal queriers get priority.</p>
     *
     * @param typeToken The compile-time type reference for {@code T}
     * @param nativeType The native type, e.g. {@link CameraMetadataNative#TYPE_BYTE TYPE_BYTE}
     * @return marshaler a non-{@code null} marshaler that supports marshaling the type combo
     *
     * @throws UnsupportedOperationException If no marshaler matching the args could be found
     */
    @SuppressWarnings("unchecked")
    public static <T> Marshaler<T> getMarshaler(TypeReference<T> typeToken, int nativeType) {
        synchronized(sMarshalLock) {
            // TODO: can avoid making a new token each time by code-genning
            // the list of type tokens and native types from the keys (at the call sites)
            MarshalToken<T> marshalToken = new MarshalToken<T>(typeToken, nativeType);

            /*
             * Marshalers are instantiated lazily once they are looked up; successive lookups
             * will not instantiate new marshalers.
             */
            Marshaler<T> marshaler =
                    (Marshaler<T>) sMarshalerMap.get(marshalToken);

            if (marshaler == null) {

                if (sRegisteredMarshalQueryables.size() == 0) {
                    throw new AssertionError("No available query marshalers registered");
                }

                // Query each marshaler to see if they support the native/managed type combination
                for (MarshalQueryable<?> potentialMarshaler : sRegisteredMarshalQueryables) {

                    MarshalQueryable<T> castedPotential =
                            (MarshalQueryable<T>)potentialMarshaler;

                    if (castedPotential.isTypeMappingSupported(typeToken, nativeType)) {
                        marshaler = castedPotential.createMarshaler(typeToken, nativeType);
                        break;
                    }
                }

                if (marshaler == null) {
                    throw new UnsupportedOperationException(
                        "Could not find marshaler that matches the requested " +
                        "combination of type reference " +
                        typeToken + " and native type " +
                        MarshalHelpers.toStringNativeType(nativeType));
                }

                // Only put when no cached version exists to avoid +0.5ms lookup per call.
                sMarshalerMap.put(marshalToken, marshaler);
            }

            return marshaler;
        }
    }

    private static class MarshalToken<T> {
        public MarshalToken(TypeReference<T> typeReference, int nativeType) {
            this.typeReference = typeReference;
            this.nativeType = nativeType;
            this.hash = typeReference.hashCode() ^ nativeType;
        }

        final TypeReference<T> typeReference;
        final int nativeType;
        private final int hash;

        @Override
        public boolean equals(@Nullable Object other) {
            if (other instanceof MarshalToken<?>) {
                MarshalToken<?> otherToken = (MarshalToken<?>)other;
                return typeReference.equals(otherToken.typeReference) &&
                        nativeType == otherToken.nativeType;
            }

            return false;
        }

        @Override
        public int hashCode() {
            return hash;
        }
    }

    // Control access to the static data structures below
    private static final Object sMarshalLock = new Object();

    private static final List<MarshalQueryable<?>> sRegisteredMarshalQueryables =
            new ArrayList<MarshalQueryable<?>>();
    private static final HashMap<MarshalToken<?>, Marshaler<?>> sMarshalerMap =
            new HashMap<MarshalToken<?>, Marshaler<?>>();

    private MarshalRegistry() {
        throw new AssertionError();
    }
}
