/*----------------------------------------------------------------------------
Copyright (c)2000 TogetherSoft Corporation. Patents pending. All rights reserved.
----------------------------------------------------------------------------*/

package com.togethersoft.modules.qa.utils;

import java.awt.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import com.togethersoft.openapi.ide.*;
import com.togethersoft.openapi.ide.window.*;
import com.togethersoft.openapi.sci.*;
import com.togethersoft.openapi.sci.enum.*;
import com.togethersoft.modules.qa.*;
import com.togethersoft.modules.qa.api.*;


/**
 * Plugin implementation - abstract class serving as ancestor for AuditPluginImpl and MetricsPluginImpl
 * @version 3.1.0
 * @author TogetherSoft
 */
public abstract class PluginImpl implements Plugin, PropertiesAccessor {

	//$VPJ_{GRP_BEGIN Redefined

	/** Empty options pane. */
	private JPanel emptyOptionsPane;

    protected boolean canProcessCPP() {
        return false;
    }

    protected boolean canProcessJava() {
        return true;
    }

    public boolean canProcessLanguage(String lang) {

        if( lang.equals(SciLanguage.JAVA) ) {
            return canProcessJava();
       }

        if( lang.equals(SciLanguage.CPP) ) {
            return canProcessCPP();
        }

		return false;
    }

	//Called upon plugin creation to perform any additional actions
	public void load() {}

	/**
	 * Gets the plugin title. This title is displayed in the plugins list and results table.
	 * @return The plugin title
	 */
	public String getTitle() {
		return "Untitled";
	}


	/**
	 * Used for grouping of plugin. Gets the name of plugin group.
	 * @return the name of plugin group.
	 */
	public String getGroupName() {
		return "undefined";
	}


	/**
	 * Gets the abbreviated name of plugin. Abbreviation is displayed in the plugin list and results table.
	 * @return the abbreviated name of plugin.
	 */
	public String getAbbreviation() {
		return Utils.getClsName( this );
	}


	/**
	 * Determines whether plugin must be chosen by default or not.
	 * @return true if plugin must be chosen by default, otherwise false.
	 */
	public boolean getDefaultChoice() {
		return true;
	}


	/**
	 * Creates additional option pane
	 * @return option pane (JComponent descendant)
	 */
	public JComponent createOptionsPane() {
		emptyOptionsPane = new JPanel();
		return emptyOptionsPane;
	}


	/**
	 * returns options panel created earlier by createOptionsPane
	 * @return options panel (JComponent descendant)
	 */
	public JComponent getOptionsPane() {
		return emptyOptionsPane;
	}

    /**
     * Called by dialog after restoring default options
     * or previously saved options set
     */
    public void updateOptionsPane() {
    }

	// used to restore options from  the properties file.
	public void restoreOptions() {
		setChosen(getProperty("Chosen", true));
	}

	// used to store options from  the properties file.
	public boolean storeOptions() {
		putProperty("Chosen", isChosen());
		return true;
	}


	/**
	 * Called after user presses the 'Start' button, before processing to carry out some additional work if need.
	 * If returns <code>false</code>, further processing is stopped.
	 * @return true to proceed, or false to stop further processing
	 */
	public boolean prepare() {
		return true;
	}


	/**
	 * Determines whether plugin can process byte code or not.
	 * @return <code>true</code> if plugin can process byte code, otherwise <code>false</code>
	 */
	public boolean canProcessByteCode() {
		return false;
	}

	/**
	 * Holds the plugin's chosen state
	 */
	private boolean chosen;

	/**
	 * Determines whether the plugin is chosen or not.
	 * @return <code>true</code> if plugin is chosen, otherwise <code>false</code>.
	 */
	public boolean isChosen() {
		return chosen;
	}

	/**
	 * Switches choosen state on or off.
	 */
	public void setChosen(boolean chosen) {
		this.chosen = chosen;
	}

    /**
     * Gets the of properties file name of plugin
	 * By default, properties file name is the same as plugin class file name but with the extemsion of ".properties".
	 *
     * @return properties file name
	 *
	 * @deprecated Since Together 4.01 this method is never called.
	 * Properties are now stored in a single file
	 **/
	public String getPropertiesFileName () {
        return "";
    }


	//$VPJ!}
	//$VPJ_{GRP_BEGIN Properties i/o

	/**
	 * Returns properties set for plugin
	 * @return properties set for plugin
	 */
	protected abstract Properties getProperties();

	/**
	 * Returns plugin's property name prefix
	 * @return plugin's property name prefix
	 */
	protected abstract String getPropertyPrefix();

	/**
	 * Retrieves String property from plugin properties file.
	 * @param propName property name
	 * @param defaultValue default value
	 * @returns property
	 */
	public String getProperty( String propName, String defaultValue ) {
		return getProperties().getProperty(getPropertyPrefix() + "." + propName, defaultValue);
	}

	/**
	 * Retrieves int property from plugin properties file.
	 * @param propName 	property name
	 * @param defaultValue default value
	 * @returns property
	 */
	public int getProperty( String propName, int defaultValue ) {
		return Utils.getProperty(getProperties(), getPropertyPrefix() + "." + propName, defaultValue);
	}


	/**
	 * Retrieves boolean property from plugin properties file.
	 * @param propName 	property name
	 * @param defaultValue default value
	 * @returns property
	 */
	public boolean getProperty( String propName, boolean defaultValue ) {
		return Utils.getProperty(getProperties(), getPropertyPrefix() + "." + propName, defaultValue);
	}

	/**
	 * Puts String property from plugin properties file.
	 * @param propName 	property name
	 * @param value 		value to store
	 */
	public void putProperty( String propName, String value ) {
		getProperties().put(getPropertyPrefix() + "." + propName, value);
	}


	/**
	 * Puts int property from plugin properties file.
	 * @param propName 	property name
	 * @param value 		value to store
	 */
	public void putProperty( String propName, int value ) {
		Utils.putProperty(getProperties(), getPropertyPrefix() + "." + propName, value );
	}


	/**
	 * Puts boolean property from plugin properties file.
	 * @param propName 	property name
	 * @param value 		value to store
	 */
	public void putProperty( String propName, boolean value ) {
		Utils.putProperty(getProperties(), getPropertyPrefix() + "." + propName, value );
	}

	//$VPJ!}

	/** stores the class that is now processed */
	protected SciClass currCls;

	/** stores the member that is now processed */
	protected SciMember currMbr;

	/** This flag is used to stop processing. If any method sets it to <code>true</code>, processing is stopped */
	protected boolean stopIter = false;

	//$VPJ!}
	//$VPJ_{GRP_BEGIN Process Goals

	/**
	 * Defines wheather we are processing operations (<a href=#PROC_OPERS>PROC_OPERS</a>),
	 * attributes(<a href=#PROC_ATTRS>PROC_ATTRS</a>) or both (PROC_ATTRS | PROC_OPERS)
	 */
	protected int procGoal = 0xFFFF;


	/** specifies that we are processing attributes */
	public final static int PROC_ATTRS = 0x01;


	/** specifies that we are processing operations */
	public final static int PROC_OPERS = 0x02;

	//$VPJ!}

	/** Is set to <code>true</code>, if the processing unit is in form of byte codes, otherwise is set to <code>false</code> */
	public static boolean isByteCode( SciObject obj ) {

        if( obj == null ) {
            return false;
        }
		SciFile file = obj.getContainingFile();
        if( file == null ) {
            return false;
        }
		if( file.getName().endsWith( ".class" ) ) {
			return true;
        }
		return false;
	}

	/**
    Determines whether given package is the root package.
    @returns <code>true</code> if current package is the root package otherwise <code>false</code>
    @deprecated in the case of multy-root project there is no single root,
    therefore isDefaultPkg would return <code>false</code> for any package.
    Use isWholePrj and com.togethersoft.openapi.sci.SciModel.rootPackages instead.
    */
	public static boolean isDefaultPkg(SciObject obj) {
		if( obj instanceof SciPackage ) {
			SciPackage pkg = ( SciPackage )obj;
			//if( pkg.getName().equals( "<default>" ) ) {
            if( ChosenNodes.getProjectRoots().length == 1
            && ChosenNodes.getProjectRoots()[0].getQualifiedName().equals(pkg.getQualifiedName()) ) {
				return true;
            }
		}

		return false;
	}

    /**
     * Determines whether the audit or metrics are processing the whole project
     * @returns <code>true</code> if we are processing the whole project otherwise <code>false</code>
     */
    public boolean isWholePrj() {
        // It's just a wrapper around analogous method of ChosenNodes class.
        // The only sense of wrapping it is to bring this method to open QA API
        return ChosenNodes.isWholePrj();
    }

	/**
	 * If object <code>obj</code> corresponds to class, sets <code>{@link #currCls currCls}</code> to this class,
	 * <code>{@link #currMbr currMbr}</code> to null and calls <code>{@link #processClass processClass}(obj)</code>
	 * @param obj 	object to process
	 * @return true to proceed, or false to stop further processing Default implementation always returns true
	 */
	protected boolean process( SciObject obj ) {

		if( !( obj instanceof SciClass ) ) {
			return true;
        }
        if( ((SciClass) obj).isDeleted() ) {
            return true;
        }
		if( !canProcessByteCode() && isByteCode( obj ) ) {
			return true;
        }
		tracer.level = 0;
		stopIter = false;
		currCls = ( SciClass )obj;
		currMbr = null;

		processClass( currCls );
		return true;
	}


	/**
	 * is invoked for each class. <br>If <code> procGoal & {@link #PROC_ATTRS} != 0 </code>,
	 * calls {@link #processAttribs processAttribs}(cls); <br>If <code> procGoal & {@link #PROC_OPERS} != 0 </code>,
	 * calls <code>{@link #processOpers processOpers}(cls)</code>
	 * <br>If <code>{@link #stopIter}</code> flag is set, stops processing
	 * @param cls 	class to process
	 */
	protected void processClass( SciClass cls ) {

        /* dbg: just for debugging
		tracer.trace = getProperty("trace", tracer.trace);
        */

		if( ( procGoal & PROC_ATTRS ) != 0 ) {
			processAttribs( cls );
		}

		if( stopIter ) {
			return;
		}

		if( ( procGoal & PROC_OPERS ) != 0 ) {
			processOpers( cls );
		}
	}


	/**
	 * Is invoked for each class. Processes its attributes.<br> Default implementation calls
	 * <code>{@link #processAttrib processAttrib}</code> for each class attribute. Before calling
	 * <code>{@link #processAttrib processAttrib}</code> it sets <code>{@link #currMbr}</code> to the current atribute.
	 * If <code>{@link #stopIter}</code> flag is set, stops processing
	 * @param cls 	class to process
	 */
	protected void processAttribs( SciClass cls ) {
		SciAttributeEnumeration attrs = cls.attributes();
		while( attrs.hasMoreElements() ) {
			SciAttribute attr = attrs.nextSciAttribute();
			if( attr.hasProperty( SciProperty.PURE ) ) {
				continue;
            }
            if( attr.isDeleted() ) {
	            continue;
            }
			currMbr = attr;
			processAttrib( attr );
			if( stopIter ) {
				return;
            }
		}
	}


	/**
	 * Is called for each attribute.<br> Calls <code>{@link #processConceptVar processConceptVar}</code> method
	 * @param attr 	attribute
	 */
	protected void processAttrib( SciAttribute attr ) {
		if( tracer.trace )
			tracer.traceAttr( attr );
		processConceptVar( attr );
	}


	/**
	 * Is invoked for each class. Processes its operations.<br> Default implementation calls
	 * <code>{@link #processOper processOper}</code> method for each operation. Before calling
	 * <code>{@link #processOper processOper}</code>, it sets <code>{@link #currMbr}</code> to the current operation
	 * If <code>{@link #stopIter}</code> flag is set, stops processing
	 * @param cls 	class to process
	 */
	protected void processOpers( SciClass cls ) {

        // just for convenience of debugging
		// tracer.trace = getProperty("trace", tracer.trace);

		SciOperationEnumeration oprs = cls.operations();
		while( oprs.hasMoreElements() ) {
			SciOperation opr = oprs.nextSciOperation();
			if( opr.hasProperty( SciProperty.PURE ) ) {
				continue;
            }
            if( opr.isDeleted() ) {
	            continue;
            }
			currMbr = opr;
			processOper( opr );
			if( stopIter ) {
				return;
            }
		}
	}


	/**
	 * Is called for each operation.<br> Default implementation calls <code>{@link #processParam processParam}</code>
	 * for each parameter of given operation (if any), <code>{@link #processThrowSpec processThrowSpec}</code>
	 * for each exception in the throw list of operation (if any) and then calls
	 * <code>{@link #processCodeBlock processCodeBlock}</code> for the body of operation
	 * @param opr 	operation
	 */
	protected void processOper( SciOperation opr ) {
		if( tracer.trace ) {
			tracer.traceOper( opr );
        }
		SciParameterList pl = opr.getParameterList();
		SciParameterEnumeration params = pl.parameters();
		while( params.hasMoreElements() ) {
			SciParameter param = params.nextSciParameter();
			processParam( param );
		}

		SciThrowList tl = opr.getThrowList();
		SciThrowSpecifierEnumeration thrs = tl.throwSpecifiers();
		while( thrs.hasMoreElements() ) {
			SciThrowSpecifier thr = thrs.nextSciThrowSpecifier();
			processThrowSpec( thr );
		}

		processCodeBlock( opr.getBody() );
	}


	/**
	 * Is called for each parameter of the operation.<br> Default implementation calls
	 * <code>{@link #processConceptVar processConceptVar}</code> for this parameter
	 * @param param 	parameter of the operation
	 */
	protected void processParam( SciParameter param ) {
		if( tracer.trace )
			tracer.traceVar( "Param", param );
		processConceptVar( param );
	}


	/**
	 * Is called for each exception in the operation throw list.
	 * @param thr 	throw specifier
	 */
	protected void processThrowSpec( SciThrowSpecifier thr ) {

	}


	/**
	 * Is called for each code block. Goes through all statements within given code block and calls
	 * <code>{@link #processStatement processStatement}</code> for each statement.
	 * @param cb 	code block
	 */
	protected void processCodeBlock( SciCodeBlock cb ) {
		if( cb == null )
			return;

		SciStatementEnumeration stmts = cb.statements();
		while( stmts.hasMoreElements() ) {
			processStatement( stmts.nextSciStatement() );
			if( stopIter )
				return;
		}
	}


	/**
	 * Is called for each statement within code block. Examines kind of statement and calls
	 * <br><code>{@link #processCaseStmt processCaseStmt}</code>, <br><code>{@link #processCatchStmt processCatchStmt}</code>,
	 * <br><code>{@link #processSwitchStmt processSwitchStmt}</code>,
	 * <br><code>{@link #processCompStmt processCompStmt}</code>, <br><code>{@link #processDeclStmt processDeclStmt}</code>,
	 * <br><code>{@link #processExprStmt processExprStmt}</code>, <br><code>{@link #processIfStmt processIfStmt}</code>,
	 * <br><code>{@link #processLoopStmt processLoopStmt}</code>,
	 * <br><code>{@link #processReturnStmt processReturnStmt}</code>,
	 * <br><code>{@link #processThrowStmt processThrowStmt}</code> or <br><code>{@link #processTryStmt processTryStmt}</code>
	 * <br> depending on the kind of statement
	 * @param stmt	statement
	 */
	protected void processStatement( SciStatement stmt ) {
		if( stmt == null )
			return;

		tracer.level++;
		if( tracer.trace )
			tracer.traceStmt( stmt );

		if( stmt instanceof SciCaseStatement ) {
			processCaseStmt( ( SciCaseStatement )stmt );
		}
		if( stmt instanceof SciCompoundStatement ) {
			if( stmt instanceof SciCatchBlockStatement ) {
				processCatchStmt( ( SciCatchBlockStatement )stmt );
			}
			else if( stmt instanceof SciSwitchStatement ) {
				processSwitchStmt( ( SciSwitchStatement )stmt );
			}
			else {
				processCompStmt( ( SciCompoundStatement )stmt );
			}
		}
		if( stmt instanceof SciDeclarationStatement ) {
			processDeclStmt( ( SciDeclarationStatement )stmt );
		}
		else if( stmt instanceof SciExpressionStatement ) {
			processExprStmt( ( SciExpressionStatement )stmt );
		}
		else if( stmt instanceof SciIfStatement ) {
			processIfStmt( ( SciIfStatement )stmt );
		}
		else if( stmt instanceof SciLoopStatement ) {
			processLoopStmt( ( SciLoopStatement )stmt );
		}
		else if( stmt instanceof SciReturnStatement ) {
			processReturnStmt( ( SciReturnStatement )stmt );
		}
		else if( stmt instanceof SciThrowStatement ) {
			processThrowStmt( ( SciThrowStatement )stmt );
		}
		else if( stmt instanceof SciTryStatement ) {
			processTryStmt( ( SciTryStatement )stmt );
		}

		if( stopIter )
			return;

		tracer.level--;
	}

	//$VPJ_{GRP_BEGIN Process Statements

	/**
	 * Is called for each <i>case</i> statement. Default implementation calls <code>{@link #processExpression
	 * processExpression}(cs)</code>
	 * @param cs case statement
	 */
	protected void processCaseStmt( SciCaseStatement cs ) {
		processExpression( cs.getExpression() );
	}


	/**
	 * Is called for each <i>catch</i> statement. Default implementation calls <code>{@link #processCodeBlock
	 * processCodeBlock}</code> for the statement body
	 * @param cs catch statement
	 */
	protected void processCatchStmt( SciCatchBlockStatement cs ) {
        // TODO: investigate why did Anatoly commented it - and uncomment
		//processConceptVar(cs.getParameter());
		processCodeBlock( cs.getCodeBlock() );
	}


	/**
	 * Is called for each <i>compound</i> statement. Default implementation calls <code>{@link #processCodeBlock
	 * processCodeBlock}</code> for the statement body
	 * @param cs catch statement
	 */
	protected void processCompStmt( SciCompoundStatement cs ) {
		processCodeBlock( cs.getCodeBlock() );
	}


	/**
	 * Is called for each <i>declaration</i> statement. Default implementation calls <code>{@link #processLocalVar
	 * processLocalVar}</code> for each declaration in statement.
	 * @param ds declaration statement
	 */
	protected void processDeclStmt( SciDeclarationStatement ds ) {
		SciElementEnumeration elems = ds.elements();
		while( elems.hasMoreElements() ) {
			SciElement elem = elems.nextSciElement();
			if( elem instanceof SciVariable ) {
				processLocalVar( ( SciVariable )elem );
				if( stopIter )
					return;
			}
		}
	}


	/**
	 * Is called for each <i>expression</i>. For each declaration in statement calls <code>{@link #processExpression
	 * processExpression}</code> for this expression.
	 * @param es expression
	 */
	protected void processExprStmt( SciExpressionStatement es ) {
		processExpression( es.getExpression() );
	}


	/**
	 * Is called for each <i>if statement</i>. Calls <code>{@link #processExpression processExpression}</code> for condition
	 * expression, <code>{@link #processCodeBlock processCodeBlock}</code> for <i>then</i> and <i>else</i> branches.
	 * @param is 	if statement
	 */
    protected void processIfStmt (SciIfStatement is) {
		processExpression( is.getCondition() );
		processCodeBlock( is.getThenCodeBlock() );
		processCodeBlock( is.getElseCodeBlock() );
	}


	/**
	 * Is called for each <i>loop</i> statement (i.e. <i>for</i>, <i>while</i> or <i>do... while</i> statement)
	 * Default implementation calls <code>{@link #processStatement processStatement}</code>
	 * for the initialization part and iteration part of <i>for</i>, <code>{@link #processExpression processExpression}</code>
	 * for the <i>repeat</i> and <i>exit</i> conditions, and
	 * <code>{@link #processCodeBlock processCodeBlock}</code> for the loop body.
	 * @param ls	loop statement
	 */
	protected void processLoopStmt( SciLoopStatement ls ) {
		processStatement( ls.getInitialization() );
		processExpression( ls.getRepeatCondition() );
		processStatement( ls.getIteration() );
		processExpression( ls.getExitCondition() );
		processCodeBlock( ls.getCodeBlock() );
	}


	/**
	 * Is called for each return statement. Default implementation calls <code>{@link #processExpression
	 * processExpression}</code>
	 * @param rs	return statement
	 */
	protected void processReturnStmt( SciReturnStatement rs ) {
		processExpression( rs.getReturnExpression() );
	}


	/**
	 * Is called for each <i>switch</i> statement. Default implementation calls <code>{@link #processExpression
	 * processExpression}</code> for the switch expression and
	 * <code>{@link #processCodeBlock processCodeBlock}</code> for the code block
	 * @param switch switch statement
	 */
	protected void processSwitchStmt( SciSwitchStatement ss ) {
		processExpression( ss.getExpression() );
		processCodeBlock( ss.getCodeBlock() );
	}


	/**
	 * Is called for each <i>throw</i> statement. Default implementation calls <code>{@link #processExpression
	 * processExpression}</code>
	 * @param ts throw statement
	 */
	protected void processThrowStmt( SciThrowStatement ts ) {
		processExpression( ts.getExpression() );
	}


	/**
	 * Is called for each <i>try-catch</i> statement; Default implementation calls
	 * <code>{@link #processStatement processStatement}</code> for the <i>try</i> part and for each of <i>catch</i> parts
	 * @param ts try statement
	 */
	protected void processTryStmt( SciTryStatement ts ) {
		processStatement( ts.getTryBlock() );
		SciStatementEnumeration ctchs = ts.catchBlocks();
		while( ctchs.hasMoreElements() ) {
			processStatement( ctchs.nextSciStatement() );
			if( stopIter )
				return;
		}
		processStatement( ts.getFinallyBlock() );
	}

	//$VPJ!}

	/**
	 * Is called for each <i>local variable</i>. Default implementation calls <code>{@link #processConceptVar
	 * processConceptVar}</code>
	 * @param var 	local variable
	 */
	protected void processLocalVar( SciVariable var ) {
		if( tracer.trace )
			tracer.traceVar( "LVar", var );
		processConceptVar( var );
	}


	/**
	 * Is called for each atribute, parameter, or local variable.
	 * Default implementation calls <code>{@link #processInitValue processInitValue}</code> for the initialisation part.
	 * @param var		atribute, parameter, or local variable
	 */
	protected void processConceptVar( SciVariable var ) {
		if( tracer.trace )
			tracer.traceVar( "CVar", var );
		processInitValue( var.getInitialValue() );
	}


	/**
	 * Is called for each initialization part of class attribute or local variable.
	 * Default implementation calls <code>{@link #processExpression processExpression}</code>
	 * @parm expr	initializer expression
	 */
	protected void processInitValue( SciExpression expr ) {
		processExpression( expr );
	}


	/**
	 * Is called for each expression, including expressions in function calls, conditional or loop statements and so on.
	 * Default implementation calls <code>{@link #examineExpression examineExpression}</code>,
	 * then calls <code>{@link #processExpression processExpression}</code> for each expression's operand
	 * @param expr	expression
	 */
	protected void processExpression( SciExpression expr ) {
		if( expr == null )
			return;

		tracer.level++;
		if( tracer.trace )
			tracer.traceExpr( expr );
		examineExpression( expr );
		if( stopIter )
			return;

		SciExpressionEnumeration exprs = expr.operands();
		while( exprs.hasMoreElements() ) {
			processExpression( exprs.nextSciExpression() );
			if( stopIter )
				return;
		}

		tracer.level--;
	}


	/**
	 * Is called for each expression. Just for overriding - default implementation does nothing.
	 * @param expr	expression
	 */
	protected void examineExpression( SciExpression expr ) {}

	/**
	 * used internally for debugging purposes
	 */
	protected Tracer tracer = new Tracer();

    /**
     * used internally for debugging purposes
	 */
    public void printDiagnostics() {
        try {
            Utils.winout("PLUGIN: " + getAbbreviation());
            if( currCls == null ) {
                Utils.winout("  currCls==null");
            }
            else {
                Utils.winout("  currCls.getQualifiedName()=" + currCls.getQualifiedName());
                Utils.winout("  currCls.isDeleted()=" + currCls.isDeleted());
                Utils.winout("  currCls=" + currCls);
            }
            if( currMbr == null ) {
                Utils.winout("  currMbr==null");
            }
            else {
                Utils.winout("  currMbr.getName()=" + currMbr.getName());
                Utils.winout("  currMbr.isDeleted()=" + currMbr.isDeleted());
                Utils.winout("  currMbr=" + currMbr);
            }
        }
        catch( Throwable innerThr ) {
            Utils.winout("Error while trying to report error");
            Utils.winoutStackTrace(innerThr);
        }
    }
}
