/*
 * (c) Copyright 2014 Hewlett-Packard Development Company, L.P.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Apache License v2.0 which accompany this distribution.
 *
 * The Apache License is available at
 * http://www.apache.org/licenses/LICENSE-2.0
 */
package org.openscore.lang.compiler.scorecompiler;

import ch.lambdaj.function.convert.Converter;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.Validate;
import org.openscore.api.ExecutionPlan;
import org.openscore.lang.compiler.SlangTextualKeys;
import org.openscore.lang.compiler.modeller.DependenciesHelper;
import org.openscore.lang.compiler.modeller.model.Executable;
import org.openscore.lang.compiler.modeller.model.Flow;
import org.openscore.lang.compiler.modeller.model.Operation;
import org.openscore.lang.compiler.modeller.model.Task;
import org.openscore.lang.entities.CompilationArtifact;
import org.openscore.lang.entities.bindings.Input;
import org.openscore.lang.entities.bindings.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static ch.lambdaj.Lambda.convertMap;

/*
 * Created by stoneo on 2/2/2015.
 */
@Component
public class ScoreCompilerImpl implements ScoreCompiler{

    @Autowired
    private ExecutionPlanBuilder executionPlanBuilder;

    @Autowired
    private DependenciesHelper dependenciesHelper;

    @Override
    public CompilationArtifact compile(Executable executable, Set<Executable> path) {
        Validate.notEmpty(executable.getNamespace(),"Operation/Flow must have a namespace");

        Map<String, Executable> filteredDependencies = new HashMap<>();
        //we handle dependencies only if the file has imports
        boolean hasDependencies = CollectionUtils.isNotEmpty(executable.getDependencies())
                && executable.getType().equals(SlangTextualKeys.FLOW_TYPE);
        if (hasDependencies) {
            Validate.notEmpty(path, "Source " + executable.getName() + " has dependencies but no path was given to the compiler");
            Validate.noNullElements(path, "Source " + executable.getName() + " has empty dependencies");

            //we add the current executable since a dependency can require it
            List<Executable> availableExecutables = new ArrayList<>(path);
            availableExecutables.add(executable);

            //than we match the references to the actual dependencies
            filteredDependencies = dependenciesHelper.matchReferences(executable, availableExecutables);

            // Validate that all the tasks of a flow have navigations for all the reference's results
            validateAllDependenciesResultsHaveMatchingNavigations(executable, filteredDependencies);
        }

        //next we create an execution plan for the required executable
        ExecutionPlan executionPlan = compileToExecutionPlan(executable);

        //and also create execution plans for all other dependencies
        Map<String, ExecutionPlan> dependencies = convertMap(filteredDependencies, new Converter<Executable, ExecutionPlan>() {
            @Override
            public ExecutionPlan convert(Executable compiledExecutable) {
                return compileToExecutionPlan(compiledExecutable);
            }
        });
        Collection<Executable> executables = new ArrayList<>(filteredDependencies.values());
        executables.add(executable);
        executionPlan.setSubflowsUUIDs(new HashSet<>(dependencies.keySet()));
        return new CompilationArtifact(executionPlan, dependencies, executable.getInputs(), getSystemProperties(executables));
    }

    /**
     * Validate that for all the tasks in the flow, all results from the referenced operation or flow have matching navigations
     * If the given {@link org.openscore.lang.compiler.modeller.model.Executable} is an operation, the method does nothing
     * Throws {@link java.lang.IllegalArgumentException} if:
     *      - Any reference of the executable is missing
     *      - There is a missing navigation for one of the tasks' references' results
     *
     * @param executable the flow to validate
     * @param filteredDependencies a map holding for each reference name, its {@link org.openscore.lang.compiler.modeller.model.Executable} object
     */
    private void validateAllDependenciesResultsHaveMatchingNavigations(Executable executable, Map<String, Executable> filteredDependencies) {
        if(executable.getType().equals(SlangTextualKeys.OPERATION_TYPE)){
            return;
        }
        Flow flow = (Flow)executable;
        Deque<Task> tasks = flow.getWorkflow().getTasks();
        for(Task task : tasks){
            Map<String, String> taskNavigations = task.getNavigationStrings();
            String refId = task.getRefId();
            Executable reference = filteredDependencies.get(refId);
            Validate.notNull(reference, "Cannot compile flow: \'" + executable.getName() + "\' since for task: \'" + task.getName()
                                        + "\', the dependency: \'" + refId + "\' is missing.");
            List<Result> refResults = reference.getResults();
            for(Result result : refResults){
                String resultName = result.getName();
                Validate.isTrue(taskNavigations.containsKey(resultName), "Cannot compile flow: \'" + executable.getName() +
                                                "\' since for task: '" + task.getName() + "\', the result \'" + resultName+
                                                "\' of its dependency: \'"+ refId + "\' has no matching navigation");
            }
        }
    }

    /**
     * Utility method that cast a {@link org.openscore.lang.compiler.modeller.model.Executable} to its subtype
     * and create an {@link org.openscore.api.ExecutionPlan} for it
     *
     * @param executable the executable to create an {@link org.openscore.api.ExecutionPlan} for
     * @return {@link org.openscore.api.ExecutionPlan} of the given {@link org.openscore.lang.compiler.modeller.model.Executable}
     */
    private ExecutionPlan compileToExecutionPlan(Executable executable) {

        switch (executable.getType()){
            case SlangTextualKeys.OPERATION_TYPE:
                return executionPlanBuilder.createOperationExecutionPlan((Operation) executable);
            case SlangTextualKeys.FLOW_TYPE:
                return executionPlanBuilder.createFlowExecutionPlan((Flow) executable);
            default:
                throw new RuntimeException("Executable: " + executable.getName() + " cannot be compiled to an ExecutionPlan since it is not a flow and not an operation");
        }
    }

	private static Collection<Input> getSystemProperties(Collection<Executable> executables) {
		Collection<Input> result = new ArrayList<>();
		for(Executable executable : executables) {
			result.addAll(getSystemProperties(executable.getInputs()));
			if(executable instanceof Flow) {
				for(Task task : ((Flow)executable).getWorkflow().getTasks()) {
					result.addAll(getSystemProperties(task.getInputs()));
				}
			}
		}
		return result;
	}

	private static Collection<Input> getSystemProperties(List<Input> inputs) {
		Collection<Input> result = new ArrayList<>();
		for(Input input : inputs) {
			if(input.getSystemPropertyName() != null) result.add(input);
		}
		return result;
	}

}

@@ -1840,16 +1840,43 @@
 ion/Flow
+ %22 +executable.getName() +%22
  must ha

add name of op/flow to error msg