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

package com.togethersoft.modules.showdependencies;

import com.togethersoft.openapi.rwi.*;
import com.togethersoft.openapi.sci.*;
import com.togethersoft.openapi.sci.enum.*;
import com.togethersoft.openapi.sci.visitor.SciReferenceVisitor;
import com.togethersoft.openapi.sci.visitor.SciStatementVisitorAdapter;
import com.togethersoft.openapi.sci.pattern.SciPatternUtil;
import com.togethersoft.together.service.sci.SciElementEx;
import java.util.Vector;

/**
 * Analyzes a dependency between two SciClasses.
 *
 * @author TogetherSoft
 */
class ClassesAnalyzer
{
    /**
     * Analyzes a dependency between two SciClasses.
     *
     * @param sourceC source SCI class
     * @param destC destination SCI class
     * @param inDeclarationsOnly only declaration flag
     * @return dependency vector
     */
    public Vector analyze(SciClass sourceC, SciClass destC, boolean inDeclarationsOnly)
    {
        // first, analyze those reference that are expressions
        ReferenceSearcher rSearcher = new ReferenceSearcher(sourceC, destC);
        ((SciElementEx)sourceC).visitReferences(rSearcher, inDeclarationsOnly);

        Vector partOne = rSearcher.getFoundDependencies();

        // now scan attributes and operations
        Vector partTwo = mSearcher.searchMembers(sourceC, destC);
        joinVectors(partOne, partTwo);

        // only in this mode
        if (!inDeclarationsOnly)
        {
            // search in the declarations in the code blocks
            DeclarationSearcher dSearcher =
            new DeclarationSearcher(sourceC, destC, inDeclarationsOnly);
            sourceC.visitStatements(dSearcher, SciStatementKind.DECLARATION);
            partTwo = dSearcher.getFoundDependencies();
            joinVectors(partOne, partTwo);
        }
        return partOne;
    }

    /**
     * Joins two vectors. Adds second vector to first one.
     *
     * @param target first Vector object. It will contain result vector.
     * @param whatToAdd second vector object.
     */
    public void joinVectors(Vector target, Vector whatToAdd)
    {
        final int whatToAddSize = whatToAdd.size();
        for (int i = 0; i < whatToAddSize; i++)
        {
            target.addElement(whatToAdd.elementAt(i));
        }
    }

    // memeber searcher instance
    private MemberSearcher mSearcher = new MemberSearcher();
}

/**
 * Implements SciReferenceVisitor.
 * Visits only those references to the cause SciClass that are expressions.
 *
 * @see com.togethersoft.openapi.sci.visitor.SciReferenceVisitor
 */
class ReferenceSearcher implements SciReferenceVisitor
{
    /**
     * Creates instance of class with specified parameters.
     *
     * @param victim SciClass object
     * @param cause  SciClass object
     */
    public ReferenceSearcher(SciClass victim, SciClass cause)
    {
        super();
        myVictim = victim;
        myCause = cause;
        collectedDependencies = new Vector();
    }

    /**
     * Returns vector that contains found dependencies
     *
     * @return vector that contains found dependencies
     */
    public Vector getFoundDependencies()
    {
        return collectedDependencies;
    }

    /**
     * Reference visitor.
     *
     * @param sciReference SciReference object
     * @return vector that contains found dependencies
     */
    public Object visitReference(SciReference sciReference)
    {
        if (sciReference.getReferencedElement() != myCause)
        {
            return null;
        }

        // it it's SciInheritance
        if (sciReference instanceof SciInheritance)
        {
            String comment = null;
            if (myVictim.hasProperty(SciProperty.INTERFACE))
            {
                comment = "Interface \"" + myVictim.getQualifiedName() +
                    "\" extends \"" +  myCause.getQualifiedName() + "\"";
            }
            else if (((SciInheritance)sciReference).isImplementation())
            {
                comment = "Class \"" + myVictim.getQualifiedName() +
                    "\" implements \"" + myCause.getQualifiedName() + "\"";
            }
            else
            {
                comment = "Class \"" + myVictim.getQualifiedName() +
                    "\" extends \"" + myCause.getQualifiedName() + "\"";
            }

            CodeFragment info =
                new CodeFragment(ShowDependencies.INDENT +
                myVictim.getDeclarationText() + ShowDependencies.INDENT,
                CodeFragment.INHERITANCE,
                myVictim.getPositions(), comment);

            // finally, add a new Dependency object based on the created CodeFragment
            collectedDependencies.addElement(new Dependency(myVictim, myCause, info));
        }

        // it it's  SciReferenceExpression
        if (sciReference instanceof SciReferenceExpression)
        {
            // now we find the upper-level expression and use its text in the tree
            SciExpression previousLevel = (SciReferenceExpression)sciReference;
            SciExpression parent = previousLevel.getParent();
            String comment = getExpressionComment(parent);
            while (parent != null)
            {
                previousLevel = parent;
                parent = previousLevel.getParent();
            }

            String text = previousLevel.getText();
            // removing the line separator
            text = SciPatternUtil.replace
                (text, System.getProperty("line.separator"), "", false);

            // building the CodeFragment containing found text and its positions
            CodeFragment info =
                new CodeFragment(ShowDependencies.INDENT + text +
                ShowDependencies.INDENT, CodeFragment.REFERENCE_EXPRESSION,
                previousLevel.getPositions(), comment);

            // finally, add a new Dependency object based on the created CodeFragment
            collectedDependencies.addElement(new Dependency(myVictim, myCause, info));
        }
        return null;
    }

    /**
     * Returns expression comment string for specified expression.
     *
     * @param expression SciExpression object
     * @return expression comment string.
     */
    private String getExpressionComment(SciExpression expression)
    {
        String comment = null;

        int kind = expression.getKind();

        switch (kind)
        {
            case SciExpressionKind.TYPE_CAST:
                comment = "Used in a type cast expression";
                break;
            case SciExpressionKind.ACCESS:
                comment = "Used in a member access expression";
                break;
            case SciExpressionKind.ARRAY_ACCESS:
                comment = "Used in an expression of accessing array's element";
                break;
            case SciExpressionKind.PARENTHESES:
                comment = getExpressionComment(expression.getParent());
                break;
            case SciExpressionKind.NEW_INSTANCE:
                comment = "Used in a 'new' expression";
                break;
            default:
                comment = null;
        }
        return comment;
    }

    private Vector collectedDependencies;
    private SciClass myCause;
    private SciClass myVictim;
}


/**
 * Searches the dependency reason in the declaration statements
 * in the code blocks.
 *
 * @see SciStatementVisitorAdapter
 */
class DeclarationSearcher extends SciStatementVisitorAdapter
{
    /**
     * Creates instance of class with specified parameters.
     *
     * @param victim SciClass object
     * @param cause  SciClass object
     * @param inDeclarationsOnly only declaration flag
     */
    public DeclarationSearcher(SciClass victim, SciClass cause, boolean inDeclarationsOnly)
    {
        myVictim = victim;
        myCause = cause;
        this.inDeclarationsOnly = inDeclarationsOnly;
    }

    /**
     * Returns vector that contains found dependencies
     *
     * @return vector that contains found dependencies
     */
    public Vector getFoundDependencies()
    {
        return collectedDependencies;
    }

    /**
     * Declaration statement visitor.
     *
     * @param sciReference SciDeclarationStatement object
     * @return vector that contains found dependencies
     */
    public Object visitDeclarationStatement(SciDeclarationStatement statement)
    {
        SciElementEnumeration elements = statement.elements();
        while (elements.hasMoreElements())
        {
            SciElement nextElement = elements.nextSciElement();
            String declarationText = nextElement.getDeclarationText();
            if (declarationText.indexOf(myCause.getName()) == -1)
            {
                continue; //bad check, doesn't check qual.names
            }
            CodeFragment info =
                new CodeFragment(ShowDependencies.INDENT + declarationText +
                ShowDependencies.INDENT, CodeFragment.DECLARATION,
                statement.getPositions(), "Declaration in a code block");

            collectedDependencies.addElement(new Dependency(myVictim, myCause, info));
        }
        return null;
    }

    private boolean inDeclarationsOnly;
    private SciClass myVictim;
    private SciClass myCause;
    private Vector collectedDependencies = new Vector();
}

/**
 * Searches a dependency reason in the attributes and operations.
 */
class MemberSearcher
{

    /**
     * Searches a dependency reason in the attributes and operations.
     *
     * @return found dependency vector
     */
    public Vector searchMembers(SciClass victim, SciClass cause)
    {
        Vector collectedDependencies = new Vector();
        SciAttributeEnumeration attributes = victim.attributes();
        while (attributes.hasMoreElements())
        {
            SciAttribute nextAttr = attributes.nextSciAttribute();
            if (nextAttr.hasProperty(SciProperty.PURE))
            {
                continue;
            }
            if (nextAttr.getType().getReferencedElement() != cause)
            {
                continue;
            }

            CodeFragment info =
                new CodeFragment(ShowDependencies.INDENT +
                nextAttr.getDeclarationText() + ShowDependencies.INDENT,
                CodeFragment.ATTRIBUTE, nextAttr.getPositions(), "Attribute");

            collectedDependencies.addElement(new Dependency(victim, cause, info));
        }

        SciOperationEnumeration operations = victim.operations();
        while (operations.hasMoreElements())
        {
            SciOperation nextOper = operations.nextSciOperation();
            SciType operRetType = nextOper.getReturnType();
            if ((operRetType != null) && (operRetType.getReferencedElement() == cause))
            {
                CodeFragment info =
                    new CodeFragment(ShowDependencies.INDENT +
                    nextOper.getDeclarationText() + ShowDependencies.INDENT,
                    CodeFragment.OPERATION, nextOper.getPositions(),
                    "Return type of operation");

                collectedDependencies.addElement(new Dependency(victim, cause, info));
            }

            SciParameterEnumeration parameters = nextOper.getParameterList().parameters();
            while (parameters.hasMoreElements())
            {
                SciParameter nextParameter = parameters.nextSciParameter();
                if (nextParameter.getType().getReferencedElement() != cause)
                {
                    continue;
                }
                CodeFragment info =
                    new CodeFragment(ShowDependencies.INDENT +
                    nextOper.getDeclarationText() + ShowDependencies.INDENT,
                    CodeFragment.OPERATION, nextOper.getPositions(),
                    "Parameter \"" + nextParameter.getName() + "\" in operation");

                collectedDependencies.addElement(new Dependency(victim, cause, info));
            }
        }
        return collectedDependencies;
    }
}

