//  package Tests;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public abstract class AbstractTest {

    public abstract boolean functionalSuite();
    public abstract boolean structuralSuite();
    public abstract boolean interactionSuite();
    public abstract boolean baselineSuite();
    public abstract boolean regressionSuite();
 
    // PostCondition:  classInvariant will print to the log if the 
    //                 test has failed
    public abstract boolean classInvariant();
    public abstract boolean instantiateOUT();

    public AbstractTest(String reportFile ) {
	
	try{
	    log = new FileWriter(reportFile); 
	}catch(IOException e){
	    System.err.println("Error: could not open file - " 
			       + reportFile);
	    e.printStackTrace();
	}
	
    }

    //------------------------------------------------------
    // The following methods are for recording test output
    
    // NOTE: we really want to reassign standard out and err.
    //       to much information is missed by doing this.
    //       we miss debug messages, printStackTrace messages, 
    //       and other messages to the console. - jlr
    public void logInfo(String logInfo){
	try{
	    System.out.println(logInfo + "\n");
	    log.write(logInfo + "\n");
	}catch(IOException e){
	    e.printStackTrace();
	}
    }


    protected void startTestReport(){
	logInfo("Test Report for "+className+"\n");
    }
    
    protected void logTestResult(String description, boolean result){
	if(result){
	    reportSuccess(description);
	}
	else{
	    reportFailure(description);
	}
    }
    
    protected void reportSuccess(String test){
	logInfo("\nTest "+ test +" succeeded.\n");
    }
    
    protected void reportFailure(String test){
	logInfo("\nTest "+ test +" failed.\n");
    }
    
    protected void finishTestProcess(){
	try{
	    log.close();		
	}catch(IOException e){	
	    e.printStackTrace();
	}
    }
    
    // End of log file methods
    //--------------------------------------------------


    //--------------------------------------------------
    // The following methods use java.lang.reflect.* to:
    // 1) display an object's data member names and values
    // 2) obtain an object's data member value
    // 3) invoke an object's method


    // Precondition: OUT is instantiated as an "OutClassName" and
    //               OUT has a method called "methodName" which takes
    //               the specified "parameterTypes" as paramters.
    //               The .java.policy file must be configured to allow
    //               access to private fields
    // Symantics: to invoke an object's private or protected method 
    // Postcondition: "OUT.methodName(args)" is invoked and the return value
    //                is returned.  The value returned by the underlying
    //                method is automatically wrapped in an object if 
    //                it has a primitive type. 
    protected static Object invokePrivateMethod(Object OUT, 
						String OutClassName,
						String methodName,
						Class[] parameterTypes,
						Object[] args) 
	throws ClassNotFoundException, NoSuchMethodException,
	       IllegalAccessException, IllegalArgumentException,
	       InvocationTargetException, SecurityException {

	Class OUTClass = Class.forName(OutClassName);
	
	Method OUTMethod = OUTClass.getDeclaredMethod(methodName, parameterTypes);
	OUTMethod.setAccessible(true);  // bypass java security
	
	return OUTMethod.invoke(OUT, args);

    }
    

    // Precondition: OUT is instantiated as an "OutClassName" and
    //               OUT has a data member called "fieldName"
    //               The .java.policy file must be configured to allow
    //               access to private fields
    // Symantics:  to obtain the value of private and protected data
    // Postcondition: returns an Object which contains the same
    //                value as "OUT.fieldName".  The returned value
    //                is the same type as "OUT.fieldName"
    protected static Object getPrivateField(Object OUT, 
					    String OutClassName,
					    String fieldName) 
	throws ClassNotFoundException, NoSuchFieldException,
	       IllegalAccessException, SecurityException {

	Class OUTClass = Class.forName(OutClassName);

	Field OUTField = OUTClass.getDeclaredField(fieldName);
	OUTField.setAccessible(true);  // bypass java security
	
	return OUTField.get(OUT);

    }


	// Precondition: OUT is instantiated as an "OutClassName" and
    //               OUT has a data member called "fieldName"
    //               The .java.policy file must be configured to allow
    //               access to private fields
    // Symantics:  to obtain the value of private and protected data
    // Postcondition: returns an Object which contains the same
    //                value as "OUT.fieldName".  The returned value
    //                is the same type as "OUT.fieldName"
    protected static void setPrivateIntField(Object OUT, 
					    String OutClassName,
					    String fieldName,
						int newValue) 
	throws ClassNotFoundException, NoSuchFieldException,
	       IllegalAccessException, SecurityException {

	Class OUTClass = Class.forName(OutClassName);

	Field OUTField = OUTClass.getDeclaredField(fieldName);
	OUTField.setAccessible(true);  // bypass java security
	
	OUTField.setInt(OUT,newValue);

    }


    // Precondition: OUT is instantiated as an "OutClassName" 
    //               The .java.policy file must be configured to allow
    //               access to private fields
    // Symantics:  to display an Objects data member names and values
    // Postcondition: Returns true if operation is successful.
    //                Prints to standard out and the log file the 
    //                "OUT's" data member names and values
    //                Prints to std out and log if failure reading fields
    // NOTE: This does not print well for arrays and non-primitives
    //       non-primitives should override the toString found in 
    //       java.lang.object
    protected boolean showFieldValues(Object OUT, String OutClassName) {
	boolean result;

	try {
	    Class OUTClass = Class.forName(OutClassName);
	    
	    Field[] OUTFields = OUTClass.getDeclaredFields();
	    
	    String fieldString;
	    
	    for (int i=0; i < OUTFields.length; i++ ) {
		OUTFields[i].setAccessible(true);// bypass security permisions
		fieldString = getDefinitionString(OUTFields[i]);
		fieldString += " = " + OUTFields[i].get(OUT);
		
		logInfo(fieldString);	  
	    }
	    result = true;
	}
	catch(Exception e) {
	    logInfo("Unable to print fields: " + e );
	    e.printStackTrace();
	    result = false;
	}
	
	return result;

    }

    // Precondition: "field" access must be set to true.
    // Postcondition: prints to standard out and the log file the 
    //               data member "field's" name 
    // NOTE: This does not print well for arrays and non-primitives
    //       non-primitives should override the toString found in 
    //       java.lang.object
    private static String getDefinitionString(Field field ) {
	String definitionString = "";

	int fieldModifier = field.getModifiers();
	if(fieldModifier != 0 )
	    definitionString = Modifier.toString(fieldModifier) + " ";


	Class type = field.getType();
	String typeString = new String();
	while(type.isArray() ) {
	    typeString += " [] ";
	    type = type.getComponentType();
	}

	definitionString += type.getName() + 
	    typeString + " " + field.getName();

	return definitionString;
    }


    // The end of methods using java.lang.reflect.*
    //--------------------------------------------------

    //--------------------------------------------------
    // The following methods are provide for flow control
    // these methods include methods for menu output and
    // user input.

    // Preconditions: 
    // Symantics: method to obtain and parse user input
    // Postconditions: returns an array of integers, if the user input
    //                 a sequence of numbers seperated by ".", 
    //                 else this generates an exception.
    protected int[] getSelection() {

	InputStreamReader isr;
	BufferedReader keyb;
	String response = " ";
	isr = new InputStreamReader(System.in);
	keyb = new BufferedReader(isr);

	System.out.print("\tEnter response and <RETURN>: ");
	try {
	    response = keyb.readLine();
	}catch(IOException e){
	    e.printStackTrace();
	    return null;
	}
	
	// parse input line - record each integer seperated by "."
	StringTokenizer stringTokenizer = new StringTokenizer(response, ".");
	int tokenCount = stringTokenizer.countTokens();
	int returnVal[] = new int[tokenCount];

	for (int i=0; i < tokenCount; i++ ) {
	    returnVal[i] = Integer.parseInt( stringTokenizer.nextToken() );
	}

	return returnVal;
    }

    // Preconditions: called by AbstractTest::testProcess
    // Symantics: showTestMenu is closely coupled with testDispatch
    //            This method is intended to be overridden.
    // Postconditions: Displays test menu header
    protected void showTestMenu(){
	System.out.println("\tTest menu for " + className+"\n");
	System.out.println("\tSelect number corresponding to desired test"+"\n");
	System.out.println("0. Exit " + className + " Test Menu"+"\n");
	System.out.println("1 Run Baseline Suite ");
	System.out.println("2 Run Regression Suite ");
	System.out.println("3 Run Functional Suite ");
	System.out.println("4 Run Structural Suite ");
	System.out.println("5 Run Interaction Suite ");
	System.out.println();
	System.out.println("6 Instantiate Using Default Constructor");
	System.out.println("7 Test Class Invariants ");
	System.out.println();

    }
  
    // Preconditions: called by AbstractTest::testProcess
    //                called after a user has input "which" test to run
    //                the tests are numbered in showTestMenu. 
    //                "which" is an array so that we can create tests
    //                using a a numbering format that uses "." to 
    //                seperate sections.  i.e. 1.2.1
    // Symantics: testDispatch is closely coupled with showTestMenu
    //            If this method is overridden, then it should be
    //            called at the end of the over ridding method.
    // Postconditions: Executes the user selected test
    protected void testDispatch(int which[])
	throws Exception{

	if(which.length != 1 ){
	    System.out.println("\n\tInvalid Menu Option:" +
			       "Selection out or Range\n");
	    return;
	}
	switch (which[0]) {
	case 0 :			// 0. Exit - test complete
	    throw new Exception("Test completed\n");
	case 1 :		// 1 Run Baseline Suite
	    logTestResult( " 1 Baseline Suite  ",
			   baselineSuite() );
	    break;
	case 2 :		// 2 Run Regression Suite
	    logTestResult( " 2 Regression Suite ",
			   regressionSuite() );
	    break;
	case 3 :		// 3 Run Functional Suite
	    logTestResult( " 3 Functional Suite ",
			   functionalSuite() );
	    break;
	case 4 :		// 4 Run Structural Suite
	    logTestResult( " 4 Structural Suite ",
			   structuralSuite() );
	    break;
	case 5 :		// 5 Run Interaction Suite
	    logTestResult( " 5 Interaction Suite ",
			   interactionSuite() );
	    break;
	case 6 :		// 6 Instantiate OUT
	    logTestResult( " 6  OUT", 
			   instantiateOUT() );
	    break;
	case 7 :		// 7 Test Class Invariants
	    logTestResult( " 7 class Invariants ", 
			   classInvariant() );
	    break;
	default :
	    System.out.println("\n\tInvalid Menu Option:" +  
			       "Selection out or Range\n");
	    break;  
	}	      
	
    }
    
    // The main control method.
    protected void testProcess(){
	startTestReport();
	while ( true ) {
	    try {
		showTestMenu();
		testDispatch(getSelection());
	    }
	    catch( Exception e ){
		System.out.println("\n\tExiting " + className + " Test Menu\n");
		break;
	    }
	}
	
	finishTestProcess();
    }
    
	//These methods provide a dialog that displays the string provided
   	//to it and returns true or false based on the user's selection
   	//This is used to ask for visual verification during a test
   	//An inner class is used to manage the dialog box. 
	//Pre:	newMessage contains the message to be displayed to user
	//Post:	user has pressed a button Yes = true; No = false
	public boolean askTester(String newMessage){
		ResponseFrame frame = new ResponseFrame(newMessage);
		return frame.getUserResponse();	
	}

	/**
	  *This inner class encapsulates all of the logic to display a
	  *modal dialog box that displays a string and asks the user to
	  *press either the "yes" button or the "no" button
	  */
	class ResponseFrame extends JFrame
				implements ActionListener{

	public ResponseFrame(String newMessage){
		setTitle("Visual Test Response");
		message = newMessage;
	}

	/**
	  *pre:  none
	  *post: dialog box has been displayed and one of two buttons has been pressed
	  */
	public boolean getUserResponse(){
		showDialog();
		this.setVisible(false);
		return result;
	}

	/**
	  *pre:  none
	  *post: eventually a dialog box has been displayed and one of two buttons
	  *has been displayed
	  */
      private void showDialog(){
	
		this.setSize(300,200);
		dialog = new JDialog(this, true);
		dialog.setTitle("Visual Test Response");
		dialog.setSize(290,195);
		dialog.setLocation(5,10);
		dialog.getContentPane().setLayout(new GridLayout(2,1));
		text = new JLabel(message);
	  	dialog.getContentPane().add(text,"North"); 
		JPanel inner = new JPanel();  
		yesButton = new JButton("Yes");
		yesButton.addActionListener(this);
		inner.add(yesButton);  
		noButton = new JButton("No");
		inner.add(noButton);   
		noButton.addActionListener(this);
		dialog.getContentPane().add(inner);
		dialog.setVisible(true);
	}

	/**
	  *This is the event handling method that captures which
	  *button has been pressed.
	  */
	public void actionPerformed(ActionEvent evt){
		this.setVisible(false);
		dialog.setVisible(false);
		Object source = evt.getSource();
		if(source == yesButton){result = true;}
		if(source == noButton){result = false;}
	}

		String message;
		boolean result = false;
		JButton yesButton;
		JButton noButton;
		JLabel text;
		JDialog dialog;		
	}


    protected FileWriter log;
    protected String className;
    protected String fileName;
    protected Object OUT = null;
  
}



