
package de.ugoe.cs.autoquest.plugin.usability2.rules.operator.wrapper;

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

import de.ugoe.cs.autoquest.plugin.usability2.meta.Untested;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IIteration;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IOptional;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelection;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskVisitor;

@Untested
public class FollowedByUtil {

    private FollowedByUtil() {}

    static class Visitor implements ITaskVisitor {
        private List<ITaskEntry> next;
        private TaskWrapper current;
        private final ITaskEntry root;

        public Visitor(ITask task) {
            next = new LinkedList<ITaskEntry>();
            current = new TaskWrapper(task);
            InstanceUtility.setupInstances(current);

            root = current;
        }

        public ITaskEntry getRoot() {
            return root;
        }

        private TaskWrapper setup(ITask value) {
            TaskWrapper cur = this.current;

            if (cur.parent != null)
                cur.parent.addChild(cur);

            this.next = cur.next;
            return cur;
        }

        private void finish(ITaskWrapper cur) {}

        @Override
        public void visit(IEventTask eventTask) {
            TaskWrapper cur = setup(eventTask);
            finish(cur);
        }

        @Override
        public void visit(IIteration iteration) {
            TaskWrapper cur = setup(iteration);

            List<ITaskEntry> n = new ArrayList<ITaskEntry>(this.next.size() + 1);
            TaskProxy p = new TaskProxy();
            n.add(p);
            n.addAll(this.next);
            this.next = n;

            ITaskWrapper c = accept(cur, iteration.getMarkedTask(), this.next);
            p.setReference(c);

            finish(cur);
        }

        private ITaskWrapper accept(ITaskWrapper cur, ITask task, List<ITaskEntry> next) {
            TaskWrapper t = new TaskWrapper(task, cur, next);
            InstanceUtility.setupInstances(t);

            this.current = t;
            task.accept(this);
            return t;
        }

        @Override
        public void visit(IOptional optional) {
            TaskWrapper cur = setup(optional);
            accept(cur, optional.getMarkedTask(), next);
            finish(cur);
        }

        @Override
        public void visit(ISelection selection) {
            TaskWrapper cur = setup(selection);

            for (ITask child : selection.getChildren()) {
                accept(cur, child, cur.getNext());
            }

            finish(cur);
        }

        @Override
        public void visit(ISequence sequence) {
            /*
             * Each entry of a sequence is followed by its predecessor. Thus the Wrappers have to be
             * setup in back-to-front order, but visited in front-to-back order.
             */
            TaskWrapper cur = setup(sequence);

            List<ITask> childrenList = sequence.getChildren();

            ITask[] children = childrenList.toArray(new ITask[childrenList.size()]);
            ITaskEntry[] cArray = new ITaskEntry[children.length];

            if (children.length == 0)
                return;

            int id = children.length - 1;
            TaskWrapper nextElement = new TaskWrapper(children[id], cur, next);
            InstanceUtility.setupSequence(nextElement, id);

            cArray[id] = nextElement;

            id--;
            for (; id >= 0; id--) {
                nextElement = new TaskWrapper(children[id], cur, nextElement);
                InstanceUtility.setupSequence(nextElement, id);
                cArray[id] = nextElement;
            }

            for (ITaskEntry child : cArray) {
                this.current = (TaskWrapper) child;
                child.getReference().accept(this);
            }

            finish(cur);
        }

        @Override
        public void visit(ITask task) {
            if (task instanceof IEventTask) {
                visit((IEventTask) task);
            }
            else if (task instanceof IIteration) {
                visit((IIteration) task);
            }
            else if (task instanceof IOptional) {
                visit((IOptional) task);
            }
            else if (task instanceof ISelection) {
                visit((ISelection) task);
            }
            else if (task instanceof ISequence) {
                visit((ISequence) task);
            }
        }
    }

    /**
     * 
     * <p>
     * TODO: comment
     * </p>
     * 
     * @param task
     * @return
     */
    public static ITaskEntry generateFollowList(ITask task) {
        Visitor v = new Visitor(task);
        v.visit(task);
        return v.getRoot();
    }
}
