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

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import de.ugoe.cs.autoquest.plugin.usability2.rules.operator.wrapper.FollowedByUtil;
import de.ugoe.cs.autoquest.plugin.usability2.rules.operator.wrapper.ITaskEntry;
import de.ugoe.cs.autoquest.plugin.usability2.rules.operator.wrapper.TaskProxy;
import de.ugoe.cs.autoquest.plugin.usability2.rules.results.AbstractResult;
import de.ugoe.cs.autoquest.plugin.usability2.rules.results.DefaultMatch;
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.ITask;

public class Follows extends AbstractFilterOperator {
    
    private final Map<String, ITask> EMPTY_MAP = Collections.emptyMap();
    
    public Follows(List<IFilter> filters) {
        super(filters);
    }

    public Follows(IFilter... filters) {
        super(filters);
    }
    
    private class ResultPair {
        int filterIndex;
        ITask task;
        Map<String, ITask> labelMap;
        
        public ResultPair(int filterIndex, ITask task, Map<String, ITask> labelMap) {
            this.filterIndex = filterIndex;
            this.task = task;
            this.labelMap = labelMap;
        }
    }
    
    private class VisitorInstance {
        Queue<ResultPair> queue;
        ArrayList<IFilter> filters;
        
        private VisitorInstance(ITask root, List<IFilter> filters) {
            this.filters = new ArrayList<IFilter>(filters);
            queue = new LinkedList<ResultPair>();
            queue.add(new ResultPair(0, root, EMPTY_MAP));
        }
        
        public IMatch getNextMatch() {
            ResultPair pair;            
            while( (pair = queue.poll()) != null ) {
            
                if (pair.filterIndex == -1) {
                    return new DefaultMatch(pair.task, pair.labelMap);
                }

                visit(pair, queue);
            }
            
            return null;
        }

        private void visit(ResultPair pair, Queue<ResultPair> queue) {
            IFilter filter = filters.get(pair.filterIndex);
            IResult result = filter.match(pair.task);
            
            int index = pair.filterIndex+1;
            if (index >= filters.size())
                index = -1;
            
            if(result.isPresent()) {
                List<ResultPair> match = new LinkedList<ResultPair>();
                for (IMatch rMatch : result) {
                    ITask iTask = rMatch.getTask();
                    
                    Map<String, ITask> m = new HashMap<String, ITask>(pair.labelMap);
                    m.putAll(rMatch.getLabeledResults());
                    
                    if(iTask instanceof ITaskEntry) {
                        if (iTask instanceof TaskProxy) {
                            System.out.print("R");
                        }
                        
                        for (ITask task : ((ITaskEntry) iTask).getNext()) {
                            match.add(new ResultPair(index, task, m));
                        }
                    } else
                        throw new RuntimeException();
                }
                queue.addAll(match);
            }
        }
    }
    
    @Override
    public IResult match(ITask task, final List<IFilter> filters) {
        final ITask root = FollowedByUtil.generateFollowList(task);
        VisitorInstance v = new VisitorInstance(root, filters);
        IMatch match = v.getNextMatch();
        
        if (match == null)
            return UnmatchableResult.NO_MATCH_FOUND;
        
        return new AbstractResult(true) {
            @Override
            public Iterator<IMatch> iterator() {
                return new Iterator<IMatch>() {
                    VisitorInstance v = new VisitorInstance(root, filters);
                    IMatch current;
                    
                    @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;
                        current = v.getNextMatch();
                        return current != null;
                    }
                };
            }
        };
    }
}
