package com.dtolabs.rundeck.core.rules;

import com.google.common.util.concurrent.*;
import lombok.Getter;
import lombok.Setter;
import org.apache.log4j.Logger;

import java.util.*;
import java.util.concurrent.*;

/**
 * A WorkflowSystem which processes the operations by use of a rule system and a mutable state.
 * implementation: Operations can supply state changes after they succeed, or fail. State
 * changes
 * provided by
 * operations are added in the order they are received, and after a set of available state
 * changes are added, the rule engine is used to update the state based on its rules.  After
 * the state is updated, all pending operations are queried to see if they can run given the new
 * state, and any that can are queued to be executed.  Workflow processing stops when no operations
 * are currently running, no new state changes are available, and no pending operations can be run.
 */
public class WorkflowEngine
        implements StateWorkflowSystem, WorkflowSystemEventHandler
{
    static Logger logger = Logger.getLogger(WorkflowEngine.class.getName());
    @Getter
    private final MutableStateObj state;
    @Getter
    private final RuleEngine ruleEngine;
    private final ListeningExecutorService executorService;
    private final ListeningExecutorService manager;

    @Getter
    @Setter
    private List<WorkflowSystemEventListener> listeners;
    @Getter
    @Setter
    private volatile boolean interrupted;

    /**
     * Create engine
     *
     * @param ruleEngine   rule engine to process state changes via rules
     * @param state    initial state
     * @param executor executor to process operations, which should be multithreaded to process operations concurrently
     */
    public WorkflowEngine(
            final RuleEngine ruleEngine,
            final MutableStateObj state,
            final ExecutorService executor

    )
    {
        this.ruleEngine = ruleEngine;
        this.state = state;
        executorService = MoreExecutors.listeningDecorator(executor);
        manager = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
    }



    static class Sleeper {
        private long orig = 250;
        private long mult = 2;
        private long max = 5000L;
        private long time = orig;
        private TimeUnit unit = TimeUnit.MILLISECONDS;

        long time() {
            return time;
        }

        void backoff() {
            time = Math.min(time * mult, max);
        }

        void reset() {
            time = orig;
        }

        TimeUnit unit() {
            return unit;
        }
    }

    @Override
    public <DAT,
            RES extends OperationCompleted<DAT>,
            OP extends Operation<DAT, RES>
            >
    Set<OperationResult<DAT, RES, OP>> processOperations(
            final Set<OP> operations,
            final SharedData<DAT, Map<String, String>> sharedData
    )
    {

        WorkflowEngineOperationsProcessor<DAT, RES, OP> processor = new WorkflowEngineOperationsProcessor<>(
                this,
                this,
                operations,
                sharedData,
                executorService,
                manager
        );

        processor.beginProcessing();

        Set<OperationResult<DAT, RES, OP>> results = processor.getResults();
        interrupted = processor.isInterrupted();

        event(
                WorkflowSystemEventType.WillShutdown,
                String.format("Workflow engine shutting down (interrupted? %s)", interrupted),
                interrupted
        );

        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(5, TimeUnit.MINUTES)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
        }

        manager.shutdown();
        try {
            if (!manager.awaitTermination(5, TimeUnit.MINUTES)) {
                manager.shutdownNow();
            }
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
        }
        if (!processor.getPending().isEmpty()) {
            event(
                    WorkflowSystemEventType.IncompleteOperations,
                    String.format("Some operations were not run: %d", processor.getPending().size()),
                    processor.getPending()
            );
        }
        event(WorkflowSystemEventType.Complete, String.format("Workflow complete: %s", results));
        return results;
    }

    static class Event implements WorkflowSystemEvent {
        private WorkflowSystemEventType eventType;
        private String message;
        private Object data;

        Event(final WorkflowSystemEventType eventType, final String message, final Object data) {
            this.eventType = eventType;
            this.message = message;
            this.data = data;
        }

        static Event with(WorkflowSystemEventType type, String message) {
            return new Event(type, message, null);
        }

        static Event with(WorkflowSystemEventType type, String message, Object data) {
            return new Event(type, message, data);
        }

        @Override
        public WorkflowSystemEventType getEventType() {
            return eventType;
        }

        public void setEventType(WorkflowSystemEventType eventType) {
            this.eventType = eventType;
        }

        @Override
        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        @Override
        public Object getData() {
            return data;
        }

        public void setData(Object data) {
            this.data = data;
        }
    }


    static <T> OperationCompleted<T> dummyResult(final StateObj state) {
        return new OperationCompleted<T>() {
            @Override
            public StateObj getNewState() {
                return state;
            }

            @Override
            public T getResult() {
                return null;
            }
        };
    }

    @Override
    public void event(final WorkflowSystemEventType eventType, final String message) {
        event(eventType, message, null);
    }

    @Override
    public void event(final WorkflowSystemEventType eventType, final String message, final Object data) {
        event(Event.with(eventType, message, data));
    }

    @Override
    public void event(final WorkflowSystemEvent event) {
        if (null != listeners && !listeners.isEmpty()) {
            listeners.forEach(a -> a.onEvent(event));
        }
    }

    @Override
    public boolean isWorkflowEndState() {
        return getState().hasState(Workflows.getWorkflowEndState());
    }

    static class WResult<D, T extends OperationCompleted<D>, X extends Operation<D, T>> implements
            OperationResult<D, T, X>
    {
        final private X operation;

        WResult(final X operation, final Throwable throwable) {
            this.operation = operation;
            this.throwable = throwable;
            this.success = null;
        }

        WResult(final X operation, final T success) {
            this.operation = operation;
            this.success = success;
            this.throwable = null;
        }

        final Throwable throwable;
        final T success;

        @Override
        public Throwable getFailure() {
            return throwable;
        }

        @Override
        public T getSuccess() {
            return success;
        }

        @Override
        public X getOperation() {
            return operation;
        }

        @Override
        public String toString() {
            return operation +
                   ": " +
                   (null != success ? success : null != throwable ? throwable.getClass().getSimpleName() : "?");
        }
    }
}

@@ -118,24 +118,48 @@
 bok.Setter;%0A
+import lombok.ToString;%0A
 import org.a
@@ -4635,32 +4635,48 @@
 Event %7B%0A        
+@Getter @Setter 
 private Workflow
@@ -4702,32 +4702,48 @@
 ntType;%0A        
+@Getter @Setter 
 private String m
@@ -4750,32 +4750,48 @@
 essage;%0A        
+@Getter @Setter 
 private Object d
@@ -5226,32 +5226,32 @@
  Object data) %7B%0A
+
             retu
@@ -5298,606 +5298,8 @@
    %7D
-%0A%0A        @Override%0A        public WorkflowSystemEventType getEventType() %7B%0A            return eventType;%0A        %7D%0A%0A        public void setEventType(WorkflowSystemEventType eventType) %7B%0A            this.eventType = eventType;%0A        %7D%0A%0A        @Override%0A        public String getMessage() %7B%0A            return message;%0A        %7D%0A%0A        public void setMessage(String message) %7B%0A            this.message = message;%0A        %7D%0A%0A        @Override%0A        public Object getData() %7B%0A            return data;%0A        %7D%0A%0A        public void setData(Object data) %7B%0A            this.data = data;%0A        %7D
 %0A   

use lombok