/*
    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.structures.AttributeInfo;
import org.gjt.jclasslib.structures.ClassFile;
import org.gjt.jclasslib.structures.InvalidByteCodeException;
import org.gjt.jclasslib.util.ExtendedJLabel;
import org.gjt.jclasslib.util.HtmlDisplayTextArea;

import javax.swing.*;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.MouseListener;
import java.util.HashMap;

/**
    Base class for all detail panes showing specific information for
    a specific tree node selected in <tt>BrowserTreePane</tt>.
    
    @author <a href="mailto:jclasslib@ej-technologies.com">Ingo Kegel</a>
*/
public abstract class AbstractDetailPane extends JPanel {
    
    /** Text prepended to constant pool hyperlinks. */
    public static final String CPINFO_LINK_TEXT = "cp_info #";

    /** Color for highlighted text (values in key-value pairs). */
    protected static final Color COLOR_HIGHLIGHT = new Color(128, 0, 0);

    /** Services for this detail pane. */
    protected BrowserServices services;

    private HashMap<ExtendedJLabel, MouseListener> labelToMouseListener = new HashMap<ExtendedJLabel, MouseListener>();
 
    /**
        Constructs a detail pane with a specified parent frame.
        @param services browser services
     */
    protected AbstractDetailPane(BrowserServices services) {
        this.services = services;
        setupComponent();
    }

    /**
        Get the associated <tt>BrowserServices</tt> object.
        @return the browser services
     */
    public BrowserServices getBrowserServices() {
        return services;
    }

    /**
        Show the detail pane for a specific tree node 
        selected in <tt>BrowserTreePane</tt>.
        @param treePath the <tt>TreePath</tt> for the selection
                        in <tt>BrowserTreePane</tt>
     */
    public abstract void show(TreePath treePath);

    /**
        Setup the detail pane at the beginning of its life cycle.
     */
    protected abstract void setupComponent();

    /**
        Create a normal label (keys in key-value pairs).
        @return the label
     */
    protected ExtendedJLabel normalLabel() {
        return normalLabel("");
    }

    /**
        Create a normal label (keys in key-value pairs).
        @param text the text for the label
        @return the label
     */
    protected ExtendedJLabel normalLabel(String text) {
        return new ExtendedJLabel(text);
    }

    /**
        Create a highlighted label (values in key-value pairs).
        @return the label
     */
    protected ExtendedJLabel highlightLabel() {
        ExtendedJLabel label = normalLabel();
        label.setForeground(COLOR_HIGHLIGHT);
        return label;
    }
    
    /**
        Create a highlighted text area for HTML display (values in key-value pairs).
        @return the text area
     */
    protected HtmlDisplayTextArea highlightTextArea() {
        HtmlDisplayTextArea textArea = new HtmlDisplayTextArea();
        textArea.setForeground(COLOR_HIGHLIGHT);
        return textArea;
    }

    /**
        Create a label with the appearance of a hyperlink.
        @return the label
     */
    protected ExtendedJLabel linkLabel() {
        ExtendedJLabel label = normalLabel();
        label.setForeground(HtmlDisplayTextArea.COLOR_LINK);
        label.setRequestFocusEnabled(true);
        label.setUnderlined(true);
        return label;
    }
    
    /**
        Determine the index of the tree node selected in <tt>BrowserTreePane</tt>
        among its siblings.
        @param treePath the tree path
        @return the index
     */
    protected int getIndex(TreePath treePath) {
        return ((BrowserTreeNode)treePath.getLastPathComponent()).getIndex();
    }
    
    /**
        Find the attribute pertaining to a specific tree path. 
        @param path the tree path
        @return the attribute
     */
    protected AttributeInfo findAttribute(TreePath path) {
        
        TreePath parentPath = path.getParentPath();
        BrowserTreeNode parentNode = (BrowserTreeNode)parentPath.getLastPathComponent();
        String parentNodeType = parentNode.getType();
        
        ClassFile classFile = services.getClassFile();
        int parentIndex = getIndex(parentPath);
        int index = getIndex(path);
        
        if (parentNodeType.equals(BrowserTreeNode.NODE_NO_CONTENT)) {
            return classFile.getAttributes()[index];

        } else if (parentNodeType.equals(BrowserTreeNode.NODE_FIELD)) {
            return classFile.getFields()[parentIndex].getAttributes()[index];

        } else if (parentNodeType.equals(BrowserTreeNode.NODE_METHOD)) {
            return classFile.getMethods()[parentIndex].getAttributes()[index];

        } else {
            return findAttribute(parentPath).getAttributes()[index];
        }
    }
    
    /**
        Get the name of a constant pool entry.
        @param constantPoolIndex the index of the constant pool entry
        @return the name
     */
    protected String getConstantPoolEntryName(int constantPoolIndex) {

        try {
            return services.getClassFile().getConstantPoolEntryName(constantPoolIndex);
        } catch (InvalidByteCodeException ex) {
            return "invalid constant pool reference";
        }
    }
    
    /**
        Construct a hyperlink into the constant pool.
        @param value the label for the hyperlink source
        @param comment an optional label whose text is automatically set to
                       the name of the constant pool entry
        @param constantPoolIndex the index of the constant pool entry for the
                                 target of the hyperlink
     */
    protected void constantPoolHyperlink(ExtendedJLabel value,
                                         ExtendedJLabel comment,
                                         int constantPoolIndex) {
                                         
        value.setText(CPINFO_LINK_TEXT + constantPoolIndex);
        setupMouseListener(value, new ConstantPoolHyperlinkListener(services, constantPoolIndex));
        value.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));

        applyComment(comment, constantPoolIndex);

    }

    protected void classAttributeIndexHyperlink(ExtendedJLabel value,
                                                ExtendedJLabel comment,
                                                int index,
                                                Class<? extends AttributeInfo> attributeInfoClass,
                                                String text) {

        value.setText(text + index);
        setupMouseListener(value, new ClassAttributeHyperlinkListener(services, index, attributeInfoClass));
        value.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));

        applyComment(comment, index);
    }

    private void applyComment(ExtendedJLabel comment, int constantPoolIndex) {
        if (comment != null) {
            comment.setToolTipText(comment.getText());
            comment.setText("<" + getConstantPoolEntryName(constantPoolIndex) + ">");
        }
    }

    private void setupMouseListener(ExtendedJLabel value, MouseListener mouseListener) {

        MouseListener oldListener = labelToMouseListener.get(value);
        if (oldListener != null) {
            value.removeMouseListener(oldListener);
        }
        value.addMouseListener(mouseListener);
        labelToMouseListener.put(value, mouseListener);
    }
}

