package org.ovirt.engine.ui.uicompat;

import org.ovirt.engine.core.compat.Event;
import org.ovirt.engine.core.compat.EventArgs;
import org.ovirt.engine.core.compat.EventDefinition;
import org.ovirt.engine.core.compat.IEventListener;

import java.util.ArrayList;
import java.util.List;

public class ReversibleFlow {

    List<IEnlistmentNotification> notifications;
    int prepareIndex;
    int commitIndex;
    int rollbackIndex;
    Object context;

    public static EventDefinition CompleteEventDefinition;
    private Event completeEvent;

    public Event getCompleteEvent() {
        return completeEvent;
    }

    static {

        CompleteEventDefinition = new EventDefinition("Complete", ReversibleFlow.class); //$NON-NLS-1$
    }


    public ReversibleFlow() {

        completeEvent = new Event(CompleteEventDefinition);
        notifications = new ArrayList<IEnlistmentNotification>();
    }

    public void enlist(IEnlistmentNotification notification) {

        // Add notifier to the list.
        notifications.add(notification);
    }

    /**
     * Note, the current implementation is not thread safe (good enough for UI)
     */
    public void run() {

        // Begin processing notifications.
        if (notifications.size() > 0) {
            prepareIndex = 0;
            rollbackIndex = notifications.size();
            prepare();
        }
    }

    public void run(Object context) {

        this.context = context;
        run();
    }

    private void prepare() {

        PreparingEnlistment enlistment = new PreparingEnlistment(context);

        enlistment.getPreparedEvent().addListener(
            new IEventListener() {
                @Override
                public void eventRaised(Event ev, Object sender, EventArgs args) {
                    preparedHandler();
                }
            });

        enlistment.getRollbackEvent().addListener(
            new IEventListener() {
                @Override
                public void eventRaised(Event ev, Object sender, EventArgs args) {
                    rollbackHandler();
                }
            });

        enlistment.getDoneEvent().addListener(
            new IEventListener() {
                @Override
                public void eventRaised(Event ev, Object sender, EventArgs args) {
                    doneOnPrepareHandler();
                }
            });

        IEnlistmentNotification notification = notifications.get(prepareIndex);
        notification.prepare(enlistment);
    }

    private void commit() {

        Enlistment enlistment = new Enlistment(context);

        enlistment.getDoneEvent().addListener(
            new IEventListener() {
                @Override
                public void eventRaised(Event ev, Object sender, EventArgs args) {
                    doneOnCommitHandler();
                }
            });

        IEnlistmentNotification notification = notifications.get(commitIndex);
        notification.commit(enlistment);
    }

    private void rollback() {

        Enlistment enlistment = new Enlistment(context);

        enlistment.getDoneEvent().addListener(
            new IEventListener() {
                @Override
                public void eventRaised(Event ev, Object sender, EventArgs args) {
                    doneOnRollbackHandler();
                }
            });

        IEnlistmentNotification notification = notifications.get(rollbackIndex);
        notification.rollback(enlistment);
    }

    private void preparedHandler() {

        if (prepareIndex == notifications.size() - 1) {

            // Move to the next (commit) stage.
            commitIndex = 0;
            commit();

        } else {
            // Process next notification.
            prepareIndex++;
            prepare();
        }
    }

    private void rollbackHandler() {

        if (rollbackIndex == 0) {
            complete();

        } else {
            // Process next notification.
            rollbackIndex--;
            rollback();
        }
    }

    private void doneOnPrepareHandler() {
        complete();
    }

    private void doneOnRollbackHandler() {
        rollbackHandler();
    }

    private void doneOnCommitHandler() {

        if (commitIndex == notifications.size() - 1) {
            complete();

        } else {
            // Process next notification.
            commitIndex++;
            commit();
        }
    }

    private void complete() {
        getCompleteEvent().raise(this, EventArgs.Empty);
    }
}
