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

import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import de.ugoe.cs.autoquest.plugin.usability2.rules.operator.IFilter;
import de.ugoe.cs.autoquest.plugin.usability2.rules.operator.IResultTransformer;
import de.ugoe.cs.autoquest.plugin.usability2.rules.operator.MarkingFilter;
import de.ugoe.cs.autoquest.plugin.usability2.rules.operator.wrapper.ITaskEntry;
import de.ugoe.cs.autoquest.plugin.usability2.rules.results.AbstractResult;
import de.ugoe.cs.autoquest.plugin.usability2.rules.results.IMatch;
import de.ugoe.cs.autoquest.plugin.usability2.rules.results.IResult;
import de.ugoe.cs.autoquest.plugin.usability2.rules.results.UnmatchableResult;
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;

public abstract class AbstractBFSContinuableScanner extends MarkingFilter {
    
    public AbstractBFSContinuableScanner(IFilter marked) {
        super(marked);
    }

    public AbstractBFSContinuableScanner(IFilter marked, IResultTransformer transformer) {
        super(marked, transformer);
    }

    
    private static final List<ITask> EMPTY_LIST = Collections.emptyList();
    
    private List<? extends ITask> getChildren(ITask task) {
        if (task instanceof ITaskEntry)
            return getChildren((ITaskEntry) task);
        else if (task instanceof ISequence)
            return getChildren((ISequence) task);            
        else if (task instanceof IOptional)
            return getChildren((IOptional) task);
        else if (task instanceof IIteration)
            return getChildren((IIteration) task);
        else if (task instanceof ISelection)
            return getChildren((ISelection) task);

        return EMPTY_LIST;        
    }

    private List<? extends ITask> getChildren(ITaskEntry task) {
        return task.getChildren();
    }

    private List<? extends ITask> getChildren(ISelection task) {
        return task.getChildren();
    }

    private List<? extends ITask> getChildren(IOptional task) {
        return Arrays.asList(task.getMarkedTask());
    }

    private List<? extends ITask> getChildren(IIteration task) {
        return Arrays.asList(task.getMarkedTask());
    }

    private List<? extends ITask> getChildren(ISequence task) {
        return task.getChildren();
    }
    
    
    class VisitorInstance  {

        Queue<ITask> queue;
        IFilter filter;
        
        VisitorInstance(ITask root, IFilter filter) {
            queue = new LinkedList<ITask>();
            queue.add(root);
            this.filter = filter;
        }
        
        public IResult getNextMatch() {
            ITask task;            
            while( (task = queue.poll()) != null ) {
                visit(task, filter, queue);
            
                IResult r = accept(task, filter);
                if (r.isPresent())
                    return r;

            }
            
            return null;
        }
        
    }
    
    protected void visit(ITask task, IFilter filter, Queue<ITask> queue) {   

        ITask taskType = task;
        
        if (taskType instanceof ITaskEntry)
            taskType = ((ITaskEntry) task).getReference();

        List<? extends ITask> children = getChildren(task);
        
        if (taskType instanceof ISequence)
            visitSequence(children, queue);
        else if (taskType instanceof IOptional)
            visitOptional(children, queue);
        else if (taskType instanceof IIteration)
            visitIteration(children, queue);
        else if (taskType instanceof ISelection)
            visitSelection(children, queue);
    }

    protected void visitSelection(List<? extends ITask> children, Queue<ITask> queue) {
        queue.addAll(children);
    }

    protected void visitIteration(List<? extends ITask> children, Queue<ITask> queue) {
        queue.addAll(children);
    }

    protected void visitOptional(List<? extends ITask> children, Queue<ITask> queue) {
        queue.addAll(children);
    }

    protected void visitSequence(List<? extends ITask> children, Queue<ITask> queue) {
        queue.addAll(children);
    }

    protected IResult accept(ITask task, IFilter filter) {
        return filter.match(task);
    }

    @Override
    protected IResult match(final ITask task, final IFilter filter) {
        final VisitorInstance v = new VisitorInstance(task, filter);
        final IResult match = v.getNextMatch();
        
        if (match == null)
            return UnmatchableResult.NO_MATCH_FOUND;
        
        return new AbstractResult(true) {
            @Override
            public Iterator<IMatch> iterator() {
                return new Iterator<IMatch>() {
                    IMatch current;
                    VisitorInstance v = new VisitorInstance(task, filter);
                    IResult curResult = v.getNextMatch();
                    Iterator<IMatch> result = null;
                    
                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                    
                    @Override
                    public IMatch next() {
                        IMatch tmp = current;
                        current = null;
                        return tmp;
                    }
                    
                    @Override
                    public boolean hasNext() {
                        if (current != null)
                            return true;
                        
                        if (curResult == null)
                            return false;

                        do {
                            if(result == null)
                                result = curResult.iterator();
                            if (result.hasNext()) {
                                current = result.next();
                                return true;
                            } else 
                                result = null;                        
                        } while ( (curResult = v.getNextMatch()) != null );
                        return false;
                    }
                };
            }
        };
    }
    
}
