//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package de.ugoe.cs.autoquest.tasktrees;

import static org.junit.Assert.*;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskInstanceTraversingVisitor;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IIteration;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IIterationInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IOptional;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IOptionalInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelection;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelectionInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequenceInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstanceList;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession;

/**
 * <p>
 * TODO comment
 * </p>
 * 
 * @author Patrick Harms
 */
public class TaskTreeValidator {

    /**
     * 
     */
    public void validate(List<IUserSession> userSessions, boolean checkInstances) {
        validate(userSessions);
        
        if (!checkInstances) {
            return;
        }
        
        Map<ITask, List<ITaskInstance>> allTasks = new HashMap<ITask, List<ITaskInstance>>();
        
        for (IUserSession userSession : userSessions) {
            getAllTasksAndInstances(userSession, allTasks);
        }
        
        for (Map.Entry<ITask, List<ITaskInstance>> entry : allTasks.entrySet()) {
            assertEquals("number of task instances of task " + entry.getKey() + " in the " +
                         "sessions is not equal to those referenced by the model",
                         entry.getValue().size(), entry.getKey().getInstances().size());
            
            for (ITaskInstance candidate : entry.getValue()) {
                boolean found = false;
                for (ITaskInstance instance : entry.getKey().getInstances()) {
                    if (candidate.equals(instance)) {
                        if (!found) {
                            found = true;
                        }
                        else {
                            fail("the same instance is referred twice by the task");
                        }
                    }
                }
                
                assertTrue("instance " + candidate + " is not referred by task", found);
            }
        }
    }

    /**
     * 
     */
    public void validate(List<IUserSession> userSessions) {
        for (IUserSession userSession : userSessions) {
            validate(userSession);
        }
    }

    /**
     * 
     */
    public void validate(ITaskInstanceList taskInstances) {
        for (ITaskInstance taskInstance : taskInstances) {
            validate(taskInstance);
        }
    }

    /**
     * 
     */
    public void validate(ITaskInstance taskInstance) {
        assertNotNull("task model of task instance must not be null", taskInstance.getTask());
        
        if (taskInstance.getTask() instanceof ISequence) {
            ISequence task = (ISequence) taskInstance.getTask();
            
            assertEquals("number of children of sequence instance must match sequence model",
                         ((ISequenceInstance) taskInstance).size(), task.getChildren().size());
            
            for (int i = 0; i < ((ISequenceInstance) taskInstance).size(); i++) {
                assertNotNull("sequence instance child " + i + " was null",
                              ((ISequenceInstance) taskInstance).get(i));
                ITask childTask = ((ISequenceInstance) taskInstance).get(i).getTask();
                assertSame("task of sequence child " + i + " does not match sequence model",
                           childTask, task.getChildren().get(i));
            }
        }
        else if (taskInstance.getTask() instanceof ISelection) {
            ISelection task = (ISelection) taskInstance.getTask();
            
            assertNotNull("number of children of selection instance must be 1",
                          ((ISelectionInstance) taskInstance).getChild());
            assertTrue
                ("number of children of selection must be larger 0", task.getChildren().size() > 0);
            
            boolean found = false;
            for (ITask childTask : task.getChildren()) {
                assertNotNull("child of selection model must not be null", childTask);
                assertFalse("child of selection model must not be a selection",
                            childTask instanceof ISelection);
                /*assertFalse("child of selection model must not be an optional",
                            childTask instanceof IOptional);*/
                if (childTask.equals(((ISelectionInstance) taskInstance).getChild().getTask())) {
                    found = true;
                    break;
                }
            }
            
            assertTrue("no child of the selection model matches the model of child of the " +
                       "selection instance", found);
        }
        else if (taskInstance.getTask() instanceof IIteration) {
            ITask childTask = ((IIteration) taskInstance.getTask()).getMarkedTask();
            assertNotNull("child task of iteration model must not be null", childTask);
            assertFalse("child of iteration model must not be an iteration",
                        childTask instanceof IIteration);
            assertFalse("child of iteration model must not be an optional",
                        childTask instanceof IOptional);
            
            for (int i = 0; i < ((IIterationInstance) taskInstance).size(); i++) {
                assertNotNull("iteration instance child " + i + " was null",
                              ((IIterationInstance) taskInstance).get(i));
                assertSame("task of iteration child " + i + " does not match iteration model",
                           childTask, ((IIterationInstance) taskInstance).get(i).getTask());
            }
        }
        else if (taskInstance.getTask() instanceof IOptional) {
            ITask childTask = ((IOptional) taskInstance.getTask()).getMarkedTask();
            assertNotNull("child task of optional model must not be null", childTask);
            assertFalse("child of optional model must not be an optional",
                        childTask instanceof IOptional);
            
            if (((IOptionalInstance) taskInstance).getChild() != null) {
                assertEquals("task of optional child does not match optional model",
                             childTask, ((IOptionalInstance) taskInstance).getChild().getTask());
            }
        }
        else if (taskInstance.getTask() instanceof IEventTask) {
            IEventTask task = (IEventTask) taskInstance.getTask();
            assertNotNull("event task model must not be null", task);
            assertNotNull("event of event task instance must not be null",
                          ((IEventTaskInstance) taskInstance).getEvent());
        }
        else {
            fail("unknown task model: " + taskInstance.getTask());
        }
        
        if (taskInstance instanceof ITaskInstanceList) {
            for (ITaskInstance child : (ITaskInstanceList) taskInstance) {
                validate(child);
            }
        }
        else if (taskInstance instanceof ISelectionInstance) {
            validate(((ISelectionInstance) taskInstance).getChild());
        }
        else if (taskInstance instanceof IOptionalInstance) {
            if (((IOptionalInstance) taskInstance).getChild() != null) {
                validate(((IOptionalInstance) taskInstance).getChild());
            }
        }
    }


    /**
     * 
     */
    private void getAllTasksAndInstances(ITaskInstanceList                     taskInstances,
                                         final Map<ITask, List<ITaskInstance>> allTasks)
    {
        for (ITaskInstance taskInstance : taskInstances) {
            
            taskInstance.accept(new DefaultTaskInstanceTraversingVisitor() {

                /* (non-Javadoc)
                 * @see DefaultTaskInstanceTraversingVisitor#visit(IOptionalInstance)
                 */
                @Override
                public void visit(IOptionalInstance optionalInstance) {
                    addToInstanceList(optionalInstance);
                    super.visit(optionalInstance);
                }

                /* (non-Javadoc)
                 * @see DefaultTaskInstanceTraversingVisitor#visit(ISelectionInstance)
                 */
                @Override
                public void visit(ISelectionInstance selectionInstance) {
                    addToInstanceList(selectionInstance);
                    super.visit(selectionInstance);
                }

                /* (non-Javadoc)
                 * @see DefaultTaskInstanceTraversingVisitor#visit(IEventTaskInstance)
                 */
                @Override
                public void visit(IEventTaskInstance eventTaskInstance) {
                    addToInstanceList(eventTaskInstance);
                    super.visit(eventTaskInstance);
                }

                /* (non-Javadoc)
                 * @see DefaultTaskInstanceTraversingVisitor#visit(IIterationInstance)
                 */
                @Override
                public void visit(IIterationInstance iterationInstance) {
                    addToInstanceList(iterationInstance);
                    super.visit(iterationInstance);
                }

                /* (non-Javadoc)
                 * @see DefaultTaskInstanceTraversingVisitor#visit(ISequenceInstance)
                 */
                @Override
                public void visit(ISequenceInstance sequenceInstance) {
                    addToInstanceList(sequenceInstance);
                    super.visit(sequenceInstance);
                }

                private void addToInstanceList(ITaskInstance taskInstance) {
                    List<ITaskInstance> instances = allTasks.get(taskInstance.getTask());
                    
                    if (instances == null) {
                        instances = new LinkedList<ITaskInstance>();
                        allTasks.put(taskInstance.getTask(), instances);
                    }
                    
                    boolean found = false;
                    
                    for (ITaskInstance candidate : instances) {
                        if (candidate.equals(taskInstance)) {
                            found = true;
                            break;
                        }
                    }
                    
                    assertFalse("instance " + taskInstance + " occurred twice", found);
                    
                    instances.add(taskInstance);
                }
                
            });
        }
    }
}
