/*
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public
    License as published by the Free Software Foundation; either
    version 2 of the license, or (at your option) any later version.
*/

package org.gjt.jclasslib.browser;

import org.gjt.jclasslib.browser.config.window.*;
import org.gjt.jclasslib.structures.ClassMember;
import org.gjt.jclasslib.structures.FieldInfo;
import org.gjt.jclasslib.structures.InvalidByteCodeException;
import org.gjt.jclasslib.structures.MethodInfo;

import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * Visual component displaying a class file.
 *
 * @author <a href="mailto:jclasslib@ej-technologies.com">Ingo Kegel</a>
 *
 */
public class BrowserComponent extends JComponent
        implements TreeSelectionListener {

    private BrowserHistory history;
    private BrowserServices services;

    // Visual Components

    private JSplitPane splitPane;
    private BrowserTreePane treePane;
    private BrowserDetailPane detailPane;

    /**
     * Constructor.
     *
     * @param services the associated browser services
     */
    public BrowserComponent(BrowserServices services) {

        this.services = services;
        setupComponent();
    }

    /**
     * Get the pane containing the tree structure for the shown class file.
     *
     * @return the pane
     */
    public BrowserTreePane getTreePane() {
        return treePane;
    }

    /**
     * Get the pane containing the detail area for the specific tree node selected
     * in the <tt>BrowserTreePane</tt>.
     *
     * @return the pane
     */
    public BrowserDetailPane getDetailPane() {
        return detailPane;
    }

    /**
     * Get the navigation history of this child window.
     *
     * @return the history
     */
    public BrowserHistory getHistory() {
        return history;
    }

    /**
     * Construct a <tt>BrowserPath</tt> object for the currently selected path in the tree.
     *
     * @return the browser path
     */
    public BrowserPath getBrowserPath() {

        TreePath selectionPath = treePane.getTree().getSelectionPath();
        if (selectionPath == null || selectionPath.getPathCount() < 3) {
            return null;
        }

        BrowserTreeNode categoryNode = (BrowserTreeNode)selectionPath.getPathComponent(2);
        String category = categoryNode.getType();
        if (category.equals(BrowserTreeNode.NODE_NO_CONTENT)) {
            return null;
        }

        BrowserPath browserPath = new BrowserPath();
        browserPath.addPathComponent(new CategoryHolder(category));
        int categoryNodeIndex = categoryNode.getIndex();
        if (category.equals(BrowserTreeNode.NODE_CONSTANT_POOL)) {
            --categoryNodeIndex;
        }
        if (category.equals(BrowserTreeNode.NODE_METHOD)) {
            MethodInfo methodInfo = services.getClassFile().getMethods()[categoryNodeIndex];
            addClassMemberPathComponent(methodInfo, browserPath, selectionPath);
        } else if (category.equals(BrowserTreeNode.NODE_FIELD)) {
            FieldInfo fieldInfo = services.getClassFile().getFields()[categoryNodeIndex];
            addClassMemberPathComponent(fieldInfo, browserPath, selectionPath);
        } else {
            browserPath.addPathComponent(new IndexHolder(categoryNodeIndex));
        }

        return browserPath;
    }

    /**
     * Set the currently selected path in the tree by analyzing a <tt>BrowserPath</tt> object.
     * In the case the given path is <code>null</code> nothing happens.
     *
     * @param browserPath the browser path
     */
    public void setBrowserPath(BrowserPath browserPath) {

        if (browserPath == null) {
            return;
        }
        LinkedList pathComponents = browserPath.getPathComponents();
        Iterator it = pathComponents.iterator();
        if (!it.hasNext()) {
            return;
        }
        CategoryHolder categoryComponent = (CategoryHolder)it.next();
        String category = categoryComponent.getCategory();
        TreePath path = treePane.getPathForCategory(category);
        if (path == null) {
            return;
        }
        while (it.hasNext()) {
            PathComponent pathComponent = (PathComponent)it.next();
            int childIndex;
            if (pathComponent instanceof ReferenceHolder) {
                ReferenceHolder referenceHolder = (ReferenceHolder)pathComponent;
                try {
                    if (category.equals(BrowserTreeNode.NODE_METHOD)) {
                        childIndex = services.getClassFile().getMethodIndex(referenceHolder.getName(), referenceHolder.getType());
                    } else if (category.equals(BrowserTreeNode.NODE_FIELD)) {
                        childIndex = services.getClassFile().getFieldIndex(referenceHolder.getName(), referenceHolder.getType());
                    } else {
                        break;
                    }
                } catch (InvalidByteCodeException ex) {
                    break;
                }
            } else if (pathComponent instanceof IndexHolder) {
                childIndex = ((IndexHolder)pathComponent).getIndex();
            } else {
                break;
            }
            BrowserTreeNode lastNode = (BrowserTreeNode)path.getLastPathComponent();
            if (childIndex >= lastNode.getChildCount()) {
                break;
            }
            path = path.pathByAddingChild(lastNode.getChildAt(childIndex));
        }

        JTree tree = treePane.getTree();
        tree.expandPath(path);
        tree.setSelectionPath(path);
        Object[] pathObjects = path.getPath();
        if (pathObjects.length > 2) {
            TreePath categoryPath = new TreePath(new Object[]{pathObjects[0], pathObjects[1], pathObjects[2]});
            tree.scrollPathToVisible(categoryPath);
        }

    }

    /**
     * Rebuild tree view, clear history and try to set the same path in the browser as before.
     */
    public void rebuild() {


        BrowserPath browserPath = getBrowserPath();
        reset();
        if (browserPath != null) {
            setBrowserPath(browserPath);
        }
    }

    /**
     * Rebuild tree view and clear history.
     */
    public void reset() {

        JTree tree = treePane.getTree();
        tree.removeTreeSelectionListener(this);
        treePane.rebuild();
        history.clear();
        tree.addTreeSelectionListener(this);
        checkSelection();
    }


    /**
     * Check whether anything is selected. If not select the first node.
     */
    public void checkSelection() {

        JTree tree = treePane.getTree();
        if (services.getClassFile() == null) {
            ((CardLayout)detailPane.getLayout()).show(detailPane, BrowserTreeNode.NODE_NO_CONTENT);
        } else {
            if (tree.getSelectionPath() == null) {
                BrowserTreeNode rootNode = (BrowserTreeNode)tree.getModel().getRoot();
                tree.setSelectionPath(new TreePath(new Object[]{rootNode, rootNode.getFirstChild()}));
            }
        }
    }

    public void valueChanged(TreeSelectionEvent selectionEvent) {

        services.activate();

        TreePath selectedPath = selectionEvent.getPath();

        history.updateHistory(selectedPath);
        showDetailPaneForPath(selectedPath);

    }

    private void addClassMemberPathComponent(ClassMember classMember, BrowserPath browserPath, TreePath selectionPath) {

        try {
            browserPath.addPathComponent(new ReferenceHolder(classMember.getName(), classMember.getDescriptor()));
            if (selectionPath.getPathCount() > 3) {
                for (int i = 3; i < selectionPath.getPathCount(); i++) {
                    BrowserTreeNode attributeNode = (BrowserTreeNode)selectionPath.getPathComponent(i);
                    browserPath.addPathComponent(new IndexHolder(attributeNode.getIndex()));
                }
            }
        } catch (InvalidByteCodeException ex) {
        }
    }

    private void showDetailPaneForPath(TreePath path) {

        BrowserTreeNode node = (BrowserTreeNode)path.getLastPathComponent();
        String nodeType = node.getType();
        detailPane.showPane(nodeType, path);
    }


    private void setupComponent() {

        setLayout(new BorderLayout());

        detailPane = new BrowserDetailPane(services);

        splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                buildTreePane(),
                detailPane);

        add(splitPane, BorderLayout.CENTER);

    }

    private BrowserTreePane buildTreePane() {

        treePane = new BrowserTreePane(services);

        JTree tree = treePane.getTree();
        tree.addTreeSelectionListener(this);
        history = new BrowserHistory(services);

        return treePane;
    }

}
