package org.jboss.windup.config.parameters;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

import org.jboss.forge.furnace.util.Iterators;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.Variables;
import org.jboss.windup.config.condition.GraphCondition;
import org.jboss.windup.graph.model.WindupVertexFrame;
import org.jboss.windup.util.Logging;
import org.jboss.windup.util.exception.WindupException;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.param.DefaultParameterValueStore;
import org.ocpsoft.rewrite.param.ParameterValueStore;
import org.ocpsoft.rewrite.param.Parameterized;

public abstract class ParameterizedGraphCondition extends GraphCondition implements Parameterized
{
    private static final Logger LOG = Logging.get(ParameterizedGraphCondition.class);

    static final String PARAM_VALUE_STORE_MAP_KEY = ParameterizedGraphCondition.class.getName()
                + "_parameterValueStoreMap";
    static final String RESULT_VALUE_STORE_MAP_KEY = ParameterizedGraphCondition.class.getName()
                + "_resultParameterValueStoreMap";

    protected abstract String getVarname();

    protected abstract boolean evaluateWithValueStore(GraphRewrite event, EvaluationContext context, FrameContext frame);

    protected abstract boolean evaluateAndPopulateValueStores(GraphRewrite event, EvaluationContext context,
                FrameCreationContext frameCreationContext);

    @Override
    public final boolean evaluate(GraphRewrite event, final EvaluationContext context)
    {
        final Map<ParameterValueStore, Map<String, Iterable<? extends WindupVertexFrame>>> valueStores = getParameterValueStoreMap(context);
        Map<WindupVertexFrame, ParameterValueStore> resultSetStores = getResultValueStoreMap(context);

        ParameterValueStore previousValueStore = DefaultParameterValueStore.getInstance(context);
        try
        {
            if (valueStores.isEmpty() || getRequiredParameterNames().isEmpty())
            {
                FrameCreationContext frameCreationContext = new FrameCreationContext()
                {
                    private ParameterValueStore current;
                    private final DefaultParameterValueStore original = (DefaultParameterValueStore) DefaultParameterValueStore
                                .getInstance(context);

                    @Override
                    public void beginNew(Map<String, Iterable<? extends WindupVertexFrame>> variables)
                    {
                        if (valueStores != null && current != null && valueStores.get(current) != null && valueStores.get(current).isEmpty())
                        {
                            // clean previous if nothing was submitted in the valuestore
                            rollback();
                        }
                        ParameterValueStore clone = clone(original);
                        this.current = clone;
                        context.put(ParameterValueStore.class, clone);

                        if (variables == null)
                            variables = new LinkedHashMap<>();
                        valueStores.put(clone, variables);
                    }

                    private ParameterValueStore clone(DefaultParameterValueStore instance)
                    {
                        DefaultParameterValueStore clone = new DefaultParameterValueStore(instance);
                        return clone;
                    }

                    @Override
                    public void rollback()
                    {
                        if (current != null)
                            valueStores.remove(current);
                    }
                };

                try
                {
                    return evaluateAndPopulateValueStores(event, context, frameCreationContext);
                }
                finally
                {
                    for (Entry<ParameterValueStore, Map<String, Iterable<? extends WindupVertexFrame>>> entry : valueStores
                                .entrySet())
                    {
                        ParameterValueStore valueStore = entry.getKey();

                        Map<String, Iterable<? extends WindupVertexFrame>> layer = entry.getValue();
                        if (layer == null)
                        {
                            throw new WindupException("Value store with no associated variables frame. This should not happen");
                        }

                        Iterable<? extends WindupVertexFrame> variable = layer.get(getVarname());
                        if (variable != null)
                        {
                            for (WindupVertexFrame frame : variable)
                            {
                                ParameterValueStore last = resultSetStores.put(frame, valueStore);
                                if (last != null)
                                {
                                    // WHY DOES THIS HAPPEN?
                                    LOG.warning("DOES THIS STILL HAPPEN?");
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                Set<WindupVertexFrame> resultSet = new LinkedHashSet<>();
                boolean result = false;

                DefaultParameterValueStore original = (DefaultParameterValueStore) DefaultParameterValueStore
                            .getInstance(context);

                for (Entry<ParameterValueStore, Map<String, Iterable<? extends WindupVertexFrame>>> entry : valueStores
                            .entrySet())
                {
                    ParameterValueStore valueStore = entry.getKey();
                    Map<String, Iterable<? extends WindupVertexFrame>> variables = entry.getValue();
                    try
                    {
                        Variables.instance(event).push(variables);
                        final AtomicBoolean rejected = new AtomicBoolean(false);
                        FrameContext frameContext = new FrameContext()
                        {
                            @Override
                            public void reject()
                            {
                                rejected.set(true);
                            }
                        };

                        try
                        {
                            context.put(ParameterValueStore.class, valueStore);

                            /*
                             * Each ValueStore must correspond with the variables map which which it was created.
                             */
                            if (evaluateWithValueStore(event, context, frameContext))
                                result = true;

                            if (rejected.get())
                                valueStores.remove(valueStore);
                        }
                        finally
                        {
                            context.put(ParameterValueStore.class, original);
                        }
                    }
                    finally
                    {
                        Iterable<? extends WindupVertexFrame> variable = Variables.instance(event).findVariable(getVarname());
                        if (variable != null)
                        {
                            resultSet.addAll(Iterators.asSet(variable));
                            for (WindupVertexFrame frame : variable)
                            {
                                ParameterValueStore last = resultSetStores.put(frame, valueStore);
                                if (last != null)
                                {
                                    // TODO is this a valid scenario?
                                }

                            }
                        }
                        Variables.instance(event).pop();
                    }
                }
                Variables.instance(event).setVariable(getVarname(), resultSet);
                return result;
            }
        }
        finally
        {
            context.put(ParameterValueStore.class, previousValueStore);
            context.put(PARAM_VALUE_STORE_MAP_KEY, valueStores);
            context.put(RESULT_VALUE_STORE_MAP_KEY, resultSetStores);
        }
    }

    @SuppressWarnings("unchecked")
    static Map<ParameterValueStore, Map<String, Iterable<? extends WindupVertexFrame>>> getParameterValueStoreMap(
                final EvaluationContext context)
    {
        Map<ParameterValueStore, Map<String, Iterable<? extends WindupVertexFrame>>> cachedStores = (Map<ParameterValueStore, Map<String, Iterable<? extends WindupVertexFrame>>>) context
                    .get(PARAM_VALUE_STORE_MAP_KEY);
        Map<ParameterValueStore, Map<String, Iterable<? extends WindupVertexFrame>>> valueStores = cachedStores;
        if (valueStores == null)
            valueStores = new ConcurrentHashMap<>();

        return valueStores;
    }

    @SuppressWarnings("unchecked")
    static Map<WindupVertexFrame, ParameterValueStore> getResultValueStoreMap(EvaluationContext context)
    {
        Map<WindupVertexFrame, ParameterValueStore> result = (Map<WindupVertexFrame, ParameterValueStore>) context
                    .get(RESULT_VALUE_STORE_MAP_KEY);
        if (result == null)
        {
            result = new LinkedHashMap<>();
        }
        return result;
    }

}
