//   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.manager;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.tasktrees.taskequality.TaskEquality;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskBuilder;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskFactory;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * The task tree manager is responsible for transforming one or more user sessions into a task
 * model. It can be called by providing a collection of user sessions where the result
 * will be a task model. Furthermore, it can be used by providing single events in their respective
 * order including notifications about where a user session ends. The result is a task model,
 * as well. 
 * </p>
 * 
 * @version 1.0
 * @author pharms
 */
public class TaskTreeManager {
    
    /**
     * <p>
     * the internally used task builder
     * </p>
     */
    private ITaskBuilder taskBuilder = ComponentManager.getDefaultTaskBuilder();

    /**
     * <p>
     * the internally used task factory
     * </p>
     */
    private ITaskFactory taskFactory = ComponentManager.getDefaultTaskFactory();

    /**
     * <p>
     * if single events are provided, the user sessions collected so far
     * </p>
     */
    private List<IUserSession> sessions = null;

    /**
     * <p>
     * if single events are provided, the currently collected user session
     * </p>
     */
    private IUserSession currentSession = null;

    /**
     * <p>
     * The default task equality considered when comparing task instances initially
     * </p>
     */
    private TaskEquality minimalTaskEquality = TaskEquality.SEMANTICALLY_EQUAL;

    /**
     * <p>
     * If this flag is set, then the task tree manager will not create new event tasks for any
     * events. Instead, it will check using the hashcode and equal methods of the provided events
     * if a certain event has already occurred, and if so, the corresponding event task will be
     * reused.
     * </p>
     */
    private boolean useEventEqualityForTaskComparison;
    
    /**
     * <p>
     * the minimum number of event task instances a sequence must cover to still detect it as a
     * representative task
     * </p>
     */
    private int minimumSequenceCoverage;;

    /**
     * <p>
     * If the flag {@link #useEventEqualityForTaskComparison} is set, then here the task tree
     * manager stores the already created event tasks.
     * </p>
     */
    private Map<Event, IEventTask> eventTasks = new HashMap<>();

    /**
     * <p>
     * initializes the task tree manager
     * </p>
     */
    public TaskTreeManager() {
        this.sessions = new LinkedList<IUserSession>();
    }

    /**
     * <p>
     * creates a task model based on the provided user sessions. Yet, the user sessions are
     * list of events. Such will be transformed in into task instances of event tasks assigned
     * to {@link IUserSession}s. The {@link IUserSession}s will then be restructured using
     * the temporal relationship rule manager to detect tasks and respective instances. The
     * results of this transformation is stored in a task model which is the return value of
     * this method.
     * </p>
     * 
     * @param newSessions the user sessions of which the task model shall be created
     * 
     * @return the task model created from the user sessions
     * 
     * @throws IllegalStateException if the task manager is already used by providing it with
     *                               single events
     */
    public synchronized ITaskModel createTaskModel(Collection<List<Event>> newSessions,
                                                   boolean useEventEqualityForTaskComparison,
                                                   int     minimumSequenceCoverage)
    {
        if ((currentSession != null) || (sessions.size() > 0)) {
            throw new IllegalStateException("do not mix calls to this method with calls to the " +
                                            "other methods for handling tasks. Use only one " +
                                            "variant instead.");
        }
        
        this.useEventEqualityForTaskComparison = useEventEqualityForTaskComparison;
        
        if (useEventEqualityForTaskComparison) {
            // if we do this, we also need to consider identity of event tasks afterwards
            minimalTaskEquality = TaskEquality.IDENTICAL;
        }
        
        this.minimumSequenceCoverage = minimumSequenceCoverage;
        
        for (List<Event> newSession : newSessions) {
            if (newSession.size() > 0) {
                for (Event event : newSession) {
                    handleNewEvent(event);
                }
                finishSession();
            }
        }
        
        return getTaskModel();
    }

    /**
     * <p>
     * call {@link #createTaskModel(Collection, boolean, int)} with false for use event equality
     * for comparison and 0 for the minimum number of covered events per sequence
     * </p>
     * 
     * @param newSessions the user sessions of which the task model shall be created
     * 
     * @return the task model created from the user sessions
     * 
     * @throws IllegalStateException if the task manager is already used by providing it with
     *                               single events
     */
    public synchronized ITaskModel createTaskModel(Collection<List<Event>> newSessions) {
        return createTaskModel(newSessions, false, 0);
    }

    /**
     * <p>
     * handles a single event that occurred in a user session.
     * </p>
     * 
     * @param event the event to handle
     */
    public void handleNewEvent(Event event) {
        assertSessionSequence();
        
        if (!useEventEqualityForTaskComparison) {
            String description = event.getType().getName() + " \u21D2 " + event.getTarget();
            IEventTask eventTask = taskFactory.createNewEventTask(description);
            taskBuilder.addExecutedTask
                (currentSession, taskFactory.createNewTaskInstance(eventTask, event));
        }
        else {
            IEventTask eventTask = eventTasks.get(event);
            if (eventTask == null) {
                String description = event.getType().getName() + " \u21D2 " + event.getTarget();
                eventTask = taskFactory.createNewEventTask(description);
                eventTasks.put(event, eventTask);
            }
            
            taskBuilder.addExecutedTask
                (currentSession, taskFactory.createNewTaskInstance(eventTask, event));
        }
    }

    /**
     * <p>
     * used to denote, that all previously added events using {@link #handleNewEvent(Event)}
     * belong to the same session and that this session is now complete. All further events
     * will be added to a new session which may be ended using this method, as well.
     * </p>
     */
    public void finishSession() {
        if ((currentSession != null) && (currentSession.size() > 0)) {
            sessions.add(currentSession);
            currentSession = null;
        }
    }

    /**
     * <p>
     * returns the task model, that belongs to the events in the user sessions collected so far.
     * </p>
     * 
     * @return the task model
     */
    public synchronized ITaskModel getTaskModel() {
        finishSession();
        
        Console.traceln
            (Level.INFO, "applying temporal relationship generation rules for detecting tasks");
        
        ComponentManager.getTemporalRelationshipRuleManager().applyTaskDetectionRule
            (sessions, minimalTaskEquality, minimumSequenceCoverage);

        return taskFactory.createTaskModel(sessions);
    }

    /**
     * <p>
     * merges similar tasks in the given task model and returns a condensed task model
     * </p>
     * 
     * @param inputModel the model with the tasks to be merged
     * 
     * @return as described
     */
    public synchronized ITaskModel mergeSimilarTasks(ITaskModel inputModel) {
        Console.traceln
            (Level.INFO, "applying temporal relationship generation rules for merging tasks");
        
        sessions = inputModel.getUserSessions();
        
        ComponentManager.getTemporalRelationshipRuleManager().applyTaskMergingRule(sessions);

        return taskFactory.createTaskModel(sessions);
    }

    /**
     * <p>
     * internally asserts that there is a current session to add new events to
     * </p>
     */
    private void assertSessionSequence() {
        if (currentSession == null) {
            currentSession = taskFactory.createUserSession();
        }
    }

}
