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

package com.togethersoft.modules.showdependencies;

import com.togethersoft.openapi.rwi.RwiDiagram;
import com.togethersoft.openapi.rwi.RwiLink;
import com.togethersoft.openapi.rwi.RwiContainer;
import com.togethersoft.openapi.rwi.RwiElement;
import com.togethersoft.openapi.rwi.RwiProperty;
import com.togethersoft.openapi.rwi.RwiModelAccess;
import com.togethersoft.openapi.rwi.RwiShapeType;
import com.togethersoft.openapi.rwi.RwiPackage;
import com.togethersoft.openapi.sci.SciClass;
import com.togethersoft.openapi.sci.SciElement;
import com.togethersoft.openapi.sci.SciPackage;
import com.togethersoft.openapi.sci.SciReference;
import com.togethersoft.openapi.sci.SciProperty;
import com.togethersoft.openapi.sci.SciInheritance;
import com.togethersoft.openapi.sci.SciLanguage;
import com.togethersoft.openapi.sci.SciAttribute;
import com.togethersoft.openapi.sci.SciOperation;
import com.togethersoft.openapi.sci.SciType;
import com.togethersoft.openapi.sci.SciParameter;
import com.togethersoft.openapi.sci.SciStatementKind;
import com.togethersoft.openapi.sci.SciExpressionKind;
import com.togethersoft.openapi.sci.SciTextPositions;
import com.togethersoft.openapi.sci.SciExpression;
import com.togethersoft.openapi.sci.SciReferenceExpression;
import com.togethersoft.openapi.sci.SciDeclarationStatement;
import com.togethersoft.openapi.sci.enum.SciClassEnumeration;
import com.togethersoft.openapi.sci.enum.SciOperationEnumeration;
import com.togethersoft.openapi.sci.enum.SciAttributeEnumeration;
import com.togethersoft.openapi.sci.enum.SciPackageEnumeration;
import com.togethersoft.openapi.sci.enum.SciElementEnumeration;
import com.togethersoft.openapi.sci.enum.SciParameterEnumeration;
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 com.togethersoft.openapi.ide.IdeScript;
import com.togethersoft.openapi.ide.IdeAccess;
import com.togethersoft.openapi.ide.message.IdeMessageManagerAccess;
import com.togethersoft.openapi.ide.message.IdeMessageManager;
import com.togethersoft.openapi.ide.message.IdeMessagePage;
import com.togethersoft.openapi.ide.message.IdeMessageView;
import com.togethersoft.openapi.ide.message.IdeMessageType;
import com.togethersoft.openapi.ide.diagram.IdeDiagramManager;
import com.togethersoft.openapi.ide.diagram.IdeDiagramManagerAccess;
import com.togethersoft.openapi.ide.diagram.IdeDiagram;
import com.togethersoft.openapi.ide.editor.IdeEditor;
import com.togethersoft.openapi.ide.editor.IdeEditorManager;
import com.togethersoft.openapi.ide.editor.IdeEditorManagerAccess;
import com.togethersoft.openapi.ide.editor.IdeEditorVisibleArea;
import com.togethersoft.openapi.ide.editor.IdeEditorSelection;
import com.togethersoft.openapi.ide.editor.IdeEditorSelectionType;
import com.togethersoft.openapi.ide.editor.IdeEditorPositionConverter;
import com.togethersoft.openapi.ide.project.IdeProjectManagerAccess;
import com.togethersoft.openapi.ide.IdeContext;
import com.togethersoft.openapi.ide.window.IdeWindowManagerAccess;
import com.togethersoft.openapi.ide.window.IdeWindowManager;
import com.togethersoft.openapi.ide.window.IdeButtonType;
import com.togethersoft.openapi.ide.window.IdeDialogType;
import com.togethersoft.openapi.ide.window.IdeDialogButtonGroup;
import com.togethersoft.openapi.ide.resource.IdeResourceManagerAccess;
import com.togethersoft.openapi.ide.resource.IdeResourceIconType;
import com.togethersoft.openapi.ide.config.IdeConfigManager;
import com.togethersoft.openapi.ide.config.IdeConfigManagerAccess;
import com.togethersoft.openapi.ide.config.IdeConfig;
import com.togethersoft.openapi.ide.config.IdeConfigLevel;
import com.togethersoft.openapi.util.text.TextPosition;
import java.awt.Component;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.BorderLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JTree;
import javax.swing.ToolTipManager;
import javax.swing.Icon;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JScrollPane;
import javax.swing.border.EtchedBorder;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.util.Hashtable;
import java.util.Vector;

/**
 * This sample module gives a detailed analysis of why a dependency link
 * is drawn between elements (between two packages or two classes).
 *
 * @version 3.0.0
 * @author TogetherSoft 
 */
public class ShowDependencies implements IdeScript
{
    // property name
    public final static String PROPERTY_NAME = "option.dependencyLinks.inDeclarationsOnly";
    // indent string
    public final static String INDENT = "    ";

   /**
    * Runs this module.
    *
    * @param context the IdeContext instance containing
    * the selection information at the moment the module was called.
    */
    public void run(IdeContext context)
    {
        // printing information message
        IdeMessageManagerAccess.printMessage
            (IdeMessageType.INFORMATION, "ShowDependencies module: started");

        // checking if project is opened
        if (IdeProjectManagerAccess.getProjectManager().getActiveProject() == null)
        {
            // no open projects
            // show modal window with error description
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.ERROR_MODAL, "No open project");
            // printing message
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.INFORMATION, "ShowDependencies module: finished");
            return;
        }

        // getting the array of selected elements from context
        RwiElement[] selectedRwiElements = context.getRwiElements();

        // if there are not selected elements
        if (selectedRwiElements == null || selectedRwiElements.length == 0)
        {
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.ERROR_MODAL, "No selection was made.");
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.INFORMATION, "ShowDependencies module: finished");
            return;
        }

        // if too many elements were selected
        if (selectedRwiElements.length > 1)
        {
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.ERROR_MODAL, "Please select only one element");
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.INFORMATION, "ShowDependencies module: finished");
            return;
        }

        // getting selected element
        RwiElement selectedElement = selectedRwiElements[0];

        // checking whether the selected element is a dependency link
        // (its shapetype is RwiShapeType.DEPENDENCY)
        if (!RwiShapeType.DEPENDENCY.equals(selectedElement.getProperty(RwiProperty.SHAPE_TYPE)))
        {
            // it's not dependency link
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.ERROR_MODAL, "Please select a dependency link");
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.INFORMATION, "ShowDependencies module: finished");
            return;
        }

        // getting source and destination elements of the link
        RwiContainer source = (RwiContainer)((RwiLink)selectedElement).getSource();
        RwiContainer dest = (RwiContainer)((RwiLink)selectedElement).getDestination();

        // getting config property which controls the dependency analysis.
        // (Options->View Management->Show Dependencies)

        // getting config manager
        IdeConfigManager cman = IdeConfigManagerAccess.getConfigManager();

        // getting the value of this property for the open project
        final IdeConfig config = cman.getConfig(IdeConfigLevel.PROJECT);

        // checking if it's declaration only
        boolean inDeclarationsOnly = "true".equals(config.getProperty(PROPERTY_NAME, "false"));

        String searchType = " (" + ((inDeclarationsOnly) ? "declarations only" : "all usages") + ")";
        boolean wasOutput = false; // output source code flag

        // remember that packages are drawn as RwiDiagmram in the class diagram.
        // (See the RwiDiagram docs)

        // if it's link between diagrams
        if ((source instanceof RwiDiagram) && (dest instanceof RwiDiagram))
        {
            // new package analyzer
            PackagesAnalyzer pa = new PackagesAnalyzer();

            // checking dependencies
            Vector dependencies = pa.analyze((RwiDiagram)source, (RwiDiagram)dest, inDeclarationsOnly);

            // if something was found
            if (dependencies.size() != 0)
            {
                displayResults("Packages: " +
                    ((RwiDiagram)source).getContainingPackage().getProperty(RwiProperty.FULL_NAME) +
                    " and " +
                    ((RwiDiagram)dest).getContainingPackage().getProperty(RwiProperty.FULL_NAME) +
                    searchType, dependencies);
                wasOutput = true; // set output flag
            }
        }
        else if ((source.getCodeElement() instanceof SciClass)
            && (dest.getCodeElement() instanceof SciClass))
        {
            // it is a link between two classes

            // new class analyzer
            ClassesAnalyzer ca = new ClassesAnalyzer();

            // getting source and destination codes
            SciClass sourceClass = (SciClass)source.getCodeElement();
            SciClass destClass = (SciClass)dest.getCodeElement();

            // checking dependencies
            Vector foundDependencies = ca.analyze(sourceClass, destClass, inDeclarationsOnly);

            // if something was found
            if (foundDependencies.size() != 0)
            {
                displayResults("Classes: " + sourceClass.getName() +
                    " and " + destClass.getName() +
                    searchType, foundDependencies);
                wasOutput = true;  // output flag
            }
        }

        // if nothing to output
        if (!wasOutput)
        {
            IdeMessageManagerAccess.printMessage(IdeMessageType.INFORMATION,
                "No source code fragments causing this dependency link were found. For example, if you create the dependeny link by yourself it does not produce a source code-based dependency between the source and the destination");
        }

        // printing final message
        IdeMessageManagerAccess.printMessage
            (IdeMessageType.INFORMATION, "ShowDependencies module: finished");
    }

    /**
     * Opens the specified SciClass and selects the specified fragment in its text
     *
     * @param classToOpen SciClass to be loaded into the editor
     * @param sciPos the SciTextPositionObject holding the selection's start and end offsets
     */
    public static void openAndSelect(final SciClass classToOpen, final SciTextPositions sciPos)
    {
        // checking if class was deleted
        if (classToOpen.isDeleted())
        {
            return; // nothing to open
        }

        // getting editor manager
        final IdeEditorManager editorManager = IdeEditorManagerAccess.getEditorManager();

        // finding the corresponding RwiElement
        final RwiElement rwiElement = RwiModelAccess.getModel().findNode(classToOpen);
        // couldn't find it
        if (rwiElement == null) {
            return;
        }

        // adding command to open the file to the command queue.
        IdeAccess.getIdeManager().addCommandToQueue(new Runnable()
        {
            // it is not used for a thread, we just use this interface's run method
            public void run()
            {
                // getting name of file witch contents element
                String fileName = rwiElement.getProperty(RwiProperty.FILE);

                // finding the file in editor to ckeck it's open or not now
                IdeEditor editor = editorManager.findEditor(fileName);

                // if the file is not open in editor
                if (editor == null)
                {
                    editor = editorManager.openEditor(); // creates a new empty editor
                    editor.setFile(fileName); // loading a file in editor
                }

                // getting the position of the beginning and end of an element's
                // defintion in a file
                int startOffset = sciPos.getStartOffset();
                int endOffset = sciPos.getEndOffset();

                // getting TextPosition objects for beginning and end of element text
                TextPosition startTextPos = editor.getPositionConverter().offsetToCell(startOffset);
                TextPosition endTextPos = editor.getPositionConverter().offsetToCell(endOffset);

                // setting the selection in editor
                IdeEditorSelection selection = editor.getSelection(IdeEditorSelectionType.PRIMARY);
                selection.setSelectionStart(startTextPos);
                selection.setSelectionEnd(endTextPos);

                // setting the view area and cursor position
                IdeEditorVisibleArea viewArea = editor.getVisibleArea();
                viewArea.setFirstColumn(1);
                viewArea.setFirstLine(startTextPos.getLine());

                // setting active editor
                editorManager.setActiveEditor(editor);
            }
        });
    }

    /**
    * Returns a Hashtable for building the tree.
    *
    * @return Hashtable object for building the tree
    */
    private Hashtable getTreeData() {
        return (myVictimsGoFirst) ? dependenciesHandler.getVictimsData() : dependenciesHandler.getCausesData();
    }

    /**
    * Returns the title opposite to the current output mode
    *
    * @return string title opposite to the current output mode
    */
    private String getButtonText() {
        return myVictimsGoFirst ? "Display classes causing a dependency first" : "Display dependent classes first";
    }

    /**
     * Builds a tree and displays it in the message pane as a new page.
     *
     * @param title String with the title for the newly created page
     * @return dependencies the Vector containing Dependency objects.
     * These objects will be grouped into two Hashtables (for each output mode)
     * and then the tree model will be build from them.
     */
    private void displayResults(String title, Vector dependencies)
    {
        // setting tooltip delay
        ToolTipManager.sharedInstance().setInitialDelay(100);

        // creation root node
        DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Root node");

        // this object sorts the dependencies and keeps them in the two Hashtables
        dependenciesHandler = new DependenciesHandler(dependencies);

        // gettign the right Hashtable
        treeData = getTreeData();

        // building the tree using visual components
        tree = new JTree(treeData);
        tree.setRootVisible(false);
        tree.setShowsRootHandles(true);
        tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.putClientProperty("JTree.lineStyle", "Angled");
        ToolTipManager.sharedInstance().registerComponent(tree);
        final DependencyTreeRenderer treeRenderer = new DependencyTreeRenderer();
        tree.setCellRenderer(treeRenderer);
        tree.addMouseListener(new MouseAdapter()
        {
            public void mouseClicked(MouseEvent e)
            {
                JTree tree = (JTree)e.getSource();
                TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
                if (selPath != null)
                {
                    if (e.getClickCount() == 2)
                    {
                        Object containingObject =
                            ((DefaultMutableTreeNode)selPath.getLastPathComponent()).getUserObject();

                        if (containingObject instanceof SciClass)
                        {
                            // if it is a class/interface then open it and select its text
                            SciClass theClass = (SciClass)containingObject;
                            // if it's not deleted
                            if (!theClass.isDeleted())
                            {
                                openAndSelect(theClass, theClass.getPositions());
                            }
                        }
                        else if (containingObject instanceof Dependency)
                        {
                            // open the containing class and select only
                            // the text of this Dependency's CodeFragment
                            SciClass victimClass = ((Dependency)containingObject).getVictim();

                            // if it's not deleted
                            if (!victimClass.isDeleted())
                            {
                                openAndSelect
                                    (victimClass, ((Dependency)containingObject).getCodeFragment().getSciPositions());
                            }
                        }
                    }
                }
            }
        });
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.add(tree, "Center");
        final JButton reverseButton = new JButton(getButtonText());
        //setting the max of the preferred sizes
        reverseButton.setText(getButtonText());
        int h1 = reverseButton.getPreferredSize().width;
        myVictimsGoFirst = !myVictimsGoFirst;
        reverseButton.setText(getButtonText());
        int h2 = reverseButton.getPreferredSize().width;
        myVictimsGoFirst = !myVictimsGoFirst; //returning to the original state
        reverseButton.setText(getButtonText()); //returning to the original state
        final int maxWidth = java.lang.Math.max(h1, h2);
        reverseButton.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                // reverse the button name and the output mode
                myVictimsGoFirst = !myVictimsGoFirst;
                reverseButton.setText(getButtonText());
                reverseButton.setPreferredSize
                    (new Dimension(maxWidth, reverseButton.getPreferredSize().height));
                // setting the new tree data from the Hashtable
                treeRenderer.setVictimsGoFirst(myVictimsGoFirst);
                tree.setModel(SmartTree.getNewModelForHashtable(getTreeData()));
            }
        });
        JPanel eastPanel = new JPanel();
        eastPanel.setBorder(new EtchedBorder());
        eastPanel.add(reverseButton);
        panel.add(eastPanel, "East");
        JScrollPane scrollPane = new JScrollPane(panel);

        // getting message manager
        IdeMessageManager messageManager = IdeMessageManagerAccess.getMessageManager();

        // we use the title as an ID of the page.
        final String pageID = title;

        // if the page with this name already exists
        if (messageManager.findPage(pageID) != null)
        {   // close page
            messageManager.closePage(messageManager.findPage(pageID));
        }

        // creation a page
        IdeMessagePage page = messageManager.openPage(pageID, scrollPane, null);
        page.setTitle(title); // setting page title
        messageManager.setActivePage(page);  // setting page active
        messageManager.setPaneVisible(true); // making the message pane visible
    }

    private boolean myVictimsGoFirst = true; // this boolean flag controls what comes first in the tree.
    private Hashtable treeData; // hashtable with tree data
    private JTree tree;         // tree
    private DependenciesHandler dependenciesHandler; // dependencies handler object
}

/**
 * This class is an utility for using the protected createTreeModel(Hashtable) method.
 */
class SmartTree extends JTree
{
    public static TreeModel getNewModelForHashtable(Hashtable data)
    {
        return createTreeModel(data);
    }
}
