//   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.temporalrelation.utils;

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>
 * Convenience class to validate if certain restrictions to task trees and task tree instances
 * apply. Restrictions are, e.g., that for every task there need to be instances or that for every
 * sequence instance the children match the corresponding sequence model. This class is vor test and
 * evaluation purposes only.
 * </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(entry.getKey(), "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(entry.getKey(), "the same instance is referred twice by the task");
                        }
                    }
                }
                
                assertTrue(candidate.getTask(), "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(ITask task) {
        assertTrue(task, "task has no instances", task.getInstances().size() > 0);
        assertTrue(task, "task has no instances", task.getInstances().iterator().hasNext());
        assertNotNull(task, "task has no instances", task.getInstances().iterator().next());
        
        for (ITaskInstance taskInstance : task.getInstances()) {
            assertSame(task, "task of instance does not match task", task, taskInstance.getTask());
            validate(taskInstance);
        }
    }

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

    /**
     * <p>
     * convenience method to log throw issues found during the evaluation 
     * </p>
     */
    private void fail(ITask task, String message) {
        System.err.println("failing validation for task " + task);
        System.err.println("message: " + message);
        new TaskTreeEncoder().encode(task, System.err);

        throw new RuntimeException(message);
    }

    /**
     * <p>
     * convenience method to log throw issues found during the evaluation 
     * </p>
     */
    private void assertNotNull(ITask task, String message, Object object) {
        if (object == null) {
            System.err.println("failing validation for task " + task);
            System.err.println("message: " + message);
            new TaskTreeEncoder().encode(task, System.err);

            throw new RuntimeException(message);
        }
    }

    /**
     * <p>
     * convenience method to log throw issues found during the evaluation 
     * </p>
     */
    private void assertTrue(ITask task, String message, boolean value) {
        if (!value) {
            System.err.println("failing validation for task " + task);
            System.err.println("message: " + message);
            new TaskTreeEncoder().encode(task, System.err);

            throw new RuntimeException(message);
        }
    }

    /**
     * <p>
     * convenience method to log throw issues found during the evaluation 
     * </p>
     */
    private void assertFalse(ITask task, String message, boolean value) {
        if (value) {
            System.err.println("failing validation for task " + task);
            System.err.println("message: " + message);
            new TaskTreeEncoder().encode(task, System.err);

            throw new RuntimeException(message);
        }
    }

    /**
     * <p>
     * convenience method to log throw issues found during the evaluation 
     * </p>
     */
    private void assertEquals(ITask task, String message, Object object1, Object object2) {
        if (!object1.equals(object2)) {
            System.err.println("failing validation for task " + task);
            System.err.println("message: " + message);
            new TaskTreeEncoder().encode(task, System.err);

            throw new RuntimeException(message);
        }
    }

    /**
     * <p>
     * convenience method to log throw issues found during the evaluation 
     * </p>
     */
    private void assertSame(ITask task, String message, Object object1, Object object2) {
        if (object1 != object2) {
            System.err.println("failing validation for task " + task);
            System.err.println("message: " + message);
            new TaskTreeEncoder().encode(task, System.err);

            throw new RuntimeException(message);
        }
    }
}
