//-------------------------------------------------------------------------------------------------
// Module    : $RCSfile: TaskTreeChecker.java,v $
// Version   : $Revision: 0.0 $  $Author: patrick $  $Date: 01.04.2012 $
// Project   : TestUtils
// Creation  : 2012 by patrick
// Copyright : Patrick Harms, 2012
//-------------------------------------------------------------------------------------------------
package de.ugoe.cs.quest.tasktrees.testutils;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.ugoe.cs.quest.tasktrees.treeifc.InteractionTask;
import de.ugoe.cs.quest.tasktrees.treeifc.Iteration;
import de.ugoe.cs.quest.tasktrees.treeifc.NodeInfo;
import de.ugoe.cs.quest.tasktrees.treeifc.Selection;
import de.ugoe.cs.quest.tasktrees.treeifc.Sequence;
import de.ugoe.cs.quest.tasktrees.treeifc.TaskTree;
import de.ugoe.cs.quest.tasktrees.treeifc.TaskTreeNode;
import de.ugoe.cs.quest.tasktrees.treeifc.TextInputInteractionTask;

//-------------------------------------------------------------------------------------------------
/**
 * TODO comment
 * 
 * @version $Revision: $ $Date: 01.04.2012$
 * @author 2012, last modified by $Author: patrick$
 */
//-------------------------------------------------------------------------------------------------
public class TaskTreeChecker
{
  /** */
  private boolean mDoTrace;
  
  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   */
  //-----------------------------------------------------------------------------------------------
  public TaskTreeChecker()
  {
    this(false);
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   */
  //-----------------------------------------------------------------------------------------------
  public TaskTreeChecker(boolean doTrace)
  {
    mDoTrace = doTrace;
  }

  //-----------------------------------------------------------------------------------------------
  /**
   *
   */
  //-----------------------------------------------------------------------------------------------
  public void assertTaskTree(String taskTreeSpec, TaskTree taskTree)
  {
    Map<TaskTreeNode, Integer> taskMapCopy = new HashMap<TaskTreeNode, Integer>();
    
    for (Map.Entry<TaskTreeNode, NodeInfo> entry : taskTree.getTaskMap().entrySet())
    {
      if (entry.getValue().getNoOfOccurencesInTree() > 0)
      {
        taskMapCopy.put(entry.getKey(), entry.getValue().getNoOfOccurencesInTree());
      }
      else
      {
        taskMapCopy.put(entry.getKey(), 1);
      }
    }
    
    if (mDoTrace)
    {
      dumpTaskMap(taskMapCopy);
    }

    TaskSpec task = null;

    Matcher matcher =
      Pattern.compile("(\\s*(\\w+)\\s+(\\w+)\\s+((\\w*\\s)*)(\\{))|((\\}))").matcher(taskTreeSpec);
    
    do
    {
      if (!matcher.find())
      {
        if (!matcher.hitEnd())
        {
          throw new IllegalArgumentException("could not parse task specification");
        }
        else
        {
          break;
        }
      }
      
      task = parseTask(matcher);
      if (task != null)
      {
        assertTaskAndChildrenInMapAndRemove(task, taskMapCopy);
      }
    }
    while (task != null);
    
    assertTrue("more tasks in map, than expected", taskMapCopy.isEmpty());
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   * @param taskTree
   */
  //-----------------------------------------------------------------------------------------------
  public void dumpAsCheckString(TaskTree taskTree)
  {
    dumpNodeAsCheckString(taskTree.getRoot(), new int[4], "");
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   * @param root
   * @param string
   */
  //-----------------------------------------------------------------------------------------------
  private void dumpNodeAsCheckString(TaskTreeNode node,
                                     int[]        typeCounters,
                                     String       indent)
  {
    System.err.print("       \"");
    System.err.print(indent);
    
    if (node instanceof Sequence)
    {
      System.err.print("Sequence sequence");
      System.err.print(typeCounters[0]++);
      System.err.println(" {\" +");
    }
    else if (node instanceof Iteration)
    {
      System.err.print("Iteration iteration");
      System.err.print(typeCounters[1]++);
      System.err.println(" {\" +");
    }
    else if (node instanceof Selection)
    {
      System.err.print("Selection selection");
      System.err.print(typeCounters[2]++);
      System.err.println(" {\" +");
    }
    else if (node instanceof TextInputInteractionTask)
    {
      System.err.print("TextInputInteraction textInput");
      System.err.print(typeCounters[3]++);
      System.err.print(" ");
      System.err.print(((TextInputInteractionTask) node).getEnteredText());
      System.err.println(" {\" +");
    }
    else if (node instanceof InteractionTask)
    {
      System.err.print("Interaction ");
      System.err.print(((InteractionTask) node).getInteraction().getName());
      System.err.print(" {}\" +");
    }
    else
    {
      fail("unknown type of node in task tree " + node);
    }
    
    for (TaskTreeNode child : node.getChildren())
    {
      dumpNodeAsCheckString(child, typeCounters, indent + "  ");
    }
    
    if (!(node instanceof InteractionTask) ||
        (node instanceof TextInputInteractionTask))
    {
      System.err.print("       \"");
      System.err.print(indent);
      System.err.print("}\" +");
    }
    
    System.err.println();
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   * @param taskTree
   */
  //-----------------------------------------------------------------------------------------------
  public void dumpFullTaskTree(TaskTree taskTree) throws FileNotFoundException
  {
    PrintWriter out = null;
    try
    {
      out = new PrintWriter(new FileOutputStream("taskTree.txt"));
      dumpFullNode(taskTree.getRoot(), out, "");
    }
    finally
    {
      if (out != null)
      {
        out.close();
      }
    }
    
  }

  //-----------------------------------------------------------------------------------------------
  /**
   *
   */
  //-----------------------------------------------------------------------------------------------
  private void dumpFullNode(TaskTreeNode node, PrintWriter out, String indent)
  {
    out.print(indent);
    if (node instanceof Sequence)
    {
      out.println("Sequence");
      out.print(indent);
      out.println("{");
    }
    else if (node instanceof Iteration)
    {
      out.println("Iteration");
      out.print(indent);
      out.println("{");
    }
    else if (node instanceof Selection)
    {
      out.println("Selection");
      out.print(indent);
      out.println("{");
    }
    else if (node instanceof TextInputInteractionTask)
    {
      out.print("TextInputInteraction");
      out.print(" ");
      out.println(((TextInputInteractionTask) node).getEnteredText());
      out.print(indent);
      out.println("{");
    }
    else if (node instanceof InteractionTask)
    {
      out.print(((InteractionTask) node).getInteraction().getName());
      out.print(" ");
      out.print(((InteractionTask) node).getGUIElement());
      out.print(" ");
      out.print(((InteractionTask) node).getGUIElement().getOriginalTypeInfo());
    }
    else
    {
      fail("unknown type of node in task tree " + node);
    }
    
    for (TaskTreeNode child : node.getChildren())
    {
      dumpFullNode(child, out, indent + "  ");
    }
    
    if (!(node instanceof InteractionTask) ||
        (node instanceof TextInputInteractionTask))
    {
      out.print(indent);
      out.print("}");
    }
    
    out.println();
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * 
   */
  //-----------------------------------------------------------------------------------------------
  private TaskSpec parseTask(Matcher tokenMatcher)
  {
    String firstToken = tokenMatcher.group();
    
    if ("}".equals(firstToken))
    {
      throw new IllegalArgumentException("found a closing bracket at an unexpected place");
    }
    
    TaskSpec task = new TaskSpec();
    task.type = tokenMatcher.group(2);
    task.interactionName = tokenMatcher.group(3);
    task.additionalInfo = tokenMatcher.group(4).trim();
    
    if ("".equals(task.interactionName))
    {
      task.interactionName = null;
    }
    
    if (!tokenMatcher.find())
    {
      throw new IllegalArgumentException("could not parse task specification");
    }
    
    firstToken = tokenMatcher.group();
    
    if (!"}".equals(firstToken))
    {
      ArrayList<TaskSpec> children = new ArrayList<TaskSpec>();
    
      TaskSpec child = null;
    
      do
      {
        child = parseTask(tokenMatcher);
        
        if (child != null)
        {
          children.add(child);
          
          if (!tokenMatcher.find())
          {
            throw new IllegalArgumentException("could not parse task specification");
          }
          
          firstToken = tokenMatcher.group();
          
          if ("}".equals(firstToken))
          {
            break;
          }
        }
        
      }
      while (child != null);
      
      task.children = children.toArray(new TaskSpec[children.size()]);
    }
    
    return task;
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * @param task
   * @param taskMapCopy
   */
  //-----------------------------------------------------------------------------------------------
  private void assertTaskAndChildrenInMapAndRemove(TaskSpec                   task,
                                                   Map<TaskTreeNode, Integer> taskMap)
  {
    for (Map.Entry<TaskTreeNode, Integer> entry : taskMap.entrySet())
    {
      if (taskSpecEqualsTask(task, entry.getKey()))
      {
        if (task.children != null)
        {
          for (TaskSpec child : task.children)
          {
            assertTaskAndChildrenInMapAndRemove(child, taskMap);
          }
        }
        
        int count = taskMap.get(entry.getKey());
        if (count == 1)
        {
          taskMap.remove(entry.getKey());
        }
        else
        {
          taskMap.put(entry.getKey(), count - 1);
        }
        return;
      }
    }
    
    fail("expected task " + task.type + " " + task.interactionName + " not included in task map");
  }

  //-----------------------------------------------------------------------------------------------
  /**
   *
   */
  //-----------------------------------------------------------------------------------------------
  private boolean taskSpecEqualsTask(TaskSpec taskSpec, TaskTreeNode task)
  {
    if (mDoTrace)
    {
      System.err.println("comparing " + taskSpec.interactionName + " with");
      dumpTask(task, 0, "");
    }
    
    if (("Interaction".equals(taskSpec.type) && (!(task instanceof InteractionTask))) ||
        ("TextInputInteraction".equals(taskSpec.type) &&
            (!(task instanceof TextInputInteractionTask))) ||
        ("Sequence".equals(taskSpec.type) && (!(task instanceof Sequence))) ||
        ("Selection".equals(taskSpec.type) && (!(task instanceof Selection))) ||
        ("Iteration".equals(taskSpec.type) && (!(task instanceof Iteration))))
    {
      if (mDoTrace)
      {
        System.err.println("task types do not match: " + taskSpec.type + " != " +
                           task.getClass().getSimpleName() + "\n");
      }
      return false;
    }
    else if (!"Interaction".equals(taskSpec.type) &&
             !"TextInputInteraction".equals(taskSpec.type) &&
             !"Sequence".equals(taskSpec.type) &&
             !"Selection".equals(taskSpec.type) &&
             !"Iteration".equals(taskSpec.type))
    {
      fail("unknown task type " + taskSpec.type + " --> please extend test case");
    }
    
    if ("TextInputInteraction".equals(taskSpec.type))
    {
      if (!"".equals(taskSpec.additionalInfo) &&
          !(taskSpec.additionalInfo.equals(((TextInputInteractionTask) task).getEnteredText())))
      {
        if (mDoTrace)
        {
          System.err.println
            ("expected text \"" + taskSpec.additionalInfo + "\" is not equal to the text " +
             "provided by the task \"" + ((TextInputInteractionTask) task).getEnteredText() +
             "\"\n");
        }
        return false;
      }
    }
    else if ((task instanceof InteractionTask) &&
        (((InteractionTask) task).getInteraction() != null) &&
        (!taskSpec.interactionName.equals(((InteractionTask) task).getInteraction().getName())))
    {
      // simple interaction names do not match. But what about the interaction name in combination
      // with the additional info
      String complexInteractionName = taskSpec.interactionName +
        (!"".equals(taskSpec.additionalInfo) ? " " + taskSpec.additionalInfo : "");
      
      if (!complexInteractionName.equals(((InteractionTask) task).getInteraction().getName()))
      {
        if (mDoTrace)
        {
          System.err.println("interaction names do not match: " + taskSpec.interactionName +
                             " != " + ((InteractionTask) task).getInteraction().getName() + "\n");
        }
        return false;
      }
    }
    
    if (((taskSpec.children == null) && (task.getChildren().size() > 0)) ||
        ((taskSpec.children != null) && (taskSpec.children.length != task.getChildren().size())))
    {
      if (mDoTrace)
      {
        System.err.println("numbers of children do not match: " +
                           (taskSpec.children == null ? "0" : taskSpec.children.length) + " != " +
                           (task.getChildren() == null ? "0" : task.getChildren().size()) + "\n");
      }
      return false;
    }
    
    Iterator<TaskTreeNode> children = task.getChildren().iterator();
    if (taskSpec.children != null)
    {
      for (TaskSpec child : taskSpec.children)
      {
        if (!taskSpecEqualsTask(child, children.next()))
        {
          if (mDoTrace)
          {
            System.err.println("one of the children does not match\n");
          }
          return false;
        }
      }
    }
        
    if (!children.hasNext())
    {
      if (mDoTrace)
      {
        System.err.println("nodes match\n");
      }
      return true;
    }
    else
    {
      if (mDoTrace)
      {
        System.err.println("number of children does not match\n");
      }
      return false;
    }
  }

  //-----------------------------------------------------------------------------------------------
  /**
   *
   */
  //-----------------------------------------------------------------------------------------------
  private void dumpTaskMap(Map<TaskTreeNode, Integer> taskMap)
  {
    System.err.println();
    for (Map.Entry<TaskTreeNode, Integer> entry : taskMap.entrySet())
    {
      dumpTask(entry.getKey(), entry.getValue(), "");
      System.err.println();
    }
  }

  //-----------------------------------------------------------------------------------------------
  /**
   *
   */
  //-----------------------------------------------------------------------------------------------
  private void dumpTask(TaskTreeNode task, int count, String indent)
  {
    System.err.print(indent);
    System.err.print(task);
    System.err.print(" ");
    System.err.print(task.getDescription());
    System.err.print(" ");
    
    if (count > 0)
    {
      System.err.print("(");
      System.err.print(count);
      System.err.print(" occurrences)");
    }
    
    System.err.println();
    
    if ((task.getChildren() != null) && (task.getChildren().size() > 0))
    {
      for (TaskTreeNode child : task.getChildren())
      {
        dumpTask(child, 0, indent + "  ");
      }
    }
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * TODO comment
   *
   * @version $Revision: $ $Date: $
   * @author  2011, last modified by $Author: $
   */
  //-----------------------------------------------------------------------------------------------
  private class TaskSpec
  {
    public String type;
    public String interactionName;
    public String additionalInfo;
    public TaskSpec[] children;
  }

}
