//   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 java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.eventcore.IEventTarget;
import de.ugoe.cs.autoquest.eventcore.IEventType;
import de.ugoe.cs.autoquest.eventcore.StringEventType;
import de.ugoe.cs.autoquest.eventcore.gui.TextInput;
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.ITaskBuilder;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskFactory;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstanceList;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession;
import de.ugoe.cs.autoquest.test.DummyGUIElement;
import de.ugoe.cs.autoquest.test.DummyTextField;

/**
 * TODO comment
 * 
 * @version $Revision: $ $Date: 01.04.2012$
 * @author 2012, last modified by $Author: patrick$
 */
public class TaskTreeDecoder {
    
    /** */
    private static Pattern taskInstancePattern = Pattern.compile("([^{}]+)\\{|\\}");
    
    /** */
    private static Pattern taskInstanceDetailsPattern =
        Pattern.compile("\\s*(\\w*)\\s*([\\w\\(\\)\"]*)\\s*(\\(([\\w*\\s*]*)\\))?((\\w*)|(\".*\"))?");

    /** */
    private ITaskFactory taskFactory;
    
    /** */
    private ITaskBuilder taskBuilder;
    
    /** */
    Map<String, IEventTarget> targets = new HashMap<String, IEventTarget>();
    
    /** */
    Map<String, ITask> tasks = new HashMap<String, ITask>();
    
    /**
     *
     */
    public TaskTreeDecoder(ITaskFactory taskFactory, ITaskBuilder taskBuilder) {
        super();
        this.taskFactory = taskFactory;
        this.taskBuilder = taskBuilder;
    }

    /**
     *
     */
    public ITaskInstanceList decode(String taskTreeSpec) {
        ITaskInstanceList taskInstanceList = null;

        Matcher taskMatcher = taskInstancePattern.matcher(taskTreeSpec);

        if (taskMatcher.find()) {
            taskInstanceList = parseTaskInstanceList(taskMatcher);
        }
        
        if (taskMatcher.find()) {
            throw new IllegalArgumentException("too many tasks specified");
        }
        
        return taskInstanceList;
    }

    /**
     * 
     */
    private ITaskInstanceList parseTaskInstanceList(Matcher taskMatcher) {
        if ("}".equals(taskMatcher.group(1))) {
            throw new IllegalArgumentException("invalid task instance list specification");
        }
        
        String taskDetails = taskMatcher.group(1);
        
        Matcher matcher = taskInstanceDetailsPattern.matcher(taskDetails);
        
        if (!matcher.find()) {
            throw new IllegalArgumentException("could not parse task details");
        }

        String type = matcher.group(1);
        
        ITaskInstanceList list;
        
        if ("UserSession".equals(type)) {
            list = taskFactory.createUserSession();
        }
        else if ("TaskInstances".equals(type)) {
            list = taskFactory.createNewTaskInstance(taskFactory.createNewSequence());
        }
        else {
            throw new IllegalArgumentException("unknown type of task instance list: " + type);
        }
        
        while (taskMatcher.find() && !"}".equals(taskMatcher.group(0))) {
            ITaskInstance childInstance = parseTaskInstance(taskMatcher);
            
            if (!(list instanceof IUserSession)) {
                taskBuilder.addChild
                    ((ISequence) ((ITaskInstance) list).getTask(), childInstance.getTask());
            }

            taskBuilder.addTaskInstance(list, childInstance);
        }

        return list;
    }

    /**
     * 
     */
    private ITaskInstance parseTaskInstance(Matcher taskMatcher) {
        if ("}".equals(taskMatcher.group(1))) {
            throw new IllegalArgumentException("invalid task instance specification");
        }
        
        String taskDetails = taskMatcher.group(1);
        
        Matcher matcher = taskInstanceDetailsPattern.matcher(taskDetails);
        
        if (!matcher.find()) {
            throw new IllegalArgumentException("could not parse task details");
        }

        String type = matcher.group(1);
        String id = matcher.group(2);
        
        ITask task = tasks.get(id);
        
        if (task == null) {
            if ("Sequence".equals(type)) {
                task = taskFactory.createNewSequence();
            }
            else if ("Selection".equals(type)) {
                task = taskFactory.createNewSelection();
            }
            else if ("Iteration".equals(type)) {
                task = taskFactory.createNewIteration();
            }
            else if ("Optional".equals(type)) {
                task = taskFactory.createNewOptional();
            }
            else {
            	task = createUserInteractionTaskInstance(matcher).getTask();
            }  
            tasks.put(id, task);
        }
        
        ITaskInstance instance;
        
        if (task instanceof ISequence) {
            instance = taskFactory.createNewTaskInstance((ISequence) task);
        }
        else if (task instanceof ISelection) {
            instance = taskFactory.createNewTaskInstance((ISelection) task);
        }
        else if (task instanceof IIteration) {
            instance = taskFactory.createNewTaskInstance((IIteration) task);
        }
        else if (task instanceof IOptional) {
            instance = taskFactory.createNewTaskInstance((IOptional) task);
        }
        else {
            instance = taskFactory.createNewTaskInstance
                 ((IEventTask) task,
                  ((IEventTaskInstance) task.getInstances().iterator().next()).getEvent());
        }  
        
        while (taskMatcher.find() && !"}".equals(taskMatcher.group(0))) {
            ITaskInstance childInstance = parseTaskInstance(taskMatcher);
            
            if (task instanceof ISequence) {
                taskBuilder.addChild((ISequence) task, childInstance.getTask());
            }
            else if (task instanceof ISelection) {
                taskBuilder.addChild((ISelection) task, childInstance.getTask());
            }
            else if (task instanceof IIteration) {
                if (((IIteration) task).getMarkedTask() == null) {
                    taskBuilder.setMarkedTask((IIteration) task, childInstance.getTask());
                }
                else if (!((IIteration) task).getMarkedTask().equals(childInstance.getTask())) {
                    throw new IllegalArgumentException
                        ("can not add more than one child to an iteration");
                }
            }
            else if (task instanceof IOptional) {
                if (((IOptional) task).getMarkedTask() == null) {
                    taskBuilder.setMarkedTask((IOptional) task, childInstance.getTask());
                }
                else if (!((IOptional) task).getMarkedTask().equals(childInstance.getTask())) {
                    throw new IllegalArgumentException
                        ("can not add more than one child to an optional");
                }
            }
            else {
                throw new IllegalArgumentException("can not add children to something that is no " +
                                                   "sequence, selection, iteration, or optional");
            }
            
            if (instance instanceof ISequenceInstance) {
                taskBuilder.addChild((ISequenceInstance) instance, childInstance);
            }
            else if (instance instanceof ISelectionInstance) {
                taskBuilder.setChild((ISelectionInstance) instance, childInstance);
            }
            else if (instance instanceof IIterationInstance) {
                taskBuilder.addChild((IIterationInstance) instance, childInstance);
            }
            else if (instance instanceof IOptionalInstance) {
                taskBuilder.setChild((IOptionalInstance) instance, childInstance);
            }
        }

        return instance;
    }

    /**
     * <p>
     * </p>
     *
     * @param matcher
     * @return
     */
    private ITaskInstance createUserInteractionTaskInstance(Matcher matcher) {
        String evenType = matcher.group(1);
        String id = matcher.group(2);
        IEventTarget eventTarget = targets.get(id);
        if (eventTarget == null) {
            eventTarget = determineTarget(evenType, id, matcher.group(4));
            targets.put(id, eventTarget);
        }
        IEventType eventType = determineType(evenType, matcher.group(4));
        IEventTask task = taskFactory.createNewEventTask(eventType + " --> " + eventTarget);
        
        return taskFactory.createNewTaskInstance(task, new Event(eventType, eventTarget));
    }

    /**
     * <p>
     * </p>
     *
     * @param type
     * @param id
     * @param enteredText
     * @return
     */
    private IEventTarget determineTarget(String type, String id, String enteredText) {
        if ("TextInput".equals(type) && enteredText != null) {
            return new DummyTextField(enteredText);
        }
        else {
            return new DummyGUIElement(id);
        }
    }

    /**
     * <p>
     * </p>
     *
     * @param type
     * @param enteredText
     * @return
     */
    private IEventType determineType(String type, String enteredText) {
        if ("TextInput".equals(type) && enteredText != null) {
            return new TextInput(enteredText, new ArrayList<Event>());
        }
        else {
            return new StringEventType(type);
        }
    }

}
