//-------------------------------------------------------------------------------------------------
// Module    : $RCSfile: TextInputStatisticsRule.java,v $
// Version   : $Revision: 0.0 $  $Author: pharms $  $Date: 16.07.2012 $
// Project   : UsabilityEvaluationManager
// Creation  : 2012 by pharms
// Copyright : Patrick Harms, 2012
//-------------------------------------------------------------------------------------------------
package de.ugoe.cs.quest.usability;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.ugoe.cs.quest.eventcore.guimodel.TextArea;
import de.ugoe.cs.quest.eventcore.guimodel.TextField;
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: 16.07.2012$
 * @author 2012, last modified by $Author: pharms$
 */
//-------------------------------------------------------------------------------------------------
public class TextInputStatisticsRule implements de.ugoe.cs.quest.usability.UsabilityEvaluationRule
{

  //-----------------------------------------------------------------------------------------------
  /* (non-Javadoc)
   * @see de.ugoe.cs.usability.UsabilityEvaluationRule#evaluate(TaskTree)
   */
  //-----------------------------------------------------------------------------------------------
  @Override
  public UsabilityEvaluationResult evaluate(TaskTree taskTree)
  {
    TextInputStatistics statistics = new TextInputStatistics();
    calculateStatistics(taskTree.getRoot(), statistics);
    
    UsabilityEvaluationResult results = new UsabilityEvaluationResult();
    analyzeStatistics(statistics, results);
    
    return results;
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   * @param statistics
   * @param results
   */
  //-----------------------------------------------------------------------------------------------
  private void analyzeStatistics(TextInputStatistics statistics, UsabilityEvaluationResult results)
  {
    checkTextInputRatio(statistics, results);
    checkTextFieldEntryRepetitions(statistics, results);
    checkTextFieldNoLetterOrDigitInputs(statistics, results);
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   * @param statistics
   * @param results 
   */
  //-----------------------------------------------------------------------------------------------
  private void checkTextInputRatio(TextInputStatistics       statistics,
                                   UsabilityEvaluationResult results)
  {
    float allTextFieldInputs =
      statistics.getNoOfTextFieldInputs() + statistics.getNoOfTextAreaInputs();
    
    float ratio = allTextFieldInputs / (float) statistics.getNoOfAllInteractions();
    
    UsabilityDefectSeverity severity = null;
    if (ratio > 0.9)
    {
      severity = UsabilityDefectSeverity.HIGH;
    }
    else if (ratio > 0.7)
    {
      severity = UsabilityDefectSeverity.MEDIUM;
    }
    else if (ratio > 0.5)
    {
      severity = UsabilityDefectSeverity.LOW;
    }
    else if (ratio > 0.3)
    {
      severity = UsabilityDefectSeverity.INFO;
    }
    
    if (severity != null)
    {
      Map<String, String> parameters = new HashMap<String, String>();
      parameters.put("textInputRatio", DecimalFormat.getInstance().format(ratio * 100) + "%");
      
      results.addDefect
        (new UsabilityDefect
           (severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_RATIO, parameters));
    }
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   * @param statistics
   * @param results
   */
  //-----------------------------------------------------------------------------------------------
  private void checkTextFieldEntryRepetitions(TextInputStatistics       statistics,
                                              UsabilityEvaluationResult results)
  {
    Map<String, Integer> words = new HashMap<String, Integer>();
    int numberOfRepeatedWords = 0;
    int maxRepetitions = 0;

    for (int i = 0; i < statistics.getNoOfTextFieldInputs(); i++)
    {
      String[] fragments = statistics.getTextFieldInputFragments(i);
      for (String fragment : fragments)
      {
        if (!"".equals(fragment.trim()))
        {
          Integer count = words.get(fragment);
          if (count == null)
          {
            words.put(fragment, 1);
          }
          else
          {
            count++;
            words.put(fragment, count);
            maxRepetitions = Math.max(count, maxRepetitions);

            if (count == 2)
            {
              // do not calculate repeated words several times
              numberOfRepeatedWords++;
            }
          }
        }
      }
    }
    
    UsabilityDefectSeverity severity = null;
    if ((numberOfRepeatedWords > 10) || (maxRepetitions > 10))
    {
      severity = UsabilityDefectSeverity.HIGH;
    }
    else if ((numberOfRepeatedWords > 4) || (maxRepetitions > 4))
    {
      severity = UsabilityDefectSeverity.MEDIUM;
    }
    else if ((numberOfRepeatedWords > 2) || (maxRepetitions > 2))
    {
      severity = UsabilityDefectSeverity.LOW;
    }
    else if ((numberOfRepeatedWords > 1) || (maxRepetitions > 1))
    {
      severity = UsabilityDefectSeverity.INFO;
    }
    
    if (severity != null)
    {
      Map<String, String> parameters = new HashMap<String, String>();
      parameters.put("textRepetitionRatio", numberOfRepeatedWords + " repeated tokens, up to " +
                     maxRepetitions + " repetitions per token");

      results.addDefect
        (new UsabilityDefect
           (severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_REPETITIONS, parameters));
    }
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   * @param statistics
   * @param results
   */
  //-----------------------------------------------------------------------------------------------
  private void checkTextFieldNoLetterOrDigitInputs(TextInputStatistics       statistics,
                                                   UsabilityEvaluationResult results)
  {
    int allCharactersCount = 0;
    int noLetterOrDigitCount = 0;

    for (int i = 0; i < statistics.getNoOfTextFieldInputs(); i++)
    {
      String[] fragments = statistics.getTextFieldInputFragments(i);
      for (String fragment : fragments)
      {
        String effectiveFragment = fragment.trim();
        for (int j = 0; j < effectiveFragment.length(); j++)
        {
          if (!Character.isWhitespace(effectiveFragment.charAt(j)))
          {
            if (!Character.isLetterOrDigit(effectiveFragment.charAt(j)))
            {
              noLetterOrDigitCount++;
            }
            allCharactersCount++;
          }
        }
      }
    }
    
    float ratio = (float) noLetterOrDigitCount / (float) allCharactersCount;
    
    UsabilityDefectSeverity severity = null;
    if (ratio > 0.1) // every 10th sign
    {
      severity = UsabilityDefectSeverity.HIGH;
    }
    else if (ratio > 0.05) // every 20th sign
    {
      severity = UsabilityDefectSeverity.MEDIUM;
    }
    else if (ratio > 0.02) // every 50th sign
    {
      severity = UsabilityDefectSeverity.LOW;
    }
    else if (ratio > 0.01) // every 100th sign
    {
      severity = UsabilityDefectSeverity.INFO;
    }
    
    if (severity != null)
    {
      Map<String, String> parameters = new HashMap<String, String>();
      parameters.put("noLetterOrDigitRatio", allCharactersCount + " entered characters of " +
                     "which " + noLetterOrDigitCount + " were no letter or digit");

      results.addDefect
        (new UsabilityDefect
           (severity, UsabilityDefectDescription.TEXT_FIELD_NO_LETTER_OR_DIGIT_RATIO, parameters));
    }
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   * @param taskTree
   * @param statistics
   */
  //-----------------------------------------------------------------------------------------------
  private void calculateStatistics(TaskTreeNode node, TextInputStatistics statistics)
  {
    if (node instanceof TextInputInteractionTask)
    {
      calculateStatistics((TextInputInteractionTask) node, statistics);
    }
    else
    {
      if ((node.getChildren() == null) ||
          (node.getChildren().size() == 0))
      {
        statistics.incrementNoOfOtherInteractionTasks();
      }
      else
      {
        for (TaskTreeNode child : node.getChildren())
        {
          calculateStatistics(child, statistics);
        }
      }
    }
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   * @param taskTree
   * @param statistics
   */
  //-----------------------------------------------------------------------------------------------
  private void calculateStatistics(TextInputInteractionTask node, TextInputStatistics statistics)
  {
    String[] fragments = determineTextFragments(node.getEnteredText());
    
    if (node.getGUIElement() instanceof TextField)
    {
      statistics.addTextFieldInput(node, fragments);
    }
    else if (node.getGUIElement() instanceof TextArea)
    {
      statistics.addTextAreaInput(node, fragments);
    }
  }
  
  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   * @param enteredText
   * @return
   */
  //-----------------------------------------------------------------------------------------------
  private String[] determineTextFragments(String enteredText)
  {
    List<String> fragments = new ArrayList<String>();
    
    StringBuffer fragment = new StringBuffer();
    char lastChar = 0;
    
    for (int i = 0; i < enteredText.length(); i++)
    {
      char currentChar = enteredText.charAt(i);
      
      if (!isEqualCharacterType(lastChar, currentChar))
      {
        // the previous fragment ended. so finalize it and start a new one
        if ((fragment != null) && (fragment.length() > 0))
        {
          fragments.add(fragment.toString());
          fragment = new StringBuffer();
        }
      }

      fragment.append(currentChar);
      lastChar = currentChar;
    }
    
    if ((fragment != null) && (fragment.length() > 0))
    {
      fragments.add(fragment.toString());
    }
    
    return fragments.toArray(new String[fragments.size()]);
  }

  //-----------------------------------------------------------------------------------------------
  /**
   * TODO: comment
   *
   * @param lastChar
   * @param currentChar
   * @return
   */
  //-----------------------------------------------------------------------------------------------
  private boolean isEqualCharacterType(char char1, char char2)
  {
    return
      ((char1 == char2) ||
       (Character.isWhitespace(char1) && Character.isWhitespace(char2)) ||
       (Character.isDigit(char1) && Character.isDigit(char2)) ||
       (Character.isLetter(char1) && Character.isLetter(char2)) ||
       (Character.isJavaIdentifierPart(char1) && Character.isJavaIdentifierPart(char2)));
  }

  //-------------------------------------------------------------------------------------------------
  /**
   * TODO comment
   * 
   * @version $Revision: $ $Date: 16.07.2012$
   * @author 2012, last modified by $Author: pharms$
   */
  //-------------------------------------------------------------------------------------------------
  public static class TextInputStatistics
  {
    /** */
    private List<Object[]> mTextFieldInputs = new ArrayList<Object[]>();
    
    /** */
    private List<Object[]> mTextAreaInputs = new ArrayList<Object[]>();
    
    /** */
    private int mOtherInteractionsCount;
    
    //-----------------------------------------------------------------------------------------------
    /**
     * TODO: comment
     * @param node 
     * @param fragments 
     *
     */
    //-----------------------------------------------------------------------------------------------
    public void addTextFieldInput(TextInputInteractionTask node, String[] fragments)
    {
      mTextFieldInputs.add(new Object[] { node, fragments });
    }

    //-----------------------------------------------------------------------------------------------
    /**
     * TODO: comment
     * @param node 
     * @param fragments 
     *
     */
    //-----------------------------------------------------------------------------------------------
    public void addTextAreaInput(TextInputInteractionTask node, String[] fragments)
    {
      mTextAreaInputs.add(new Object[] { node, fragments });
    }

    //-----------------------------------------------------------------------------------------------
    /**
     * TODO: comment
     *
     * @return
     */
    //-----------------------------------------------------------------------------------------------
    public int getNoOfAllInteractions()
    {
      return mTextFieldInputs.size() + mTextAreaInputs.size() + mOtherInteractionsCount;
    }

    //-----------------------------------------------------------------------------------------------
    /**
     * TODO: comment
     *
     * @return
     */
    //-----------------------------------------------------------------------------------------------
    public int getNoOfTextFieldInputs()
    {
      return mTextFieldInputs.size();
    }

    //-----------------------------------------------------------------------------------------------
    /**
     * TODO: comment
     *
     * @param i
     * @return
     */
    //-----------------------------------------------------------------------------------------------
    public String[] getTextFieldInputFragments(int index)
    {
      return (String[]) mTextFieldInputs.get(index)[1];
    }

    //-----------------------------------------------------------------------------------------------
    /**
     * TODO: comment
     *
     * @return
     */
    //-----------------------------------------------------------------------------------------------
    public int getNoOfTextAreaInputs()
    {
      return mTextAreaInputs.size();
    }

    //-----------------------------------------------------------------------------------------------
    /**
     * TODO: comment
     *
     * @param i
     * @return
     */
    //-----------------------------------------------------------------------------------------------
    public String[] getTextAreaInputFragments(int index)
    {
      return (String[]) mTextAreaInputs.get(index)[1];
    }

    //-----------------------------------------------------------------------------------------------
    /**
     * TODO: comment
     *
     */
    //-----------------------------------------------------------------------------------------------
    public void incrementNoOfOtherInteractionTasks()
    {
      mOtherInteractionsCount++;
    }

    
  }

}
