package com.atlassian.stash.internal.task.enrich;

import com.atlassian.bitbucket.property.PropertyMap;
import com.atlassian.stash.internal.content.AbstractProviderContext;
import com.atlassian.stash.internal.pull.InternalPullRequest;
import com.atlassian.stash.internal.task.InternalTask;
import com.atlassian.stash.internal.task.InternalTaskContext;
import com.atlassian.stash.internal.task.InternalTaskContextVisitor;
import com.atlassian.stash.internal.task.property.TaskPropertyContext;
import com.atlassian.stash.internal.task.property.TaskPropertyProvider;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;

import javax.annotation.Nonnull;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;

import static com.atlassian.bitbucket.util.Chainable.chain;
import static com.google.common.base.Predicates.not;

/**
 * {@link TaskEnricher} that adds {@link PropertyMap properties} to one
 * or more tasks.
 *
 */
// This interface should be renamed {@code PluginTaskPostProcessor} if providing task properties is made a public API,
// for consistency with the comment and pull request properties' API.
public class PropertyTaskEnricher implements TaskEnricher {

    private final List<TaskPropertyProvider> propertyProviders;

    public PropertyTaskEnricher(List<TaskPropertyProvider> propertyProviders) {
        this.propertyProviders = ImmutableList.copyOf(propertyProviders);
    }

    @Nonnull
    @Override
    public InternalTask enrich(@Nonnull InternalTask task) {
        if (task.hasProperties()) {
            return task;
        }

        Context context = new Context(task.getContext(), task);
        for (TaskPropertyProvider provider : getPropertyProviders()) {
            provider.provideProperties(context);
        }

        return new Enricher(context).apply(task);
    }

    @Nonnull
    @Override
    public Iterable<InternalTask> enrich(@Nonnull InternalTaskContext taskContext,
                                         @Nonnull Iterable<InternalTask> tasks) {
        Set<InternalTask> toProcess = chain(tasks).filter(task -> !task.hasProperties()).toSet();

        Context context = new Context(taskContext, toProcess);
        for (TaskPropertyProvider provider : getPropertyProviders()) {
            provider.provideProperties(context);
        }

        return ImmutableList.copyOf(Iterables.transform(tasks, new Enricher(context)));
    }

    private List<TaskPropertyProvider> getPropertyProviders() {
        return propertyProviders;
    }

    private static class Context extends AbstractProviderContext<InternalTask> implements TaskPropertyContext {
        private final InternalTaskContext taskContext;

        private Context(InternalTaskContext taskContext, InternalTask task) {
            this(taskContext, ImmutableSet.of(task));
        }

        private Context(InternalTaskContext taskContext, Set<InternalTask> tasks) {
            super(tasks);
            this.taskContext = taskContext;
        }

        @Override
        public <T> T accept(InternalTaskContextVisitor<T> visitor) {
            if (taskContext instanceof InternalPullRequest) {
                return visitor.visit((InternalPullRequest) taskContext);
            }

            throw new UnsupportedOperationException("Cannot visit " + taskContext.getClass());
        }
    }

    private static class Enricher implements Function<InternalTask, InternalTask> {
        private final Context context;

        public Enricher(Context context) {
            this.context = context;
        }

        @Override
        public InternalTask apply(InternalTask task) {
            if (!task.hasProperties()) {
                task.setProperties(context.getPropertiesFor(task));
            }
            return task;
        }
    }
}
