/*
 * 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.camp.brooklyn.spi.creation;

import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;

import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec;
import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind;
import org.apache.brooklyn.api.typereg.RegisteredType;
import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan;
import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext;
import org.apache.brooklyn.core.typereg.AbstractFormatSpecificTypeImplementationPlan;
import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer;
import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan;
import org.apache.brooklyn.core.typereg.RegisteredTypes;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;

import com.google.common.collect.ImmutableList;

public class CampTypePlanTransformer extends AbstractTypePlanTransformer {

    private static final List<String> FORMATS = ImmutableList.of("brooklyn-camp");
    // TODO any use in having these formats? if not, remove. Nov 2015.
    // , "camp", "brooklyn");
    
    public static final String FORMAT = FORMATS.get(0);
    
    public CampTypePlanTransformer() {
        super(FORMAT, "OASIS CAMP / Brooklyn", "The Apache Brooklyn implementation of the OASIS CAMP blueprint plan format and extensions");
    }

    @Override
    protected double scoreForNullFormat(Object planData, RegisteredType type, RegisteredTypeLoadingContext context) {
        double weight;
        if (type!=null) {
            if (type.getKind()==RegisteredTypeKind.SPEC) {
                weight = 1.0;
            } else if (type.getKind()==RegisteredTypeKind.UNRESOLVED) {
                // might be a template
                weight = 0.4;
            } else {
                return 0;
            }
        } else {
            // have a plan but no type, weaken slightly
            weight = 0.8;
        }
        
        Maybe<Map<?,?>> plan = RegisteredTypes.getAsYamlMap(planData);
        if (plan.isPresent()) {
            return weight * scoreObject(plan.get(), (map,s) -> map.containsKey(s));
        }
        if (planData==null) {
            return 0;
        }
        double unparseableScore = scoreObject(planData.toString(), (p,s) -> p.contains(s));
        if (unparseableScore>0) {
            return 0.5 + 0.25 * unparseableScore;
        }
        return 0;
    }
    
    protected <T> double scoreObject(T plan, BiFunction<T, String, Boolean> contains) {
        if (contains.apply(plan, "services")) return 0.8;
        if (contains.apply(plan, "type")) return 0.4;
        if (contains.apply(plan, "brooklyn.locations")) return 0.7;
        if (contains.apply(plan, "brooklyn.policies")) return 0.7;
        if (contains.apply(plan, "brooklyn.enrichers")) return 0.7;
        // score low so we can say it's the wrong place
        if (contains.apply(plan, "catalog.bom")) return 0.2;
        return 0;
    }

    @Override
    protected double scoreForNonmatchingNonnullFormat(String planFormat, Object planData, RegisteredType type, RegisteredTypeLoadingContext context) {
        if (type!=null && type.getKind()!=RegisteredTypeKind.SPEC) return 0;

        if (FORMATS.contains(planFormat.toLowerCase())) return 0.9;
        return 0;
    }

    @Override
    protected AbstractBrooklynObjectSpec<?, ?> createSpec(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception {
        try {
            return new CampResolver(mgmt, type, context).createSpec();
        } catch (Exception e) {
            Exceptions.propagateIfFatal(e);
            String message = null;
            // check a few common errors, annotate if so
            if (type==null || type.getPlan()==null || type.getPlan().getPlanData()==null) {
                // shouldn't happen
                message = "Type/plan/data is null when resolving CAMP blueprint";
            } else {
                Object planData = type.getPlan().getPlanData();
                if (RegisteredTypes.getAsYamlMap(planData).isAbsent()) {
                    message = "Type or plan is invalid YAML when resolving CAMP blueprint";
                } else if (planData.toString().contains("brooklyn.catalog")) {
                    message = "CAMP blueprint for type definition looks like a catalog file";
                } else {
                    // leave null, don't annotate
                }
            }
            if (message!=null) {
                throw Exceptions.propagateAnnotated(message, e);
            } else {
                throw e;
            }
        }
    }

    @Override
    protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception {
        // beans not supported by this?
        throw new IllegalStateException("beans not supported here");
    }

    @Override
    public double scoreForTypeDefinition(String formatCode, Object catalogData) {
        // TODO catalog parsing
        return 0;
    }

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

    public static class CampTypeImplementationPlan extends AbstractFormatSpecificTypeImplementationPlan<String> {
        public CampTypeImplementationPlan(TypeImplementationPlan otherPlan) {
            super(FORMATS.get(0), String.class, otherPlan);
        }
        public CampTypeImplementationPlan(String planData) {
            this(new BasicTypeImplementationPlan(FORMATS.get(0), planData));
        }
    }
}
