import java.lang.reflect.*;
import java.io.*;

/**
 * Provide some tests of visibility modifiers for a list of classes.
 */
public class StructureChecker {
  
  /**
   * log some messages.
   */
  private Logger logger;
  
  /**
   * reader for this StructureChecker
   */
  private BufferedReader br;
  
  /**
   * Number of line in file being read by br
   */
  private int brLineNumber;
  
  /**
   * create a StructureChecker with the user's logger
   */
  public StructureChecker (Logger logger) {
    this.logger= logger;
  }
  
  /**
   * Check the members of className for forbidden (combinations of)
   * modifiers (represented by badBits), using memberType to distinguish
   * methods and constructors from fields, and logging message to the log
   * for test number testCase.
   * 
   * @param className The name of the class being checked.
   * @param badBits Sum of bits representing a bad set of modifiers, 
   * @see java.lang.reflect.Modifier.
   * @param memberType One of "constructor", "method", "field"
   * @param message Message flagging prohibited modifiers
   * @param testCase Test case number
   * 
   * @return The number of members with bad modifiers.
   */
  
  public int badModifierCount(String className,
                              int badBits,
                              String memberType,
                              String message,
                              int testCase) throws Exception {
    int count= 0;
    Member[] member;
    Class c= Class.forName(className);
    
    if (memberType.equals("method")) {
      member= c.getDeclaredMethods();
    }
    else if (memberType.equals("field")) {
      member= c.getDeclaredFields();
    }
    else {
      return -1;
    }
    
    for (int i= 0; i < member.length; i++) {
      if ((member[i].getModifiers() & badBits) ==
          badBits) {
        count++;
        logger.logMessage(testCase, message + className + ": "+ member[i].getName());
      }
    }
    return count;
  }
  
  /**
   * Count the number of times a method or field identifier starts
   * with something other than a lower-case alphabetic character in
   * className, and append message to log for test case number testCase.
   * 
   * @param className Name of class to check.
   * @param message Flag a badly-capitalized member name.
   * @param testCase Index of this test case.
   * 
   * @return Number of bad capitalizations
   */
  public int badCapitalizationCount(String className, 
                                    String message, 
                                    int testCase) throws Exception { 
    int count= 0;
    Class c= Class.forName(className);
    Member[] field= c.getDeclaredFields(),
      method= c.getDeclaredMethods();
    
    for (int i= 0; i < field.length; i++) {
      if (!(Character.isLetter(field[i].getName().charAt(0)) &&
            Character.isLowerCase(field[i].getName().charAt(0)))) {
        count++;
        logger.logMessage(testCase, message + "field " + field[i].getName() +
                          ", class " + className);
      }
    }
    
    for (int i= 0; i < method.length; i++) {
      if (!(Character.isLetter(method[i].getName().charAt(0)) &&
            Character.isLowerCase(method[i].getName().charAt(0)))) {
        count++;
        logger.logMessage(testCase, message + "method " + method[i].getName() +
                          ", class " + className);
      }
    }
    return count;
  }
  /**
   * Skip commented lines of code.
   * 
   * 
   * Caveat: Assumes that comments and code aren't mixed on one line,
   *         and file pointed to by br exists.
   */
  private String getNextCodeLine() throws Exception {
    String nextLine= br.readLine();
    brLineNumber++;
    String doubleSlash= "^\\s*//.*$",
      slashStarStarSlash= "^\\s*/\\*.*\\*/\\s*$",
      slashStar= "^\\s*/\\*.*$",
      starSlash= "^.*\\*/.*$",
      endDoubleSlash= "^.*//.*$";
    boolean inComment= false;
    
    while (nextLine != null) {
      if (nextLine.matches(doubleSlash)) {
        nextLine= br.readLine();
      }
      else if (nextLine.matches(endDoubleSlash)) {
        nextLine= nextLine.substring(0, nextLine.indexOf("//"));
      }
      else if (nextLine.matches(slashStarStarSlash)) {
        nextLine= br.readLine();
      }
      else if (nextLine.matches(slashStar)) {
        inComment= true;
        nextLine= br.readLine();
      }
      else if (nextLine.matches(starSlash)) {
        inComment= false;
        nextLine= br.readLine();
      }
      else if (inComment) {
        nextLine= br.readLine();
      }
      else {
        return nextLine; // yippee! It's not a comment unless they
        // mixed comment and code on a line.
      }
      brLineNumber++; // once for every case where we read a line
    }
    return nextLine; // end of file
  }
  /**
   * count the number of while or for loops, or if statements
   * in <code>file</code>, which should exist and be readable.
   * 
   * @param file Name of file to be checked.
   * @param testCase Number of this test case
   * 
   * @return Number of for or while loops, or if statements
   * found.
   */
  public int badLoopIfCount(String file, 
                            int testCase) throws Exception {
    int count= 0;
    String forLoop= "^.*\\bwhile\\b\\s*\\(.*$",
      whileLoop= "^.*\\bfor\\b\\s*\\(.*$",
      ifStatement="^.*\\bif\\b.*$";
    br= new BufferedReader(new FileReader(file+".java"));
    String nextLine= getNextCodeLine();
    brLineNumber= 1;
    while (nextLine != null) {
      if (nextLine.matches(whileLoop)) {
        count++;
        logger.logMessage(testCase, "for loop in " + file + ", line " +
                          brLineNumber + ": " + nextLine);
      }
      if (nextLine.matches(forLoop)) {
        count++;
        logger.logMessage(testCase, "while loop in " + file +
                          ", line " + brLineNumber + ": " + nextLine);
      }
      if (nextLine.matches(ifStatement)) {
        count++;
        logger.logMessage(testCase, "if statement in " + file +
                          ", line " + brLineNumber + ": " + nextLine);
      }
      nextLine= getNextCodeLine();
    }
    br.close();
    return count;
  }
  
  /**
   * Count the number of times System.out.print or System.out.println
   * is used in file, log a message for test number 
   * testCase.
   *
   * @param file Name of file to check.
   * @param testCase Number of test case.
   *
   * @return Number of print or println calls
   */
  public int badPrintCount(String file,
                           int testCase) throws Exception {
    int count= 0;
    br= new BufferedReader(new FileReader(file+".java"));
    String systemOutPrint= "^.*\\bSystem\\.out\\.print\\s*\\(.*$",
      systemOutPrintln="^.*\\bSystem\\.out\\.println\\s*\\(.*",
      nextLine= br.readLine();
    brLineNumber= 1;
    while (nextLine != null) {
      if (nextLine.matches(systemOutPrintln)) {
        count++;
        logger.logMessage(testCase, "System.out.print in " + file + ", line " +
                          brLineNumber + ": " + nextLine);
      }
      if (nextLine.matches(systemOutPrintln)) {
        count++;
        logger.logMessage(testCase, "System.out.println in " + file +
                          ", line " + brLineNumber + ": " + nextLine);
      }
      nextLine= getNextCodeLine();
    }
    br.close();
    return count;
  }

  /*
   * Check whether a ourString is contained in file
   * 
   * @param file Name of file to be check
   * @param ourString String to search for
   * @param testCase Number of test case to log go
   */
  public boolean stringFound(String file, String ourString, int testCase) throws Exception {
    br= new BufferedReader(new FileReader(file+".java"));
    String nextLine= br.readLine();
    while (nextLine != null) {
      if (nextLine.matches(ourString)) {
        return true;
      }
      nextLine= br.readLine();
    }
    return false;
  }

  
  public static void main(String[] args) throws Exception {
    String[] file= {"DoublingWindow",
      "ResizingWindow",
      "QuarteringWindow",
      "DayNameWindow",
      "StairWindow",
      "OverlappingWindow",
      "TypoTester",
      "StructureChecker"};
    StructureChecker sc= new StructureChecker(new BasicLogger());
    int[] sinCount= {0, 0, 0, 0, 0, 0, 0};
    for (int i= 0; i < file.length; i++) {
      sinCount[0]+=
        sc.badModifierCount(file[i], Modifier.STATIC, "method",
                            "static method found in ", 0);
      sinCount[1]+= 
        sc.badModifierCount(file[i], Modifier.STATIC, "field",
                            "static field found in ", 1);
      sinCount[2]+=
        sc.badModifierCount(file[i], Modifier.PUBLIC, "field",
                            "public field found in ", 2);
      sinCount[3]+=
        sc.badModifierCount(file[i], Modifier.PRIVATE, "method",
                            "private method found in ", 3);
      sinCount[4]+=
        sc.badCapitalizationCount(file[i], "Bad capitalization: ", 4);
      sinCount[5]+=
        sc.badLoopIfCount(file[i], 5);
      sinCount[6]+=
        sc.badPrintCount(file[i], 6);
    }
    for (int i= 0; i < 7; i++) {
      System.out.println(sinCount[i] + " sin(s) of type " + i);
    }
  }
}


