/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.brooklyn.core.catalog.internal;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec;
import org.apache.brooklyn.api.typereg.RegisteredType;
import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext;
import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer;
import org.apache.brooklyn.core.typereg.JavaClassNameTypePlanTransformer;
import org.apache.brooklyn.core.typereg.TypePlanTransformers;
import org.apache.brooklyn.util.text.Identifiers;

/**
 * Allows a caller to register a spec (statically) and get a UID for it --
 * <pre> {@code
 * String specId = StaticTypePlanTransformer.registerSpec(EntitySpec.create(BasicEntity.class));
 * }</pre>
 * and then build a plan referring to that type name, such as:
 * <pre> {@code
 *  brooklyn.catalog:
 *    id: test.inputs
 *    version: "0.0.1"
 *    itemType: entity
 *    item: <specId>
 * } </pre>
 * <p>
 * For use when testing type plan resolution. 
 * <p>
 * This is different to {@link JavaClassNameTypePlanTransformer} as that one
 * does a <code>Class.forName(typeName)</code> to create specs, and this one uses a static registry.
 * <p>
 * Use {@link #forceInstall()} to set up and {@link #clearForced()} after use (in a finally or "AfterTest" block)
 * to prevent interference with other tests.
 */
public class StaticTypePlanTransformer extends AbstractTypePlanTransformer {
    
    public static final String FORMAT = "static-types";
    
    public StaticTypePlanTransformer() {
        super(FORMAT, "Static Type", "Static transformer for use in tests");
    }

    private static final Map<String, AbstractBrooklynObjectSpec<?, ?>> REGISTERED_SPECS = new ConcurrentHashMap<>();

    public static void forceInstall() {
        TypePlanTransformers.forceAvailable(StaticTypePlanTransformer.class, JavaClassNameTypePlanTransformer.class);
    }
    
    public static void clearForced() {
        TypePlanTransformers.clearForced();
        REGISTERED_SPECS.clear();
    }
    
    public static String registerSpec(AbstractBrooklynObjectSpec<?, ?> spec) {
        String id = Identifiers.makeRandomId(10);
        REGISTERED_SPECS.put(id, spec);
        return id;
    }

    @Override
    public double scoreForTypeDefinition(String formatCode, Object catalogData) {
        // not supported
        return 0;
    }

    @Override
    public List<RegisteredType> createFromTypeDefinition(String formatCode, Object catalogData) {
        // not supported
        return null;
    }

    @Override
    protected double scoreForNullFormat(Object planData, RegisteredType type, RegisteredTypeLoadingContext context) {
        if (REGISTERED_SPECS.containsKey(type.getId())) return 1;
        if (REGISTERED_SPECS.containsKey(planData)) return 1;
        return 0;
    }

    @Override
    protected double scoreForNonmatchingNonnullFormat(String planFormat, Object planData, RegisteredType type, RegisteredTypeLoadingContext context) {
        // not supported
        return 0;
    }

    @Override
    protected AbstractBrooklynObjectSpec<?, ?> createSpec(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception {
        if (REGISTERED_SPECS.containsKey(type.getSymbolicName()))
            return get(type.getSymbolicName());
        if (type.getPlan().getPlanData()!=null && REGISTERED_SPECS.containsKey(type.getPlan().getPlanData()))
            return get((String)type.getPlan().getPlanData());
        return null;
    }

    @Override
    protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception {
        // not supported
        return null;
    }

    public static AbstractBrooklynObjectSpec<?, ?> get(String typeName) {
        return REGISTERED_SPECS.get(typeName);
    }


}
