package lawOfDemeter;

import java.util.*;
import org.aspectj.lang.JoinPoint;

/**
 * The StatMonitor aspect is part of a Law of Demeter Checker - a set of
 * classes and aspects that verify a given program does not violate
 * the Law of Demeter.  This aspect monitors all of the checks made by the
 * Checker aspect, keeping track of various statistics.
 *
 * An interesting feature of this design is that this aspect uses pointcuts
 * defined in the checker aspect that describe methods in the checker aspect
 * which signal what type of check has been made.  This is has many similarities
 * to an event throw -> catch implimentation, but the classes invloved do not
 * need to impliment the standard Java Interfaces that would normally be involved.
 *
 * @author Modifications made by Paul Freeman
 */

public aspect StatMonitor {

  // statistcs variables initialized before every main method execution
  private long totalChecks;
  private long numViolations;
  private Vector validChecks;

  /**
   * @return Returns the total number of checks made.
   */
  public long getTotalChecked(){
    return totalChecks;
  }

  /**
   * @return Returns the total number of violations found.
   */
  public long getTotalViolations(){
    return numViolations;
  }

  /**
   * @return Returns the number of valid checks as a Vector where:<br>
   * - position 0 = total valid checks <br>
   * - position i = total valid checks where i = num of matches perCheck <br>
   */
  public Vector getValidChecks(){
    return validChecks;
  }

  // initialize statistical variables
  before():
    Any.MainMethodExecution(){
      totalChecks = 0;
      numViolations = 0;
      validChecks = new Vector();
      validChecks.add(new Long(0)); // set the total valid counts = 0
  }

  // increase the total count of checks
  before():
    Any.ToCheck(){
      totalChecks++;
  }

  // increase the violation count
  before(JoinPoint tjp):
    Checker.Violation(tjp){
      numViolations++;
  }

  // increase the correct count
  before(JoinPoint tjp, Vector binsIn):
    Checker.Success(tjp, binsIn) {
      addToStats(binsIn.size());
  }

  // Keeps track of the valid count statistics
  private void addToStats(int binCount){
    //initialize count being increased if necessary
    try{
      if(validChecks.elementAt(binCount) == null)
        validChecks.setElementAt(new Long(0), binCount);
    }catch(java.lang.ArrayIndexOutOfBoundsException e){
      validChecks.setSize(binCount + 1);
      validChecks.setElementAt(new Long(0), binCount);
    }

    // increase counts
    Long val = new Long(((Long)validChecks.elementAt(0)).longValue() + 1);  // total count
    validChecks.setElementAt(val, 0);
    val = new Long(((Long)validChecks.elementAt(binCount)).longValue() + 1);
    validChecks.setElementAt(val, binCount);
  }

  // prints statistics when program is done executing
  after():
    Any.MainMethodExecution(){
      long totalValid = ((Long)validChecks.elementAt(0)).longValue();

      // produce headings
      System.out.println("\nStatistics:");
      System.out.println("Type        \t\tCount\t%Total\t%Valid");
      System.out.println("Total Checks\t\t" + totalChecks);

      // produce violation statistics
      System.out.println("Violations  \t\t" + numViolations +
        "\t"+ percent(numViolations, totalChecks, 2));

      // produce valid percents
      long thisCount;
      for(int i = 0; i < validChecks.size(); i++){
        String statement;
        if(i==0){statement = "Valid Checks";}
        else if(i==1){statement = "As "+i+" Supplier";}
        else{statement = "As "+i+" Suppliers";}

        if(validChecks.elementAt(i) == null){
          thisCount = 0;
        }else{
          thisCount = ((Long)validChecks.elementAt(i)).longValue();
        }
        statement += "\t\t" + thisCount +
          "\t"+ percent(thisCount, totalChecks, 2);
        // add the % of valid stat if applicable
        if(i != 0){
          statement += "\t" + percent(thisCount, totalValid, 2);
        }

        System.out.println(statement);
      }
  }

  // returns a double representing the percent of the numerator in the denominator
  private double percent(long noom, long denom, int numDec){
    if(denom == 0){return 0;}
    return fix((double)noom/(double)denom * 100, numDec);
  }

  // fixes a double to a certain number of decimal places - does not round
  private double fix(double aDouble, int numDec){
    try{
      String d = new String(new Double(aDouble).toString());
      int dotPos = d.indexOf('.');
      int len = d.length();
      int lastPos = dotPos + numDec + 1;
      if(lastPos > len){lastPos = len;}
      return new Double(d.substring(0, lastPos)).doubleValue();
    }catch(java.lang.NumberFormatException e){
      return 0;
    }
  }
}
