/*
 * 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.plan;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.apache.brooklyn.api.framework.FrameworkLookup;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer;
import org.apache.brooklyn.core.typereg.TypePlanTransformers;
import org.apache.brooklyn.core.typereg.UnsupportedTypePlanException;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.text.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;

/** @deprecated since 0.9.0 use {@link TypePlanTransformers} as part of switch to {@link BrooklynTypePlanTransformer};
 * mark transformers as deprecated if there is a preferred corresponding {@link BrooklynTypePlanTransformer} */
@Deprecated 
public class PlanToSpecFactory {
    
    private static final Logger log = LoggerFactory.getLogger(PlanToSpecFactory.class);

    private static Collection<PlanToSpecTransformer> getAll(boolean includeDeprecated) {
        Iterable<PlanToSpecTransformer> transformers = FrameworkLookup.lookupAll(PlanToSpecTransformer.class);
        return ImmutableList.copyOf(includeDeprecated ? transformers : filterDeprecated(transformers));
    }

    private static Iterable<PlanToSpecTransformer> filterDeprecated(Iterable<PlanToSpecTransformer> transformers) {
        List<PlanToSpecTransformer> result = MutableList.of();
        for (PlanToSpecTransformer t: transformers) {
            if (!isDeprecated(t.getClass())) {
                result.add(t);
            }
        }
        return result;
    }

    private static boolean isDeprecated(Class<? extends PlanToSpecTransformer> c) {
        return (c.getAnnotation(Deprecated.class)!=null);
    }

    private static Collection<Class<? extends PlanToSpecTransformer>> OVERRIDE;
    @SafeVarargs
    @VisibleForTesting
    public synchronized static void forceAvailable(Class<? extends PlanToSpecTransformer> ...classes) {
        OVERRIDE = Arrays.asList(classes);
    }
    public synchronized static void clearForced() {
        OVERRIDE = null;
    }

    public static Collection<PlanToSpecTransformer> all(ManagementContext mgmt) {
        return all(mgmt, true);
    }
    public static Collection<PlanToSpecTransformer> all(ManagementContext mgmt, boolean includeSuperseded) {
        Collection<Class<? extends PlanToSpecTransformer>> override = OVERRIDE;
        Collection<PlanToSpecTransformer> result = new ArrayList<PlanToSpecTransformer>();
        if (override!=null) {
            for (Class<? extends PlanToSpecTransformer> o1: override) {
                try {
                    if (includeSuperseded || !isDeprecated(o1))
                        result.add(o1.newInstance());
                } catch (Exception e) {
                    Exceptions.propagate(e);
                }
            }
        } else {
            result.addAll(getAll(includeSuperseded));
        }
        for(PlanToSpecTransformer t : result) {
            t.setManagementContext(mgmt);
        }
        return result;
    }

    @Beta
    public static PlanToSpecTransformer forPlanType(ManagementContext mgmt, String planType) {
        Collection<PlanToSpecTransformer> transformers = all(mgmt);
        for (PlanToSpecTransformer transformer : transformers) {
            if (transformer.accepts(planType)) {
                return transformer;
            }
        }
        throw new IllegalStateException("PlanToSpecTransformer for plan type " + planType + " not found. Registered transformers are: " + transformers);
    }
    
    // TODO primitive loading mechanism, just tries all in order; we'll want something better as we get more plan transformers 
    @Beta
    public static <T> Maybe<T> attemptWithLoaders(ManagementContext mgmt, boolean includeDeprecated, Function<PlanToSpecTransformer,T> f) {
        return attemptWithLoaders(all(mgmt, includeDeprecated), f);
    }
    
    public static <T> Maybe<T> attemptWithLoaders(Iterable<PlanToSpecTransformer> transformers, Function<PlanToSpecTransformer,T> f) {
        Collection<String> transformersWhoDontSupport = new ArrayList<String>();
        Collection<Exception> otherProblemsFromTransformers = new ArrayList<Exception>();
        for (PlanToSpecTransformer t: transformers) {
            try {
                T result = f.apply(t);
                if (result==null) {
                    transformersWhoDontSupport.add(t.getShortDescription() + " (returned null)");
                    continue;
                }
                return Maybe.of(result);
            } catch (PlanNotRecognizedException|UnsupportedTypePlanException e) {
                transformersWhoDontSupport.add(t.getShortDescription() +
                    (Strings.isNonBlank(e.getMessage()) ? " ("+e.getMessage()+")" : ""));
            } catch (Throwable e) {
                Exceptions.propagateIfFatal(e);
                otherProblemsFromTransformers.add(new PropagatedRuntimeException("Transformer for "+t.getShortDescription()+" gave an error creating this plan: ",
                    Exceptions.collapseText(e), e));
            }
        }
        // failed
        Exception result;
        if (!otherProblemsFromTransformers.isEmpty()) {
            // at least one thought he could do it
            log.debug("Plan could not be transformed; failure will be propagated (other transformers tried = "+transformersWhoDontSupport+"): "+otherProblemsFromTransformers);
            result = otherProblemsFromTransformers.size()==1 ? Exceptions.create(null, otherProblemsFromTransformers) :
                Exceptions.create("All plan transformers failed", otherProblemsFromTransformers);
        } else {
            result = new UnsupportedTypePlanException("Invalid plan; format could not be recognized, trying with: "+transformersWhoDontSupport);
        }
        return Maybe.absent(result);
    }
    
}
